Clients
Clients API
Endpoints for managing your client directory. Each client has a unique email per tenant.
List Clients
GET /api/clients
Permission: clients:read
Retrieve a paginated list of active clients for the current tenant.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| search | string | No | Search by contact name, company name, or email (case-insensitive) |
| page | number | No | Page number (default: 1) |
Response 200 OK:
{
"data": [
{
"id": "clxyz...",
"contactName": "María López",
"companyName": "Distribuidora del Norte S.A.",
"email": "maria@distribuidora.mx",
"phone": "+52 81 1234 5678",
"isActive": true,
"tenantId": "...",
"createdAt": "2026-03-01T10:00:00.000Z",
"updatedAt": "2026-03-15T14:00:00.000Z"
}
],
"total": 45,
"page": 1,
"totalPages": 3
}cURL Example:
curl -X GET "https://cotizera.com/api/clients?search=María&page=1" \
-H "Cookie: next-auth.session-token=YOUR_TOKEN"Create Client
POST /api/clients
Permission: clients:create
Create a new client. Email must be unique within the tenant.
Request Body:
{
"contactName": "Roberto García",
"companyName": "Servicios Industriales MX",
"email": "roberto@servindustrial.mx",
"phone": "+52 55 9876 5432"
}| Field | Type | Required | Description |
|---|---|---|---|
| contactName | string | Yes | Contact person name (min 1 character) |
| companyName | string | No | Company or business name |
| string | Yes | Valid email address (unique per tenant) | |
| phone | string | No | Phone number |
Response 201 Created: Returns the created client object.
Errors:
| Status | Error | When |
|---|---|---|
| 400 | Zod validation message | Invalid input |
| 401 | "No autorizado" | Missing or invalid session |
| 403 | "Sin permisos" | Role lacks clients:create |
| 409 | "Ya existe un cliente con ese correo" | Duplicate email in tenant |
Side Effects:
- Fires
client.createdwebhook event - Triggers onboarding step
first_clientcompletion - Logs audit entry with action
create
Get Single Client
GET /api/clients/:id
Permission: clients:read
Retrieve a single active client. Optionally include their quote history.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| includeQuotes | string | No | Set to "true" to include the client's quotes |
Response 200 OK (with includeQuotes=true):
{
"id": "clxyz...",
"contactName": "María López",
"companyName": "Distribuidora del Norte S.A.",
"email": "maria@distribuidora.mx",
"phone": "+52 81 1234 5678",
"quotes": [
{
"id": "...",
"quoteNumber": 42,
"status": "WON",
"total": "17900.00",
"createdAt": "2026-03-31T10:00:00.000Z",
"_count": { "items": 3 }
}
]
}Errors:
| Status | Error | When |
|---|---|---|
| 404 | "Cliente no encontrado" | Client not found, inactive, or wrong tenant |
Update Client
PUT /api/clients/:id
Permission: clients:update
Update an existing client's information. If the email is changed, uniqueness is re-validated.
Request Body: Same schema as Create Client.
Response 200 OK: Returns the updated client object.
Errors:
| Status | Error | When |
|---|---|---|
| 404 | "Cliente no encontrado" | Client not found or inactive |
| 409 | "Ya existe un cliente con ese correo" | New email conflicts with existing client |
Delete Client (Soft Delete)
DELETE /api/clients/:id
Permission: clients:delete
Soft-deletes a client by setting isActive to false. The client's data is preserved but excluded from listings.
Response 200 OK:
{
"success": true
}Errors:
| Status | Error | When |
|---|---|---|
| 404 | "Cliente no encontrado" | Client not found or already inactive |