Clientes
GET /api/v1/clients clients:read
Listar clientes
Ordenados por id ascendente. Los clientes borrados por RGPD aparecen anonimizados, con erased_at informado.
Permiso necesario: clients:read.
Consulta (query)
| Nombre | Tipo | Obligatorio | Descripción |
|---|
search | string | No | Busca en nombre, teléfono, email y NIF/CIF (mínimo 2 caracteres). |
expand | string [vehicles] | No | Relaciones opcionales a incluir. |
limit | integer · default 50 | No | Resultados por página (1–200). |
cursor | string | No | Cursor opaco devuelto en next_cursor de la página anterior. |
Ejemplo
curl "https://tu-taller.example/api/v1/clients" \
-H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Respuesta
{
"data": [
{
"id": 1204,
"name": "Laura Gómez",
"phone": "+34600111222",
"phone_secondary": null,
"whatsapp_phone": null,
"email": "laura@ejemplo.com",
"billing_email": null,
"tax_id": "12345678Z",
"address": "C/ Mayor 12",
"city": "Barcelona",
"zip": "08001",
"province": "Barcelona",
"country": "ES",
"preferred_language": "es",
"preferred_contact_method": "whatsapp",
"vip": false,
"marketing_opt_out": false,
"channel_opt_out": {
"email": false,
"sms": false,
"whatsapp": false,
"call": false
},
"erased_at": null,
"vehicles": [
{
"id": 871,
"client_id": 1204,
"client": null,
"plate": "1234 KLM",
"vin": "VSSZZZ5FZJR123456",
"brand": "Seat",
"model": "León",
"variant": "1.5 TSI",
"year": 2019,
"registration_date": "2019-03-15",
"fuel": "Gasolina",
"transmission": "Manual",
"engine_code": "DADA",
"horsepower": 130,
"displacement": "1498",
"color_code": null,
"environmental_label": "C",
"km": 84500,
"itv_expiry_date": "2027-03-15",
"status": "activo"
}
]
}
],
"next_cursor": "aWQ6MTIzNA",
"has_more": true
}
POST /api/v1/clients clients:write
Crear un cliente
Nunca fusiona con una ficha existente: si el NIF/CIF, el email o el teléfono ya están en otro cliente responde 409 client_exists con existing_id (o 200 con ese cliente si pasas ?on_conflict=return_existing). El teléfono se guarda como en la ficha (España en 9 dígitos, otros países con prefijo) y el email en minúsculas; no se corrigen erratas. Las bajas comerciales quedan en el registro de consentimientos.
Permiso necesario: clients:write.
Consulta (query)
| Nombre | Tipo | Obligatorio | Descripción |
|---|
on_conflict | string [error, return_existing] · default error | No | error (por defecto): 409 si ya existe. return_existing: 200 con el cliente existente. |
Cabeceras
| Nombre | Tipo | Obligatorio | Descripción |
|---|
Idempotency-Key | string | Sí | Clave única por operación (UUID recomendado). Repetirla con el mismo cuerpo en 24 h devuelve la respuesta guardada con Idempotent-Replay: true; con otro cuerpo, 409 idempotency_conflict. |
Cuerpo
{
"name": "Laura Gómez",
"phone": "600111222",
"phone_secondary": null,
"email": "laura@ejemplo.com",
"billing_email": null,
"tax_id": "12345678Z",
"address": "C/ Mayor 12",
"city": "Barcelona",
"zip": "08001",
"province": "Barcelona",
"country": "ES",
"preferred_language": "es",
"preferred_contact_method": "whatsapp",
"marketing_opt_out": false,
"channel_opt_out": {
"email": false,
"sms": false,
"whatsapp": false,
"call": false
}
}
Ejemplo
curl -X POST "https://tu-taller.example/api/v1/clients" \
-H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name":"Laura Gómez","phone":"600111222","phone_secondary":null,"email":"laura@ejemplo.com","billing_email":null,"tax_id":"12345678Z","address":"C/ Mayor 12","city":"Barcelona","zip":"08001","province":"Barcelona","country":"ES","preferred_language":"es","preferred_contact_method":"whatsapp","marketing_opt_out":false,"channel_opt_out":{"email":false,"sms":false,"whatsapp":false,"call":false}}'
Respuesta
{
"id": 1204,
"name": "Laura Gómez",
"phone": "+34600111222",
"phone_secondary": null,
"whatsapp_phone": null,
"email": "laura@ejemplo.com",
"billing_email": null,
"tax_id": "12345678Z",
"address": "C/ Mayor 12",
"city": "Barcelona",
"zip": "08001",
"province": "Barcelona",
"country": "ES",
"preferred_language": "es",
"preferred_contact_method": "whatsapp",
"vip": false,
"marketing_opt_out": false,
"channel_opt_out": {
"email": false,
"sms": false,
"whatsapp": false,
"call": false
},
"erased_at": null,
"vehicles": [
{
"id": 871,
"client_id": 1204,
"client": {
"id": 1204,
"name": "Laura Gómez"
},
"plate": "1234 KLM",
"vin": "VSSZZZ5FZJR123456",
"brand": "Seat",
"model": "León",
"variant": "1.5 TSI",
"year": 2019,
"registration_date": "2019-03-15",
"fuel": "Gasolina",
"transmission": "Manual",
"engine_code": "DADA",
"horsepower": 130,
"displacement": "1498",
"color_code": null,
"environmental_label": "C",
"km": 84500,
"itv_expiry_date": "2027-03-15",
"status": "activo"
}
]
}
GET /api/v1/clients/{id} clients:read
Detalle de un cliente
Permiso necesario: clients:read.
Parámetros
| Nombre | Tipo | Obligatorio | Descripción |
|---|
id | integer | Sí | Id del cliente. |
Consulta (query)
| Nombre | Tipo | Obligatorio | Descripción |
|---|
expand | string [vehicles] | No | Relaciones opcionales a incluir. |
Ejemplo
curl "https://tu-taller.example/api/v1/clients/1234" \
-H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Respuesta
{
"id": 1204,
"name": "Laura Gómez",
"phone": "+34600111222",
"phone_secondary": null,
"whatsapp_phone": null,
"email": "laura@ejemplo.com",
"billing_email": null,
"tax_id": "12345678Z",
"address": "C/ Mayor 12",
"city": "Barcelona",
"zip": "08001",
"province": "Barcelona",
"country": "ES",
"preferred_language": "es",
"preferred_contact_method": "whatsapp",
"vip": false,
"marketing_opt_out": false,
"channel_opt_out": {
"email": false,
"sms": false,
"whatsapp": false,
"call": false
},
"erased_at": null,
"vehicles": [
{
"id": 871,
"client_id": 1204,
"client": {
"id": 1204,
"name": "Laura Gómez"
},
"plate": "1234 KLM",
"vin": "VSSZZZ5FZJR123456",
"brand": "Seat",
"model": "León",
"variant": "1.5 TSI",
"year": 2019,
"registration_date": "2019-03-15",
"fuel": "Gasolina",
"transmission": "Manual",
"engine_code": "DADA",
"horsepower": 130,
"displacement": "1498",
"color_code": null,
"environmental_label": "C",
"km": 84500,
"itv_expiry_date": "2027-03-15",
"status": "activo"
}
]
}
PATCH /api/v1/clients/{id} clients:write
Modificar un cliente
Solo cambian los campos enviados (null vacía el campo). Cambiar email o teléfono está permitido y deja el valor anterior → nuevo en la actividad del cliente con el nombre de la clave; emite client.updated. No se puede poner el email, teléfono o NIF de OTRA ficha (409 client_exists). Las fichas borradas por RGPD responden 409 client_erased.
Permiso necesario: clients:write.
Parámetros
| Nombre | Tipo | Obligatorio | Descripción |
|---|
id | integer | Sí | Id del cliente. |
Cabeceras
| Nombre | Tipo | Obligatorio | Descripción |
|---|
Idempotency-Key | string | No | Clave única por operación (UUID recomendado). Repetirla con el mismo cuerpo en 24 h devuelve la respuesta guardada con Idempotent-Replay: true; con otro cuerpo, 409 idempotency_conflict. |
Cuerpo
{
"name": "Laura Gómez Ruiz",
"phone": "600111222",
"phone_secondary": null,
"email": "laura@ejemplo.com",
"billing_email": null,
"tax_id": "12345678Z",
"address": "C/ Mayor 12",
"city": "Barcelona",
"zip": "08001",
"province": "Barcelona",
"country": "ES",
"preferred_language": "es",
"preferred_contact_method": "whatsapp",
"marketing_opt_out": false,
"channel_opt_out": {
"email": false,
"sms": false,
"whatsapp": false,
"call": false
}
}
Ejemplo
curl -X PATCH "https://tu-taller.example/api/v1/clients/1234" \
-H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name":"Laura Gómez Ruiz","phone":"600111222","phone_secondary":null,"email":"laura@ejemplo.com","billing_email":null,"tax_id":"12345678Z","address":"C/ Mayor 12","city":"Barcelona","zip":"08001","province":"Barcelona","country":"ES","preferred_language":"es","preferred_contact_method":"whatsapp","marketing_opt_out":false,"channel_opt_out":{"email":false,"sms":false,"whatsapp":false,"call":false}}'
Respuesta
{
"id": 1204,
"name": "Laura Gómez",
"phone": "+34600111222",
"phone_secondary": null,
"whatsapp_phone": null,
"email": "laura@ejemplo.com",
"billing_email": null,
"tax_id": "12345678Z",
"address": "C/ Mayor 12",
"city": "Barcelona",
"zip": "08001",
"province": "Barcelona",
"country": "ES",
"preferred_language": "es",
"preferred_contact_method": "whatsapp",
"vip": false,
"marketing_opt_out": false,
"channel_opt_out": {
"email": false,
"sms": false,
"whatsapp": false,
"call": false
},
"erased_at": null,
"vehicles": [
{
"id": 871,
"client_id": 1204,
"client": {
"id": 1204,
"name": "Laura Gómez"
},
"plate": "1234 KLM",
"vin": "VSSZZZ5FZJR123456",
"brand": "Seat",
"model": "León",
"variant": "1.5 TSI",
"year": 2019,
"registration_date": "2019-03-15",
"fuel": "Gasolina",
"transmission": "Manual",
"engine_code": "DADA",
"horsepower": 130,
"displacement": "1498",
"color_code": null,
"environmental_label": "C",
"km": 84500,
"itv_expiry_date": "2027-03-15",
"status": "activo"
}
]
}
Vehículos
GET /api/v1/vehicles vehicles:read
Listar vehículos
Incluye el último kilometraje anotado y el vencimiento de la ITV.
Permiso necesario: vehicles:read.
Consulta (query)
| Nombre | Tipo | Obligatorio | Descripción |
|---|
plate | string | No | Matrícula exacta (se ignoran espacios y guiones). |
client_id | integer | No | Filtrar por cliente. |
status | string [activo, baja_temporal, baja] | No | Estado del vehículo. |
limit | integer · default 50 | No | Resultados por página (1–200). |
cursor | string | No | Cursor opaco devuelto en next_cursor de la página anterior. |
Ejemplo
curl "https://tu-taller.example/api/v1/vehicles" \
-H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Respuesta
{
"data": [
{
"id": 871,
"client_id": 1204,
"client": {
"id": 1204,
"name": "Laura Gómez"
},
"plate": "1234 KLM",
"vin": "VSSZZZ5FZJR123456",
"brand": "Seat",
"model": "León",
"variant": "1.5 TSI",
"year": 2019,
"registration_date": "2019-03-15",
"fuel": "Gasolina",
"transmission": "Manual",
"engine_code": "DADA",
"horsepower": 130,
"displacement": "1498",
"color_code": null,
"environmental_label": "C",
"km": 84500,
"itv_expiry_date": "2027-03-15",
"status": "activo"
}
],
"next_cursor": "aWQ6MTIzNA",
"has_more": true
}
POST /api/v1/vehicles vehicles:write
Dar de alta un vehículo
Siempre a nombre de un cliente existente (client_id). La matrícula se normaliza (mayúsculas, sin espacios ni guiones). Si ya está en la ficha de OTRO cliente: 409 vehicle_belongs_to_other_client. Si el mismo cliente ya la tiene: 409 vehicle_exists con existing_id (o 200 con ?on_conflict=return_existing).
Permiso necesario: vehicles:write.
Consulta (query)
| Nombre | Tipo | Obligatorio | Descripción |
|---|
on_conflict | string [error, return_existing] · default error | No | error (por defecto) o return_existing. |
Cabeceras
| Nombre | Tipo | Obligatorio | Descripción |
|---|
Idempotency-Key | string | Sí | Clave única por operación (UUID recomendado). Repetirla con el mismo cuerpo en 24 h devuelve la respuesta guardada con Idempotent-Replay: true; con otro cuerpo, 409 idempotency_conflict. |
Cuerpo
{
"client_id": 1204,
"plate": "1234 KLM",
"vin": "VSSZZZ5FZJR123456",
"brand": "Seat",
"model": "León",
"variant": "1.5 TSI",
"year": 2019,
"registration_date": "2019-03-15",
"fuel": "Gasolina",
"transmission": "Manual",
"engine_code": "DADA",
"horsepower": 130,
"displacement": "1498",
"color_code": null,
"environmental_label": "C",
"itv_expiry_date": "2027-03-15",
"status": "activo"
}
Ejemplo
curl -X POST "https://tu-taller.example/api/v1/vehicles" \
-H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"client_id":1204,"plate":"1234 KLM","vin":"VSSZZZ5FZJR123456","brand":"Seat","model":"León","variant":"1.5 TSI","year":2019,"registration_date":"2019-03-15","fuel":"Gasolina","transmission":"Manual","engine_code":"DADA","horsepower":130,"displacement":"1498","color_code":null,"environmental_label":"C","itv_expiry_date":"2027-03-15","status":"activo"}'
Respuesta
{
"id": 871,
"client_id": 1204,
"client": {
"id": 1204,
"name": "Laura Gómez"
},
"plate": "1234 KLM",
"vin": "VSSZZZ5FZJR123456",
"brand": "Seat",
"model": "León",
"variant": "1.5 TSI",
"year": 2019,
"registration_date": "2019-03-15",
"fuel": "Gasolina",
"transmission": "Manual",
"engine_code": "DADA",
"horsepower": 130,
"displacement": "1498",
"color_code": null,
"environmental_label": "C",
"km": 84500,
"itv_expiry_date": "2027-03-15",
"status": "activo"
}
GET /api/v1/vehicles/{id} vehicles:read
Detalle de un vehículo
Permiso necesario: vehicles:read.
Parámetros
| Nombre | Tipo | Obligatorio | Descripción |
|---|
id | integer | Sí | Id del vehículo. |
Ejemplo
curl "https://tu-taller.example/api/v1/vehicles/1234" \
-H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Respuesta
{
"id": 871,
"client_id": 1204,
"client": {
"id": 1204,
"name": "Laura Gómez"
},
"plate": "1234 KLM",
"vin": "VSSZZZ5FZJR123456",
"brand": "Seat",
"model": "León",
"variant": "1.5 TSI",
"year": 2019,
"registration_date": "2019-03-15",
"fuel": "Gasolina",
"transmission": "Manual",
"engine_code": "DADA",
"horsepower": 130,
"displacement": "1498",
"color_code": null,
"environmental_label": "C",
"km": 84500,
"itv_expiry_date": "2027-03-15",
"status": "activo"
}
PATCH /api/v1/vehicles/{id} vehicles:write
Modificar un vehículo
Cambios parciales. client_id no admite null: un vehículo nunca se desvincula de su titular por API (sí puede pasar a otro cliente existente).
Permiso necesario: vehicles:write.
Parámetros
| Nombre | Tipo | Obligatorio | Descripción |
|---|
id | integer | Sí | Id del vehículo. |
Cabeceras
| Nombre | Tipo | Obligatorio | Descripción |
|---|
Idempotency-Key | string | No | Clave única por operación (UUID recomendado). Repetirla con el mismo cuerpo en 24 h devuelve la respuesta guardada con Idempotent-Replay: true; con otro cuerpo, 409 idempotency_conflict. |
Cuerpo
{
"client_id": 1204,
"plate": "1234 KLM",
"vin": "VSSZZZ5FZJR123456",
"brand": "Seat",
"model": "León",
"variant": "1.5 TSI",
"year": 2019,
"registration_date": "2019-03-15",
"fuel": "Gasolina",
"transmission": "Manual",
"engine_code": "DADA",
"horsepower": 130,
"displacement": "1498",
"color_code": null,
"environmental_label": "C",
"itv_expiry_date": "2027-03-15",
"status": "activo"
}
Ejemplo
curl -X PATCH "https://tu-taller.example/api/v1/vehicles/1234" \
-H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"client_id":1204,"plate":"1234 KLM","vin":"VSSZZZ5FZJR123456","brand":"Seat","model":"León","variant":"1.5 TSI","year":2019,"registration_date":"2019-03-15","fuel":"Gasolina","transmission":"Manual","engine_code":"DADA","horsepower":130,"displacement":"1498","color_code":null,"environmental_label":"C","itv_expiry_date":"2027-03-15","status":"activo"}'
Respuesta
{
"id": 871,
"client_id": 1204,
"client": {
"id": 1204,
"name": "Laura Gómez"
},
"plate": "1234 KLM",
"vin": "VSSZZZ5FZJR123456",
"brand": "Seat",
"model": "León",
"variant": "1.5 TSI",
"year": 2019,
"registration_date": "2019-03-15",
"fuel": "Gasolina",
"transmission": "Manual",
"engine_code": "DADA",
"horsepower": 130,
"displacement": "1498",
"color_code": null,
"environmental_label": "C",
"km": 84500,
"itv_expiry_date": "2027-03-15",
"status": "activo"
}
Presupuestos
GET /api/v1/budgets budgets:read
Listar presupuestos
Nunca incluye los presupuestos de uso interno del taller ni datos de coste. El detalle (con partidas y totales) está en /budgets/{id}.
Permiso necesario: budgets:read.
Consulta (query)
| Nombre | Tipo | Obligatorio | Descripción |
|---|
status | string | No | Estado exacto (Pendiente, Enviado, Aprobado, Finalizado, Facturado, Rechazado…). |
client_id | integer | No | Filtrar por cliente. |
vehicle_id | integer | No | Filtrar por vehículo. |
from | string (date-time) | No | Creados a partir de esta fecha. |
to | string (date-time) | No | Creados hasta esta fecha. |
updated_since | string (date-time) | No | Solo registros modificados a partir de esta fecha (ISO 8601). |
limit | integer · default 50 | No | Resultados por página (1–200). |
cursor | string | No | Cursor opaco devuelto en next_cursor de la página anterior. |
Ejemplo
curl "https://tu-taller.example/api/v1/budgets" \
-H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Respuesta
{
"data": [
{
"id": 1234,
"status": "Aprobado",
"created_at": "2026-10-06T09:30:00.000Z",
"updated_at": "2026-10-06T09:30:00.000Z",
"channel": "web",
"client_id": 1204,
"client": {
"id": 1204,
"name": "Laura Gómez"
},
"vehicle_id": 871,
"vehicle": {
"id": 871,
"plate": "1234 KLM",
"brand": "Seat",
"model": "León"
},
"category": {
"id": 5,
"name": "Mantenimiento"
},
"subcategory": {
"id": 5,
"name": "Mantenimiento"
},
"assigned_user": {
"id": 3,
"name": "Marta"
},
"mechanic": {
"id": 3,
"name": "Marta"
},
"appointment": {
"start": "2026-10-06T09:30:00.000Z",
"end": "2026-10-06T10:30:00.000Z",
"status": "confirmed",
"client_confirmed_at": null,
"estimated_duration_minutes": 60,
"box": null
},
"date_in": null,
"date_out": null,
"km": 84500,
"client_reference": null,
"waiting_parts": false,
"on_hold": false,
"hold_until": null,
"client_signed_at": null,
"delivered_at": null,
"reject_reason": null,
"tracking_url": "https://taller.ejemplo.com/public/seguimiento?id=…"
}
],
"next_cursor": "aWQ6MTIzNA",
"has_more": true
}
POST /api/v1/budgets budgets:write
Crear un presupuesto
Entra en «Pendiente» con canal «API», igual que un alta desde el programa (registro, hito de apertura, webhook lead.created). Los totales se calculan en el servidor con el impuesto del taller; si una partida no trae tax_rate usa el del taller. El vehículo, si se indica, debe ser del cliente. No admite categorías de uso interno. Con notify_client=true (y permiso communications:send) se envía al cliente el acuse con su enlace de seguimiento.
Permiso necesario: budgets:write.
Cabeceras
| Nombre | Tipo | Obligatorio | Descripción |
|---|
Idempotency-Key | string | Sí | Clave única por operación (UUID recomendado). Repetirla con el mismo cuerpo en 24 h devuelve la respuesta guardada con Idempotent-Replay: true; con otro cuerpo, 409 idempotency_conflict. |
Cuerpo
{
"client_id": 1204,
"vehicle_id": 871,
"category_id": 5,
"subcategory_id": 51,
"client_reference": "PED-2026-118",
"public_notes": "Revisar también el ruido de la suspensión.",
"lines": [
{
"description": "Cambio de aceite y filtro",
"quantity": 1,
"unit_price": 65,
"tax_rate": 21,
"discount_pct": 0,
"line_type": "labor",
"reference": null,
"group_title": null
}
],
"notify_client": false
}
Ejemplo
curl -X POST "https://tu-taller.example/api/v1/budgets" \
-H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"client_id":1204,"vehicle_id":871,"category_id":5,"subcategory_id":51,"client_reference":"PED-2026-118","public_notes":"Revisar también el ruido de la suspensión.","lines":[{"description":"Cambio de aceite y filtro","quantity":1,"unit_price":65,"tax_rate":21,"discount_pct":0,"line_type":"labor","reference":null,"group_title":null}],"notify_client":false}'
Respuesta
{
"id": 1234,
"status": "Aprobado",
"created_at": "2026-10-06T09:30:00.000Z",
"updated_at": "2026-10-06T09:30:00.000Z",
"channel": "web",
"client_id": 1204,
"client": {
"id": 1204,
"name": "Laura Gómez"
},
"vehicle_id": 871,
"vehicle": {
"id": 871,
"plate": "1234 KLM",
"brand": "Seat",
"model": "León"
},
"category": {
"id": 5,
"name": "Mantenimiento"
},
"subcategory": {
"id": 5,
"name": "Mantenimiento"
},
"assigned_user": {
"id": 3,
"name": "Marta"
},
"mechanic": {
"id": 3,
"name": "Marta"
},
"appointment": {
"start": "2026-10-06T09:30:00.000Z",
"end": "2026-10-06T10:30:00.000Z",
"status": "confirmed",
"client_confirmed_at": null,
"estimated_duration_minutes": 60,
"box": {
"id": 2,
"name": "Elevador 2"
}
},
"date_in": null,
"date_out": null,
"km": 84500,
"client_reference": null,
"waiting_parts": false,
"on_hold": false,
"hold_until": null,
"client_signed_at": null,
"delivered_at": null,
"reject_reason": null,
"tracking_url": "https://taller.ejemplo.com/public/seguimiento?id=…",
"public_notes": "Revisar también el ruido en la suspensión.",
"totals": {
"base": 65,
"tax": 13.65,
"total": 78.65,
"currency": "EUR",
"tax_rates": [
{
"rate": 21,
"base": 65,
"quota": 13.65
}
]
},
"lines": [
{
"id": 5501,
"description": "Cambio de aceite y filtro",
"extended_detail": null,
"reference": null,
"line_type": "labor",
"group_title": null,
"quantity": 1,
"unit_price": 65,
"discount_pct": 0,
"tax_rate": 21,
"tax_exempt_code": null,
"price_estimated": false,
"base": 65,
"currency": "EUR",
"sort_order": 0
}
]
}
GET /api/v1/budgets/{id} budgets:read
Detalle de un presupuesto
Partidas (descripción, cantidad, precio unitario, impuesto, tipo, descuento), totales con desglose, cita, fechas de entrada y salida, responsable y URL pública de seguimiento.
Permiso necesario: budgets:read.
Parámetros
| Nombre | Tipo | Obligatorio | Descripción |
|---|
id | integer | Sí | Id del presupuesto. |
Ejemplo
curl "https://tu-taller.example/api/v1/budgets/1234" \
-H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Respuesta
{
"id": 1234,
"status": "Aprobado",
"created_at": "2026-10-06T09:30:00.000Z",
"updated_at": "2026-10-06T09:30:00.000Z",
"channel": "web",
"client_id": 1204,
"client": {
"id": 1204,
"name": "Laura Gómez"
},
"vehicle_id": 871,
"vehicle": {
"id": 871,
"plate": "1234 KLM",
"brand": "Seat",
"model": "León"
},
"category": {
"id": 5,
"name": "Mantenimiento"
},
"subcategory": {
"id": 5,
"name": "Mantenimiento"
},
"assigned_user": {
"id": 3,
"name": "Marta"
},
"mechanic": {
"id": 3,
"name": "Marta"
},
"appointment": {
"start": "2026-10-06T09:30:00.000Z",
"end": "2026-10-06T10:30:00.000Z",
"status": "confirmed",
"client_confirmed_at": null,
"estimated_duration_minutes": 60,
"box": {
"id": 2,
"name": "Elevador 2"
}
},
"date_in": null,
"date_out": null,
"km": 84500,
"client_reference": null,
"waiting_parts": false,
"on_hold": false,
"hold_until": null,
"client_signed_at": null,
"delivered_at": null,
"reject_reason": null,
"tracking_url": "https://taller.ejemplo.com/public/seguimiento?id=…",
"public_notes": "Revisar también el ruido en la suspensión.",
"totals": {
"base": 65,
"tax": 13.65,
"total": 78.65,
"currency": "EUR",
"tax_rates": [
{
"rate": 21,
"base": 65,
"quota": 13.65
}
]
},
"lines": [
{
"id": 5501,
"description": "Cambio de aceite y filtro",
"extended_detail": null,
"reference": null,
"line_type": "labor",
"group_title": null,
"quantity": 1,
"unit_price": 65,
"discount_pct": 0,
"tax_rate": 21,
"tax_exempt_code": null,
"price_estimated": false,
"base": 65,
"currency": "EUR",
"sort_order": 0
}
]
}
POST /api/v1/budgets/{id}/lines budgets:write
Añadir una partida
Las demás partidas conservan su id. Deja instantánea previa y registro como cualquier edición del programa. 409 budget_locked si el presupuesto está Facturado, Facturado externamente, Cancelado, Rechazado, Desistido o ya tiene factura.
Permiso necesario: budgets:write.
Parámetros
| Nombre | Tipo | Obligatorio | Descripción |
|---|
id | integer | Sí | Id del presupuesto. |
Cabeceras
| Nombre | Tipo | Obligatorio | Descripción |
|---|
Idempotency-Key | string | Sí | Clave única por operación (UUID recomendado). Repetirla con el mismo cuerpo en 24 h devuelve la respuesta guardada con Idempotent-Replay: true; con otro cuerpo, 409 idempotency_conflict. |
Cuerpo
{
"description": "Cambio de aceite y filtro",
"quantity": 1,
"unit_price": 65,
"tax_rate": 21,
"discount_pct": 0,
"line_type": "labor",
"reference": null,
"group_title": null
}
Ejemplo
curl -X POST "https://tu-taller.example/api/v1/budgets/1234/lines" \
-H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"description":"Cambio de aceite y filtro","quantity":1,"unit_price":65,"tax_rate":21,"discount_pct":0,"line_type":"labor","reference":null,"group_title":null}'
Respuesta
{
"line": {
"id": 5501,
"description": "Cambio de aceite y filtro",
"extended_detail": null,
"reference": null,
"line_type": "labor",
"group_title": null,
"quantity": 1,
"unit_price": 65,
"discount_pct": 0,
"tax_rate": 21,
"tax_exempt_code": null,
"price_estimated": false,
"base": 65,
"currency": "EUR",
"sort_order": 0
},
"budget": {
"id": 1234,
"status": "Aprobado",
"created_at": "2026-10-06T09:30:00.000Z",
"updated_at": "2026-10-06T09:30:00.000Z",
"channel": "web",
"client_id": 1204,
"client": {
"id": 1204,
"name": "Laura Gómez"
},
"vehicle_id": 871,
"vehicle": {
"id": 871,
"plate": "1234 KLM",
"brand": "Seat",
"model": "León"
},
"category": {
"id": 5,
"name": "Mantenimiento"
},
"subcategory": {
"id": 5,
"name": "Mantenimiento"
},
"assigned_user": {
"id": 3,
"name": "Marta"
},
"mechanic": {
"id": 3,
"name": "Marta"
},
"appointment": {
"start": "2026-10-06T09:30:00.000Z",
"end": "2026-10-06T10:30:00.000Z",
"status": "confirmed",
"client_confirmed_at": null,
"estimated_duration_minutes": 60,
"box": {
"id": null,
"name": null
}
},
"date_in": null,
"date_out": null,
"km": 84500,
"client_reference": null,
"waiting_parts": false,
"on_hold": false,
"hold_until": null,
"client_signed_at": null,
"delivered_at": null,
"reject_reason": null,
"tracking_url": "https://taller.ejemplo.com/public/seguimiento?id=…",
"public_notes": "Revisar también el ruido en la suspensión.",
"totals": {
"base": 65,
"tax": 13.65,
"total": 78.65,
"currency": "EUR",
"tax_rates": [
{
"rate": 21,
"base": 65,
"quota": 13.65
}
]
},
"lines": [
{
"id": 5501,
"description": "Cambio de aceite y filtro",
"extended_detail": null,
"reference": null,
"line_type": "labor",
"group_title": null,
"quantity": 1,
"unit_price": 65,
"discount_pct": 0,
"tax_rate": 21,
"tax_exempt_code": null,
"price_estimated": false,
"base": 65,
"currency": "EUR",
"sort_order": 0
}
]
}
}
PATCH /api/v1/budgets/{id}/lines/{lineId} budgets:write
Modificar una partida
Permiso necesario: budgets:write.
Parámetros
| Nombre | Tipo | Obligatorio | Descripción |
|---|
id | integer | Sí | Id del presupuesto. |
lineId | integer | Sí | Id de la partida. |
Cabeceras
| Nombre | Tipo | Obligatorio | Descripción |
|---|
Idempotency-Key | string | No | Clave única por operación (UUID recomendado). Repetirla con el mismo cuerpo en 24 h devuelve la respuesta guardada con Idempotent-Replay: true; con otro cuerpo, 409 idempotency_conflict. |
Cuerpo
{
"description": "Cambio de aceite y filtro",
"quantity": 1,
"unit_price": 65,
"tax_rate": 21,
"discount_pct": 0,
"line_type": "labor",
"reference": null,
"group_title": null
}
Ejemplo
curl -X PATCH "https://tu-taller.example/api/v1/budgets/1234/lines/5501" \
-H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"description":"Cambio de aceite y filtro","quantity":1,"unit_price":65,"tax_rate":21,"discount_pct":0,"line_type":"labor","reference":null,"group_title":null}'
Respuesta
{
"line": {
"id": 5501,
"description": "Cambio de aceite y filtro",
"extended_detail": null,
"reference": null,
"line_type": "labor",
"group_title": null,
"quantity": 1,
"unit_price": 65,
"discount_pct": 0,
"tax_rate": 21,
"tax_exempt_code": null,
"price_estimated": false,
"base": 65,
"currency": "EUR",
"sort_order": 0
},
"budget": {
"id": 1234,
"status": "Aprobado",
"created_at": "2026-10-06T09:30:00.000Z",
"updated_at": "2026-10-06T09:30:00.000Z",
"channel": "web",
"client_id": 1204,
"client": {
"id": 1204,
"name": "Laura Gómez"
},
"vehicle_id": 871,
"vehicle": {
"id": 871,
"plate": "1234 KLM",
"brand": "Seat",
"model": "León"
},
"category": {
"id": 5,
"name": "Mantenimiento"
},
"subcategory": {
"id": 5,
"name": "Mantenimiento"
},
"assigned_user": {
"id": 3,
"name": "Marta"
},
"mechanic": {
"id": 3,
"name": "Marta"
},
"appointment": {
"start": "2026-10-06T09:30:00.000Z",
"end": "2026-10-06T10:30:00.000Z",
"status": "confirmed",
"client_confirmed_at": null,
"estimated_duration_minutes": 60,
"box": {
"id": null,
"name": null
}
},
"date_in": null,
"date_out": null,
"km": 84500,
"client_reference": null,
"waiting_parts": false,
"on_hold": false,
"hold_until": null,
"client_signed_at": null,
"delivered_at": null,
"reject_reason": null,
"tracking_url": "https://taller.ejemplo.com/public/seguimiento?id=…",
"public_notes": "Revisar también el ruido en la suspensión.",
"totals": {
"base": 65,
"tax": 13.65,
"total": 78.65,
"currency": "EUR",
"tax_rates": [
{
"rate": 21,
"base": 65,
"quota": 13.65
}
]
},
"lines": [
{
"id": 5501,
"description": "Cambio de aceite y filtro",
"extended_detail": null,
"reference": null,
"line_type": "labor",
"group_title": null,
"quantity": 1,
"unit_price": 65,
"discount_pct": 0,
"tax_rate": 21,
"tax_exempt_code": null,
"price_estimated": false,
"base": 65,
"currency": "EUR",
"sort_order": 0
}
]
}
}
DELETE /api/v1/budgets/{id}/lines/{lineId} budgets:write
Quitar una partida
Devuelve el presupuesto con los totales recalculados. La partida queda en la instantánea previa del historial de versiones.
Permiso necesario: budgets:write.
Parámetros
| Nombre | Tipo | Obligatorio | Descripción |
|---|
id | integer | Sí | Id del presupuesto. |
lineId | integer | Sí | Id de la partida. |
Ejemplo
curl -X DELETE "https://tu-taller.example/api/v1/budgets/1234/lines/5501" \
-H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Respuesta
{
"id": 1234,
"status": "Aprobado",
"created_at": "2026-10-06T09:30:00.000Z",
"updated_at": "2026-10-06T09:30:00.000Z",
"channel": "web",
"client_id": 1204,
"client": {
"id": 1204,
"name": "Laura Gómez"
},
"vehicle_id": 871,
"vehicle": {
"id": 871,
"plate": "1234 KLM",
"brand": "Seat",
"model": "León"
},
"category": {
"id": 5,
"name": "Mantenimiento"
},
"subcategory": {
"id": 5,
"name": "Mantenimiento"
},
"assigned_user": {
"id": 3,
"name": "Marta"
},
"mechanic": {
"id": 3,
"name": "Marta"
},
"appointment": {
"start": "2026-10-06T09:30:00.000Z",
"end": "2026-10-06T10:30:00.000Z",
"status": "confirmed",
"client_confirmed_at": null,
"estimated_duration_minutes": 60,
"box": {
"id": 2,
"name": "Elevador 2"
}
},
"date_in": null,
"date_out": null,
"km": 84500,
"client_reference": null,
"waiting_parts": false,
"on_hold": false,
"hold_until": null,
"client_signed_at": null,
"delivered_at": null,
"reject_reason": null,
"tracking_url": "https://taller.ejemplo.com/public/seguimiento?id=…",
"public_notes": "Revisar también el ruido en la suspensión.",
"totals": {
"base": 65,
"tax": 13.65,
"total": 78.65,
"currency": "EUR",
"tax_rates": [
{
"rate": 21,
"base": 65,
"quota": 13.65
}
]
},
"lines": [
{
"id": 5501,
"description": "Cambio de aceite y filtro",
"extended_detail": null,
"reference": null,
"line_type": "labor",
"group_title": null,
"quantity": 1,
"unit_price": 65,
"discount_pct": 0,
"tax_rate": 21,
"tax_exempt_code": null,
"price_estimated": false,
"base": 65,
"currency": "EUR",
"sort_order": 0
}
]
}
POST /api/v1/budgets/{id}/status budgets:write
Cambiar el estado
Admite Pendiente/En cotización/Enviado (antes de la aceptación), En curso (presupuesto ya aceptado), Finalizado (desde Aprobado, En curso o En espera) y Cancelado. «Aprobado» responde 403 client_acceptance_required con la tracking_url: la aceptación la firma el cliente. Facturar, rechazar o desistir responden 403 status_transition_forbidden; desde Facturado o Cancelado, 409 budget_locked. «Enviado» exige además communications:send y sent_via: registra que TU sistema ya lo envió (no lo envía). Ningún cambio avisa al cliente salvo Finalizado con notify_client=true y communications:send.
Permiso necesario: budgets:write.
Parámetros
| Nombre | Tipo | Obligatorio | Descripción |
|---|
id | integer | Sí | Id del presupuesto. |
Cabeceras
| Nombre | Tipo | Obligatorio | Descripción |
|---|
Idempotency-Key | string | Sí | Clave única por operación (UUID recomendado). Repetirla con el mismo cuerpo en 24 h devuelve la respuesta guardada con Idempotent-Replay: true; con otro cuerpo, 409 idempotency_conflict. |
Cuerpo
{
"status": "En curso",
"sent_via": "email",
"notify_client": false
}
Ejemplo
curl -X POST "https://tu-taller.example/api/v1/budgets/1234/status" \
-H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"status":"En curso","sent_via":"email","notify_client":false}'
Respuesta
{
"id": 1234,
"status": "Aprobado",
"created_at": "2026-10-06T09:30:00.000Z",
"updated_at": "2026-10-06T09:30:00.000Z",
"channel": "web",
"client_id": 1204,
"client": {
"id": 1204,
"name": "Laura Gómez"
},
"vehicle_id": 871,
"vehicle": {
"id": 871,
"plate": "1234 KLM",
"brand": "Seat",
"model": "León"
},
"category": {
"id": 5,
"name": "Mantenimiento"
},
"subcategory": {
"id": 5,
"name": "Mantenimiento"
},
"assigned_user": {
"id": 3,
"name": "Marta"
},
"mechanic": {
"id": 3,
"name": "Marta"
},
"appointment": {
"start": "2026-10-06T09:30:00.000Z",
"end": "2026-10-06T10:30:00.000Z",
"status": "confirmed",
"client_confirmed_at": null,
"estimated_duration_minutes": 60,
"box": {
"id": 2,
"name": "Elevador 2"
}
},
"date_in": null,
"date_out": null,
"km": 84500,
"client_reference": null,
"waiting_parts": false,
"on_hold": false,
"hold_until": null,
"client_signed_at": null,
"delivered_at": null,
"reject_reason": null,
"tracking_url": "https://taller.ejemplo.com/public/seguimiento?id=…",
"public_notes": "Revisar también el ruido en la suspensión.",
"totals": {
"base": 65,
"tax": 13.65,
"total": 78.65,
"currency": "EUR",
"tax_rates": [
{
"rate": 21,
"base": 65,
"quota": 13.65
}
]
},
"lines": [
{
"id": 5501,
"description": "Cambio de aceite y filtro",
"extended_detail": null,
"reference": null,
"line_type": "labor",
"group_title": null,
"quantity": 1,
"unit_price": 65,
"discount_pct": 0,
"tax_rate": 21,
"tax_exempt_code": null,
"price_estimated": false,
"base": 65,
"currency": "EUR",
"sort_order": 0
}
]
}
POST /api/v1/budgets/{id}/documents budgets:write
Adjuntar un documento
multipart/form-data con el campo «file» (JPEG, PNG, WebP o PDF, comprobado por su contenido; máximo 4 MB). Por defecto solo lo ve el taller; client_visible=true lo enseña en el enlace de seguimiento y mechanic_visible=true en la app del mecánico. La huella de idempotencia incluye el fichero.
Permiso necesario: budgets:write.
Parámetros
| Nombre | Tipo | Obligatorio | Descripción |
|---|
id | integer | Sí | Id del presupuesto. |
Cabeceras
| Nombre | Tipo | Obligatorio | Descripción |
|---|
Idempotency-Key | string | Sí | Clave única por operación (UUID recomendado). Repetirla con el mismo cuerpo en 24 h devuelve la respuesta guardada con Idempotent-Replay: true; con otro cuerpo, 409 idempotency_conflict. |
Cuerpo (multipart/form-data)
| Nombre | Tipo | Obligatorio | Descripción |
|---|
file | string (binary) | Sí | Fichero |
client_visible | string [true, false] | No | |
mechanic_visible | string [true, false] | No | |
Ejemplo
curl -X POST "https://tu-taller.example/api/v1/budgets/1234/documents" \
-H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Idempotency-Key: $(uuidgen)" \
-F "file=@fichero.pdf" \
-F "client_visible=false" \
-F "mechanic_visible=false"
Respuesta
{
"id": 991,
"budget_id": 1234,
"filename": "parte-de-trabajo.pdf",
"content_type": "application/pdf",
"size": 182044,
"url": "https://…/leads/1234/api-parte-de-trabajo.pdf",
"client_visible": false,
"mechanic_visible": false,
"created_at": "2026-10-06T09:30:00.000Z"
}