API Quick Start Guide
Tenanto provides a RESTful API for programmatic access to your SaaS data. This guide covers authentication and basic usage.
Base URL
All API endpoints are prefixed with:
https://your-tenant.yourdomain.com/api/v1/
Example: https://demo.tenanto.local/api/v1/projects
Authentication
The API uses Bearer token authentication via Laravel Sanctum.
Getting a Token
curl -X POST https://demo.tenanto.local/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "your-password"
}'
Response:
{
"success": true,
"message": "Login successful",
"data": {
"token": "1|abc123xyz...",
"user": {
"id": 1,
"name": "John Doe",
"email": "[email protected]"
}
}
}
Using the Token
Include the token in the Authorization header:
curl https://demo.tenanto.local/api/v1/projects \
-H "Authorization: Bearer 1|abc123xyz..."
Token Expiration
Tokens expire after 7 days by default. Configure in .env:
SANCTUM_TOKEN_EXPIRATION_DAYS=7
Revoking a Token
curl -X POST https://demo.tenanto.local/api/v1/auth/logout \
-H "Authorization: Bearer 1|abc123xyz..."
Rate Limiting
| Endpoint Type | Limit |
|---|---|
| Authentication | 60/minute |
| Authenticated requests | 60/minute |
| Guest requests | 30/minute |
| Read operations | 120/minute |
Rate limit headers are included in responses:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
Response Format
Success Response
{
"success": true,
"message": "Optional message",
"data": {
// Response data
}
}
Error Response
{
"success": false,
"message": "Error description",
"errors": {
"field": ["Validation error message"]
}
}
HTTP Status Codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not Found |
| 422 | Validation Error |
| 429 | Rate Limited |
| 500 | Server Error |
API Endpoints
Authentication
| Method | Endpoint | Description |
|---|---|---|
| POST | /auth/login |
Login and get token |
| POST | /auth/logout |
Revoke current token |
| GET | /auth/me |
Get current user |
Projects
| Method | Endpoint | Description |
|---|---|---|
| GET | /projects |
List projects |
| POST | /projects |
Create project |
| GET | /projects/{id} |
Get project |
| PUT | /projects/{id} |
Update project |
| DELETE | /projects/{id} |
Delete project |
| POST | /projects/{id}/archive |
Archive project |
| POST | /projects/{id}/unarchive |
Unarchive project |
Tasks
| Method | Endpoint | Description |
|---|---|---|
| GET | /projects/{id}/tasks |
List project tasks |
| POST | /projects/{id}/tasks |
Create task |
| GET | /tasks/{id} |
Get task |
| PUT | /tasks/{id} |
Update task |
| DELETE | /tasks/{id} |
Delete task |
| POST | /tasks/{id}/complete |
Mark task complete |
Examples
List Projects
curl https://demo.tenanto.local/api/v1/projects \
-H "Authorization: Bearer YOUR_TOKEN"
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
| search | string | Search in name/description |
| archived | boolean | Filter archived projects |
| per_page | integer | Items per page (max 100) |
| page | integer | Page number |
Response:
{
"success": true,
"data": {
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Website Redesign",
"description": "Complete overhaul of company website",
"archived_at": null,
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-15T10:00:00Z"
}
],
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 15,
"total": 1
}
}
}
Create Project
curl -X POST https://demo.tenanto.local/api/v1/projects \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "New Project",
"description": "Project description"
}'
Create Task
curl -X POST https://demo.tenanto.local/api/v1/projects/PROJECT_ID/tasks \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Task title",
"description": "Task description",
"status": "todo",
"priority": "high",
"due_date": "2025-02-01"
}'
Task Status Values:
todoin_progressin_reviewdone
Task Priority Values:
lowmediumhighurgent
Update Task
curl -X PUT https://demo.tenanto.local/api/v1/tasks/TASK_ID \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "in_progress"
}'
Complete Task
curl -X POST https://demo.tenanto.local/api/v1/tasks/TASK_ID/complete \
-H "Authorization: Bearer YOUR_TOKEN"
Pagination
List endpoints return paginated results:
{
"data": [...],
"meta": {
"current_page": 1,
"last_page": 5,
"per_page": 15,
"total": 72,
"from": 1,
"to": 15
},
"links": {
"first": "...?page=1",
"last": "...?page=5",
"prev": null,
"next": "...?page=2"
}
}
Error Handling
Validation Errors (422)
{
"success": false,
"message": "The given data was invalid.",
"errors": {
"name": ["The name field is required."],
"email": ["The email must be a valid email address."]
}
}
Authentication Error (401)
{
"success": false,
"message": "Unauthenticated."
}
Not Found (404)
{
"success": false,
"message": "Project not found"
}
Tenant Isolation
The API automatically scopes all requests to the current tenant:
- Requests to
acme.domain.comonly access Acme's data - Requests to
corp.domain.comonly access Corp's data
You cannot access another tenant's data via API.
SDK Examples
JavaScript/TypeScript
const API_URL = 'https://demo.tenanto.local/api/v1';
let token = null;
async function login(email, password) {
const response = await fetch(`${API_URL}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const data = await response.json();
token = data.data.token;
return data;
}
async function getProjects() {
const response = await fetch(`${API_URL}/projects`, {
headers: { 'Authorization': `Bearer ${token}` }
});
return response.json();
}
// Usage
await login('[email protected]', 'password');
const projects = await getProjects();
PHP
<?php
$apiUrl = 'https://demo.tenanto.local/api/v1';
$token = null;
function login($email, $password) {
global $apiUrl, $token;
$ch = curl_init("$apiUrl/auth/login");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'email' => $email,
'password' => $password
])
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
$token = $response['data']['token'];
return $response;
}
function getProjects() {
global $apiUrl, $token;
$ch = curl_init("$apiUrl/projects");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $token"]
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
return $response;
}
// Usage
login('[email protected]', 'password');
$projects = getProjects();
Next Steps
- Full API Reference - Complete endpoint documentation
- Customization - Add custom API endpoints