Tenanto API Documentation
Version: 1.0 Base URL:
/api/v1/Authentication: Bearer Token (Laravel Sanctum)
Table of Contents
Authentication
All API requests (except login) require a Bearer token in the Authorization header:
Authorization: Bearer {token}
Login
Create an API token by providing valid credentials.
POST /api/v1/auth/login
Content-Type: application/json
Request Body:
{
"email": "[email protected]",
"password": "your-password",
"device_name": "my-app" // optional, defaults to "api-token"
}
Success Response (200):
{
"success": true,
"message": "Login successful",
"data": {
"user": {
"id": 1,
"name": "John Doe",
"email": "[email protected]",
"email_verified_at": "2024-01-01T00:00:00+00:00",
"is_tenant_owner": true,
"created_at": "2024-01-01T00:00:00+00:00",
"updated_at": "2024-01-01T00:00:00+00:00"
},
"token": "1|abc123...",
"token_type": "Bearer"
}
}
Error Responses:
422- Invalid credentials or validation error403- Email not verified or system admin attempting tenant access
Logout
Revoke the current API token.
POST /api/v1/auth/logout
Authorization: Bearer {token}
Success Response (200):
{
"success": true,
"message": "Logged out successfully"
}
Get Current User
Retrieve the authenticated user's profile.
GET /api/v1/auth/me
Authorization: Bearer {token}
Success Response (200):
{
"success": true,
"message": "Success",
"data": {
"id": 1,
"name": "John Doe",
"email": "[email protected]",
"email_verified_at": "2024-01-01T00:00:00+00:00",
"is_tenant_owner": true,
"created_at": "2024-01-01T00:00:00+00:00",
"updated_at": "2024-01-01T00:00:00+00:00"
}
}
Demo
Create a demo tenant (public endpoint, rate-limited).
POST /api/v1/demo
Content-Type: application/json
Request Body:
{
"email": "[email protected]",
"name": "Demo User",
"company_name": "Demo Company"
}
Success Response (201):
{
"success": true,
"message": "Demo tenant created successfully",
"data": {
"tenant_id": 123,
"tenant_slug": "demo-abc123",
"tenant_url": "https://demo-abc123.example.com",
"app_url": "https://demo-abc123.example.com/app",
"email": "[email protected]",
"password": "demo-password-123",
"expires_at": "2026-01-25T12:00:00+00:00"
}
}
Error Responses:
403- Demo creation disabled429- Rate limit exceeded503- Demo password not configured in production
Projects
List Projects
Retrieve a paginated list of projects for the current tenant.
GET /api/v1/projects
Authorization: Bearer {token}
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
search |
string | - | Filter by project name |
archived |
boolean | false | Show archived projects only |
sort |
string | created_at | Sort field (name, created_at, updated_at) |
direction |
string | desc | Sort direction (asc, desc) |
per_page |
integer | 15 | Items per page (max 100) |
page |
integer | 1 | Page number |
Success Response (200):
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Marketing Campaign",
"description": "Q1 marketing initiatives",
"color": "#3B82F6",
"is_archived": false,
"settings": {},
"open_tasks_count": 5,
"completed_tasks_count": 12,
"created_at": "2024-01-01T00:00:00+00:00",
"updated_at": "2024-01-01T00:00:00+00:00"
}
],
"links": {
"first": "/api/v1/projects?page=1",
"last": "/api/v1/projects?page=3",
"prev": null,
"next": "/api/v1/projects?page=2"
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 3,
"per_page": 15,
"to": 15,
"total": 42
}
}
Create Project
Create a new project.
POST /api/v1/projects
Authorization: Bearer {token}
Content-Type: application/json
Request Body:
{
"name": "New Project", // required, max 255 chars
"description": "Description", // optional, max 5000 chars
"color": "#3B82F6", // optional, hex color
"settings": {} // optional, JSON object
}
Success Response (201):
{
"success": true,
"message": "Project created successfully",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "New Project",
"description": "Description",
"color": "#3B82F6",
"is_archived": false,
"settings": {},
"created_at": "2024-01-01T00:00:00+00:00",
"updated_at": "2024-01-01T00:00:00+00:00"
}
}
Get Project
Retrieve a specific project by UUID.
GET /api/v1/projects/{uuid}
Authorization: Bearer {token}
Success Response (200):
{
"success": true,
"message": "Success",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Marketing Campaign",
"description": "Q1 marketing initiatives",
"color": "#3B82F6",
"is_archived": false,
"settings": {},
"open_tasks_count": 5,
"completed_tasks_count": 12,
"created_at": "2024-01-01T00:00:00+00:00",
"updated_at": "2024-01-01T00:00:00+00:00"
}
}
Update Project
Update an existing project.
PUT /api/v1/projects/{uuid}
Authorization: Bearer {token}
Content-Type: application/json
Request Body:
{
"name": "Updated Name",
"description": "Updated description",
"color": "#10B981"
}
Success Response (200):
{
"success": true,
"message": "Project updated successfully",
"data": { ... }
}
Delete Project
Delete a project.
DELETE /api/v1/projects/{uuid}
Authorization: Bearer {token}
Success Response (200):
{
"success": true,
"message": "Project deleted successfully"
}
Archive Project
Archive a project to hide it from default listings.
POST /api/v1/projects/{uuid}/archive
Authorization: Bearer {token}
Success Response (200):
{
"success": true,
"message": "Project archived successfully",
"data": {
"is_archived": true,
...
}
}
Unarchive Project
Restore an archived project.
POST /api/v1/projects/{uuid}/unarchive
Authorization: Bearer {token}
Success Response (200):
{
"success": true,
"message": "Project unarchived successfully",
"data": {
"is_archived": false,
...
}
}
Tasks
List Tasks
Retrieve tasks for a specific project.
GET /api/v1/projects/{project_uuid}/tasks
Authorization: Bearer {token}
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
status |
string | Filter by status (todo, in_progress, in_review, done, cancelled) |
priority |
string | Filter by priority (low, medium, high, urgent) |
open |
boolean | Filter open (true) or completed (false) tasks |
overdue |
boolean | Show only overdue tasks |
assigned_to |
integer | Filter by assignee user ID |
my_tasks |
boolean | Show only tasks assigned to current user |
search |
string | Search by task title |
sort |
string | Sort field (title, status, priority, due_date, created_at, sort_order) |
direction |
string | Sort direction (asc, desc) |
per_page |
integer | Items per page (max 100) |
Success Response (200):
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"title": "Design homepage mockup",
"description": "Create wireframes for the new homepage",
"status": {
"value": "in_progress",
"label": "In Progress",
"color": "blue",
"is_open": true
},
"priority": {
"value": "high",
"label": "High",
"color": "orange"
},
"due_date": "2024-02-15",
"completed_at": null,
"is_overdue": false,
"is_due_soon": true,
"sort_order": 1,
"assignee": {
"id": 1,
"name": "John Doe",
"email": "[email protected]"
},
"creator": {
"id": 2,
"name": "Jane Smith",
"email": "[email protected]"
},
"created_at": "2024-01-01T00:00:00+00:00",
"updated_at": "2024-01-01T00:00:00+00:00"
}
],
"links": { ... },
"meta": { ... }
}
Create Task
Create a new task within a project.
POST /api/v1/projects/{project_uuid}/tasks
Authorization: Bearer {token}
Content-Type: application/json
Request Body:
{
"title": "New Task", // required, max 255 chars
"description": "Task description", // optional, max 10000 chars
"status": "todo", // optional: todo, in_progress, in_review, done, cancelled
"priority": "medium", // optional: low, medium, high, urgent
"due_date": "2024-02-15", // optional, date (must be today or future)
"assigned_to": 1, // optional, user ID
"sort_order": 0 // optional, integer
}
Success Response (201):
{
"success": true,
"message": "Task created successfully",
"data": { ... }
}
Get Task
Retrieve a specific task.
GET /api/v1/tasks/{uuid}
Authorization: Bearer {token}
Success Response (200):
{
"success": true,
"message": "Success",
"data": { ... }
}
Update Task
Update an existing task.
PUT /api/v1/tasks/{uuid}
Authorization: Bearer {token}
Content-Type: application/json
Request Body:
{
"title": "Updated Title",
"status": "in_progress",
"priority": "high",
"due_date": "2024-03-01",
"assigned_to": 2
}
Success Response (200):
{
"success": true,
"message": "Task updated successfully",
"data": { ... }
}
Delete Task
Delete a task.
DELETE /api/v1/tasks/{uuid}
Authorization: Bearer {token}
Success Response (200):
{
"success": true,
"message": "Task deleted successfully"
}
Complete Task
Mark a task as completed.
POST /api/v1/tasks/{uuid}/complete
Authorization: Bearer {token}
Success Response (200):
{
"success": true,
"message": "Task marked as completed",
"data": {
"status": {
"value": "done",
"label": "Done",
"color": "green",
"is_open": false
},
"completed_at": "2024-01-15T10:30:00+00:00",
...
}
}
Response Formats
Success Response
{
"success": true,
"message": "Operation description",
"data": { ... }
}
Error Response
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"details": { ... } // optional, validation errors
}
}
Error Handling
HTTP Status Codes
| Code | Description |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request |
| 401 | Unauthorized (invalid/missing token) |
| 403 | Forbidden (insufficient permissions) |
| 404 | Not Found |
| 422 | Validation Error |
| 429 | Too Many Requests |
| 500 | Internal Server Error |
Error Codes
| Code | Description |
|---|---|
BAD_REQUEST |
Invalid request format |
UNAUTHORIZED |
Authentication required |
FORBIDDEN |
Permission denied |
NOT_FOUND |
Resource not found |
VALIDATION_ERROR |
Input validation failed |
TOO_MANY_REQUESTS |
Rate limit exceeded |
INTERNAL_SERVER_ERROR |
Server error |
Rate Limiting
The API implements tiered rate limiting:
| Limiter | Limit | Scope |
|---|---|---|
api |
60 requests/minute | Authenticated users |
api-auth |
10 requests/minute | Authentication endpoints |
api-read |
120 requests/minute | Read operations (GET) |
demo |
Configurable | Demo creation endpoint |
In addition, tenants are limited by their plan's daily API quota (api_requests_per_day).
When rate limit is exceeded, the API returns:
- HTTP Status:
429 Too Many Requests - Headers:
X-RateLimit-Limit,X-RateLimit-Remaining,Retry-After
Tenant Isolation
All API endpoints enforce tenant isolation:
- Automatic Filtering: All queries are automatically filtered by the authenticated user's tenant
- Resource Access: Users can only access resources belonging to their tenant
- Cross-Tenant Protection: Attempting to access another tenant's resources returns
403or404 - Automatic Assignment: New resources are automatically assigned to the user's tenant
Important Notes
- System administrators cannot use the tenant API (returns
403) - Unverified email accounts cannot authenticate (returns
403) - Resource UUIDs are used in URLs for security (not auto-increment IDs)
Examples
cURL Examples
Login:
curl -X POST https://example.com/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"password"}'
List Projects:
curl -X GET https://example.com/api/v1/projects \
-H "Authorization: Bearer YOUR_TOKEN"
Create Task:
curl -X POST https://example.com/api/v1/projects/{uuid}/tasks \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"New Task","priority":"high"}'
Changelog
v1.0 (2025-11-29)
- Initial API release
- Authentication endpoints (login, logout, me)
- Project CRUD with archive/unarchive
- Task CRUD with complete action
- Tenant isolation
- Rate limiting
Related Documentation
- Multi-Tenancy Architecture - Tenant isolation
- Authorization & Permissions - RBAC and policies
- Security Guide - Security best practices
- Example Module - Building custom modules