P Programa Taller
ESCAT
▶ Demo 3 meses por 1 €
API REST v1

Referencia de la API

Todos los endpoints de la versión 1 con sus parámetros, ejemplos de petición y respuesta, permisos, errores y eventos de webhook.

URL baseCada taller tiene la API en su dominio: https://<su-dominio>/api/v1. La URL de tu taller aparece en «Desarrolladores / API»; en los ejemplos usamos https://tu-taller.example.

Autenticación

Envía la clave en la cabecera Authorization: Bearer pt_live_… (o X-Api-Key). Las claves pt_test_… pueden leer, pero cualquier operación con efectos responde 403 test_key_forbidden. Para probar escrituras, aceptaciones, facturas y mensajes sin riesgo usa el entorno de pruebas: https://demo.programa-taller.es/desarrolladores-sandbox (datos ficticios, nada sale de verdad).

Convenciones

Fechas en ISO 8601 UTC; importes en euros con dos decimales y currency: "EUR"; identificadores enteros; nombres de campo estables en snake_case. El personal del taller aparece solo como { id, name }.

Paginación

Los listados devuelven { data, next_cursor, has_more }. Pide la página siguiente repitiendo la llamada con ?cursor=<next_cursor>. limit va de 1 a 200 (50 por defecto).

Formato de error

{
  "error": {
    "code": "insufficient_scope",
    "message": "La clave API no tiene el permiso «budgets:read»."
  }
}
HTTPCódigoMensaje
401missing_api_keyFalta la clave API: envíala en la cabecera Authorization: Bearer pt_… o X-Api-Key.
401invalid_api_keyLa clave API no es válida para esta instancia.
401revoked_api_keyLa clave API está revocada.
401expired_api_keyLa clave API ha caducado.
403ip_not_allowedLa dirección IP de origen no está en la lista permitida de esta clave.
403insufficient_scopeLa clave API no tiene el permiso necesario para esta operación.
403test_key_forbiddenUna clave de prueba (pt_test_) no puede realizar operaciones con efectos: usa una clave live.
429rate_limitedHas superado el límite de peticiones de esta clave. Espera y reintenta.
503instance_rate_limitedLa instancia está recibiendo demasiadas peticiones por API en este momento. Reintenta en unos segundos.
429plan_quota_exceededSe ha agotado el cupo diario de peticiones del plan de API del taller. Se renueva a las 00:00 UTC; para más volumen, mejora el plan.
403plan_scope_not_allowedEl plan de API del taller no incluye este permiso. Para usarlo hay que mejorar el plan.
400bad_requestLa petición no es válida.
404not_foundNo se ha encontrado el recurso.
502upstream_errorUn servicio externo ha rechazado la operación.
500internal_errorError interno.
422validation_errorEl cuerpo de la petición no es válido: revisa la lista «fields».
400idempotency_key_requiredFalta la cabecera Idempotency-Key (obligatoria en todo POST; de 8 a 255 caracteres visibles).
409idempotency_conflictEsa Idempotency-Key ya se usó en las últimas 24 h con otra petición distinta.
409idempotency_in_progressHay otra petición con la misma Idempotency-Key en curso. Reintenta en unos segundos.
409client_existsYa existe un cliente con ese teléfono, email o NIF/CIF.
409client_erasedEl cliente pidió el borrado de sus datos (RGPD): su ficha no admite cambios ni altas asociadas.
409vehicle_existsEse cliente ya tiene un vehículo con esa matrícula.
409vehicle_belongs_to_other_clientEsa matrícula ya está dada de alta a nombre de otro cliente.
409budget_lockedEl presupuesto está cerrado y ya no admite este cambio.
409invalid_status_transitionEl presupuesto no puede pasar a ese estado desde el estado actual.
403status_transition_forbiddenEse cambio de estado no está disponible por API.
403client_acceptance_requiredPara aceptar un presupuesto usa POST /budgets/{id}/accept con el permiso budgets:accept y la evidencia de quién acepta.
403booking_mode_propose_onlyEl taller trabaja en modo «proponer cita»: solo se pueden crear propuestas que el taller confirma.
409slot_unavailableEse hueco no está disponible.
413payload_too_largeEl fichero supera el tamaño máximo (4 MB).
415unsupported_media_typeTipo de fichero no admitido: solo JPEG, PNG, WebP o PDF.
422fiscal_data_incompleteFaltan datos fiscales del cliente para poder facturar: revisa la lista «missing».
409budget_not_invoiceableEl presupuesto no se puede facturar en su estado actual.
422invoice_rejectedEl programa no ha podido emitir la factura: revisa el mensaje.
422whatsapp_window_closedHan pasado más de 24 h desde el último mensaje del cliente: por WhatsApp solo se le puede escribir con una plantilla aprobada (template_id).
409channel_unavailableEse canal no está configurado en el taller.
404conversation_not_foundNo existe esa conversación.
404sandbox_onlySolo disponible en el entorno de pruebas (https://demo.programa-taller.es).
403client_key_forbiddenEsta clave está limitada a un cliente del taller y no puede hacer esta operación: solo trabaja con los datos de su cliente.

Escrituras e idempotencia

Todo POST exige la cabecera Idempotency-Key (un UUID nuevo por operación). Repetir la misma clave con el mismo cuerpo en 24 h devuelve la respuesta guardada con Idempotent-Replay: true; con otro cuerpo responde 409 idempotency_conflict. Los cuerpos se validan contra su esquema y cualquier campo desconocido se rechaza: 422 validation_error con la lista fields (path y message).

Claves de cliente

Claves de cliente: el taller puede crear claves limitadas a un único cliente (por ejemplo, una flota o una empresa). Con ellas cada listado devuelve solo los datos de ese cliente, un id de otro cliente responde 404, las operaciones de todo el taller responden 403 client_key_forbidden y los webhooks solo reciben eventos de ese cliente. GET /ping indica en key.client a qué cliente está limitada.

invoices:write stock:write settings:write reports:read: permisos que una clave de cliente no puede tener.

Guías

Primeros pasos

El mismo recorrido que el tutorial «Empieza con la API paso a paso» de Desarrolladores, para quien programa. Crea una clave de pruebas (pt_test_…) con permisos de solo lectura, comprueba la conexión, lee datos reales y, cuando la integración esté lista, pasa a una clave live con los permisos justos. Los avisos automáticos (webhooks) y las escrituras requieren el plan Developer o superior.

1. Crea una clave en «Mis claves»

Elige el entorno de pruebas y solo permisos de lectura (por ejemplo budgets:read). La clave completa se muestra una sola vez: guárdala en un gestor de contraseñas y envíala en la cabecera Authorization: Bearer.

2. Comprueba la conexión

GET /api/v1/ping

Responde con el nombre de la clave, sus permisos y el entorno. Un 401 significa clave no válida; un 403, que falta un permiso.

3. Lee tus datos reales

GET /api/v1/budgets

?limit=3

Los listados se paginan por cursor: repite la llamada con cursor=<next_cursor> hasta que has_more sea false. Cada respuesta trae X-RateLimit-Remaining con las peticiones que quedan en la ventana.

4. Recibe avisos automáticos

POST /api/v1/webhooks

{ "url": "https://tu-sistema.com/avisos-del-taller", "events": ["lead.accepted", "vehicle.ready"] }

También se crean desde la pestaña Webhooks sin programar. Guarda el secreto de firma y verifica X-PT-Signature en cada entrega; responde 2xx en menos de 5 segundos.

5. Pasa a producción

Antes, si quieres, prueba escrituras, aceptaciones, facturas y mensajes sin riesgo en el entorno de pruebas (https://demo.programa-taller.es/desarrolladores-sandbox): datos ficticios y nada sale de verdad. Crea una clave live con los permisos mínimos y, si vas a escribir datos, añade Idempotency-Key a cada POST. Los límites y el plan de API del taller se ven en la pestaña Consumo; los planes Developer y Business se contratan desde el propio programa.

Guía: crear un presupuesto y proponer cita

Flujo típico de un CRM o una web de reservas. Todas las peticiones POST llevan Idempotency-Key (un UUID nuevo por operación; repítelo solo al reintentar la misma). Necesitas una clave live con clients:write, vehicles:write, budgets:write, appointments:read y appointments:write.

1. Cliente: crearlo o recuperar el existente

POST /api/v1/clients

{ "name": "Laura Gómez", "phone": "600111222", "email": "laura@ejemplo.com" }

Con ?on_conflict=return_existing, si el teléfono, email o NIF ya existen recibes ese cliente (200) en vez de 409.

2. Vehículo del cliente

POST /api/v1/vehicles

{ "client_id": 1204, "plate": "1234KLM", "brand": "Seat", "model": "León" }

Si la matrícula es de otro cliente: 409 vehicle_belongs_to_other_client (el taller decide).

3. Presupuesto con sus partidas

POST /api/v1/budgets

{ "client_id": 1204, "vehicle_id": 871, "lines": [{ "description": "Cambio de aceite y filtro", "quantity": 1, "unit_price": 65 }] }

La respuesta trae los totales calculados. El cliente acepta y firma desde el enlace que le envía el taller (la API nunca da ese enlace); si te lo confirma a ti por otra vía, acéptalo en su nombre con POST /budgets/{id}/accept (permiso budgets:accept).

4. Huecos libres

GET /api/v1/appointments/availability

?from=2026-10-14&to=2026-10-18&duration_minutes=60

5. Proponer la cita

POST /api/v1/appointments

{ "budget_id": 1234, "start": "2026-10-14T09:00:00+02:00", "duration_minutes": 60 }

En modo «proponer» queda status=proposed hasta que el taller la confirma (recibirás appointment.confirmed por webhook). Si el hueco se ha ocupado: 409 slot_unavailable con alternativas.

Guía: tu bot o IA en los canales del taller

Tu asistente contesta por WhatsApp, SMS, chat web o email en lugar del asistente del programa. Necesitas una clave live con communications:read, messages:send, settings:write y webhooks:manage (plan Business o Partner). Pruébalo antes en el entorno de pruebas con POST /sandbox/simulate/message.

1. Suscríbete a message.received

POST /api/v1/webhooks

{ "url": "https://tu-bot.com/taller", "events": ["message.received"] }

Cada mensaje del cliente llega firmado con la clave de la conversación (key), el cliente, el presupuesto activo, el texto y los adjuntos.

2. Activa el modo asistente externo

PUT /api/v1/settings/assistant

{ "external_channels": ["whatsapp", "chat"], "fallback_minutes": 10 }

En esos canales el asistente del programa deja de contestar. Si no respondes a tiempo, o tu clave o tu webhook dejan de funcionar, vuelve a contestar él a ese mensaje.

3. Lee el hilo si lo necesitas

GET /api/v1/conversations/{key}/messages

4. Responde

POST /api/v1/conversations/{key}/messages

{ "text": "Mañana a las 9:00 tenemos hueco. ¿Se la reservo?" }

Fuera de la ventana de 24 h de WhatsApp envía template_id (GET /messages/templates). Para cerrar la venta: POST /appointments, POST /budgets/{id}/accept o POST /budgets/{id}/invoice según tus permisos.

Guía: flotas y renting con una clave de cliente

Para que una flota, una empresa de renting o un cliente con muchos vehículos conecte sus sistemas con el taller sin ver los datos de los demás clientes, el taller le crea una clave limitada a su ficha de cliente («Limitar a un cliente» al crearla en Mis claves). Con ella cada listado devuelve solo sus datos, un id de otro cliente responde 404, las operaciones de todo el taller responden 403 client_key_forbidden y sus webhooks solo reciben sus eventos.

1. Comprueba a qué cliente está limitada

GET /api/v1/ping

key.client trae el id y el nombre del cliente; en una clave del taller es null.

2. Sus vehículos

GET /api/v1/vehicles

?limit=200

Solo salen los vehículos de su ficha. Para dar de alta uno nuevo, POST /vehicles con su propio client_id.

3. Pedir un presupuesto

POST /api/v1/budgets

{ "client_id": 1204, "vehicle_id": 871, "public_notes": "Revisión de los 60.000 km" }

client_id debe ser el de la clave (si no, 403 client_key_forbidden). El taller lo completa y lo envía; la flota lo acepta o rechaza con POST /budgets/{id}/accept o /reject (permiso budgets:accept).

4. Proponer cita y seguir el estado

POST /api/v1/appointments

{ "budget_id": 1234, "start": "2026-10-20T09:00:00+02:00", "duration_minutes": 60 }

Suscribe un webhook a lead.status_changed, vehicle.ready e invoice.issued para enterarte al momento; solo llegarán los de sus vehículos.

Entorno de pruebas

https://demo.programa-taller.es es una instancia con datos ficticios que se restauran cada 2 horas. Toda clave allí es de pruebas con todos los permisos: puede escribir, aceptar, facturar y «enviar», pero nada sale de verdad (los envíos se registran como simulados) y los webhooks se emiten igual para probar el ciclo completo. Tus claves, webhooks y registro de peticiones se conservan entre reinicios.

1. Pide tu clave

POST /api/v1/sandbox/keys

{ "email": "dev@ejemplo.com", "company": "Integraciones Ejemplo SL", "name": "Ana Pérez" }

También desde la página https://demo.programa-taller.es/desarrolladores-sandbox. La clave se muestra una sola vez y vale 30 días.

2. Simula un cliente que escribe

POST /api/v1/sandbox/simulate/message

{ "channel": "whatsapp", "from": "+34600111222", "text": "¿Tienen hueco mañana?" }

3. Cambia a producción

Cambia la URL base por la de la instancia del taller y usa una clave live creada en su sección Desarrolladores.

Permisos (scopes)

PermisoNombreCon efectosDescripción
clients:readLeer clientesNoConsultar fichas de clientes y sus datos de contacto.
clients:writeCrear y editar clientesSíDar de alta clientes nuevos y modificar los existentes.
vehicles:readLeer vehículosNoConsultar vehículos, matrículas y su historial.
vehicles:writeCrear y editar vehículosSíDar de alta vehículos y modificar sus datos.
budgets:readLeer presupuestosNoConsultar presupuestos, sus partidas y su estado.
budgets:writeCrear y editar presupuestosSíCrear presupuestos, añadir partidas y cambiar su estado.
budgets:acceptAceptar o rechazar en nombre del clienteSíAceptar un presupuesto en nombre del cliente con su evidencia (quién y por qué canal) o rechazarlo con motivo. Queda en el historial con el nombre de la clave.
invoices:readLeer facturasNoConsultar facturas emitidas, importes y cobros.
invoices:writeEmitir facturas y anotar cobrosSíEmitir la factura de un presupuesto (numeración, serie, Verifactu y contabilidad del programa) y registrar cobros.
appointments:readLeer citasNoConsultar la agenda de citas y los huecos disponibles.
appointments:writeReservar y cancelar citasSíCrear, mover y cancelar citas en la agenda.
communications:readLeer comunicacionesNoConsultar el registro de emails, SMS, WhatsApp y llamadas (incluye el contenido completo).
communications:sendEnviar email y SMSSíEnviar emails y SMS transaccionales desde la instancia; consume saldo.
messages:sendResponder conversacionesSíResponder como el taller en las conversaciones de WhatsApp, SMS, chat web y email (bot o IA externa); consume saldo.
catalog:readLeer catálogoNoConsultar servicios, tarifas y conceptos del tarifario.
stock:readLeer stockNoConsultar existencias y referencias de recambios.
stock:writeAjustar stockSíDar entradas y salidas de recambios.
webhooks:manageGestionar webhooksSíCrear, listar y borrar las suscripciones a eventos de esta clave.
settings:writeAjustes del asistente externoSíConsultar y cambiar en qué canales contesta un asistente externo en lugar del asistente del programa.
reports:readLeer informesNoConsultar cifras agregadas de facturación, actividad y rendimiento.

General

GET /api/v1/ping Cualquier clave

Probar la conexión

Devuelve el nombre de la clave, su entorno, sus permisos, el estado de sus límites y, si es una clave de cliente, el cliente al que está limitada (key.client; null en una clave del taller). Vale cualquier clave válida.

Cualquier clave válida, sin permiso concreto.

Clave de cliente: permitido (no lleva datos de otros clientes).

Ejemplo

curl "https://tu-taller.example/api/v1/ping" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "ok": true,
  "key": {
    "id": 7,
    "name": "CRM",
    "environment": "live",
    "scopes": [
      "clients:read"
    ],
    "expires_at": null,
    "client": {
      "id": 1204,
      "name": "Laura Gómez"
    }
  },
  "rate_limit": {
    "per_minute": {
      "limit": 60,
      "used": 1,
      "remaining": 59
    },
    "per_day": {
      "limit": 20000,
      "used": 1,
      "remaining": 19999
    }
  },
  "instance": "taller.ejemplo.com",
  "server_time": "2026-10-06T09:30:00.000Z",
  "version": "v1"
}

GET /api/v1/workshop Cualquier clave

Datos públicos del taller

Nombre, razón social, CIF, dirección, contacto, horario semanal de la agenda, modo de reserva de citas, festivos próximos y zona horaria.

Cualquier clave válida, sin permiso concreto.

Clave de cliente: permitido (no lleva datos de otros clientes).

Ejemplo

curl "https://tu-taller.example/api/v1/workshop" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "name": "Taller Ejemplo",
  "legal_name": "Taller Ejemplo S.L.",
  "tax_id": "B12345678",
  "address": "C/ Industria 4",
  "city": "Barcelona",
  "zip": "08020",
  "province": "Barcelona",
  "phone": "+34931234567",
  "whatsapp": "+34600111222",
  "email": "taller@ejemplo.com",
  "web": "https://www.ejemplo.com",
  "logo_url": null,
  "timezone": "Europe/Madrid",
  "currency": "EUR",
  "booking_mode": "propose",
  "schedule": {
    "mon": {
      "start": "08:00",
      "end": "19:00",
      "closed": false
    },
    "tue": {
      "start": "08:00",
      "end": "19:00",
      "closed": false
    },
    "wed": {
      "start": "08:00",
      "end": "19:00",
      "closed": false
    },
    "thu": {
      "start": "08:00",
      "end": "19:00",
      "closed": false
    },
    "fri": {
      "start": "08:00",
      "end": "19:00",
      "closed": false
    },
    "sat": {
      "start": "08:00",
      "end": "19:00",
      "closed": false
    },
    "sun": {
      "start": "08:00",
      "end": "19:00",
      "closed": false
    }
  },
  "lunch_break": {
    "start": "13:30",
    "end": "15:00"
  },
  "min_slot_minutes": 60,
  "upcoming_holidays": [
    {
      "date": "2026-10-12",
      "name": "Fiesta Nacional de España",
      "scope": "nacional"
    }
  ]
}

GET /api/v1/openapi.json Sin autenticación

Especificación OpenAPI 3.1

Fichero generado desde este mismo catálogo. Sin autenticación. Admite ?lang=es|en|ca|pt|fr|bg para los textos.

Sin autenticación.

Consulta (query)

NombreTipoObligatorioDescripción
langstring [es, en, ca, pt, fr, bg] · default esNoIdioma de las descripciones.

Ejemplo

curl "https://tu-taller.example/api/v1/openapi.json"

Respuesta

{}

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.

Clave de cliente: solo devuelve o crea datos de su cliente.

Consulta (query)

NombreTipoObligatorioDescripción
searchstringNoBusca en nombre, teléfono, email y NIF/CIF (mínimo 2 caracteres).
expandstring [vehicles]NoRelaciones opcionales a incluir.
limitinteger · default 50NoResultados por página (1–200).
cursorstringNoCursor 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.

Clave de cliente: no disponible (403 client_key_forbidden).

Consulta (query)

NombreTipoObligatorioDescripción
on_conflictstring [error, return_existing] · default errorNoerror (por defecto): 409 si ya existe. return_existing: 200 con el cliente existente.

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringSí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.

Clave de cliente: solo si el recurso es de su cliente; si no, 404.

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del cliente.

Consulta (query)

NombreTipoObligatorioDescripción
expandstring [vehicles]NoRelaciones 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.

Clave de cliente: solo si el recurso es de su cliente; si no, 404.

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del cliente.

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringNoClave ú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.

Clave de cliente: solo devuelve o crea datos de su cliente.

Consulta (query)

NombreTipoObligatorioDescripción
platestringNoMatrícula exacta (se ignoran espacios y guiones).
client_idintegerNoFiltrar por cliente.
statusstring [activo, baja_temporal, baja]NoEstado del vehículo.
limitinteger · default 50NoResultados por página (1–200).
cursorstringNoCursor 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.

Clave de cliente: solo devuelve o crea datos de su cliente.

Consulta (query)

NombreTipoObligatorioDescripción
on_conflictstring [error, return_existing] · default errorNoerror (por defecto) o return_existing.

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringSí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.

Clave de cliente: solo si el recurso es de su cliente; si no, 404.

Parámetros

NombreTipoObligatorioDescripción
idintegerSí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.

Clave de cliente: solo si el recurso es de su cliente; si no, 404.

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del vehículo.

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringNoClave ú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"
}

GET /api/v1/vehicles/{id}/history vehicles:read

Historial del vehículo

Visitas al taller (todos sus presupuestos salvo los de uso interno, del más reciente al más antiguo) con estado, entrada y salida, kilometraje, importe, nº de factura y conceptos de las partidas; más las intervenciones anotadas en otros talleres. Nunca incluye costes.

Permiso necesario: vehicles:read.

Clave de cliente: solo si el recurso es de su cliente; si no, 404.

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del vehículo.

Consulta (query)

NombreTipoObligatorioDescripción
limitinteger · default 50NoMáximo de visitas (1–200).

Ejemplo

curl "https://tu-taller.example/api/v1/vehicles/1234/history" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "vehicle": {
    "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"
  },
  "visits": [
    {
      "budget_id": 1234,
      "status": "Facturado",
      "created_at": "2026-10-06T09:30:00.000Z",
      "checked_in_at": "2026-09-02T07:30:00.000Z",
      "delivered_at": "2026-09-03T16:00:00.000Z",
      "km": 84210,
      "amount": 245.3,
      "invoice": "F26/118",
      "category": "Mantenimiento",
      "works": [
        "Cambio de aceite y filtro"
      ]
    }
  ],
  "external_interventions": [
    {
      "title": "Cambio de neumáticos",
      "date": "2025-11-20",
      "km": 78000,
      "workshop_name": null,
      "warranty_until": null
    }
  ]
}

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.

Clave de cliente: solo devuelve o crea datos de su cliente.

Consulta (query)

NombreTipoObligatorioDescripción
statusstringNoEstado exacto (Pendiente, Enviado, Aprobado, Finalizado, Facturado, Rechazado…).
client_idintegerNoFiltrar por cliente.
vehicle_idintegerNoFiltrar por vehículo.
fromstring (date-time)NoCreados a partir de esta fecha.
tostring (date-time)NoCreados hasta esta fecha.
updated_sincestring (date-time)NoSolo registros modificados a partir de esta fecha (ISO 8601).
limitinteger · default 50NoResultados por página (1–200).
cursorstringNoCursor 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,
      "estimated_ready_at": null,
      "delivered_at": null,
      "reject_reason": null
    }
  ],
  "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.

Clave de cliente: solo devuelve o crea datos de su cliente.

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringSí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,
  "estimated_ready_at": null,
  "delivered_at": null,
  "reject_reason": null,
  "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.

Clave de cliente: solo si el recurso es de su cliente; si no, 404.

Parámetros

NombreTipoObligatorioDescripción
idintegerSí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,
  "estimated_ready_at": null,
  "delivered_at": null,
  "reject_reason": null,
  "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}/activity budgets:read

Historial del presupuesto

Lo que ha pasado en el presupuesto, de más reciente a más antiguo: cambios de estado, envíos, aceptación (con su origen), citas, mensajes del cliente, facturación… El mismo registro que ve el taller en la ficha; public=true marca lo que también ve el cliente. Los códigos de un solo uso y enlaces de acceso salen ocultos.

Permiso necesario: budgets:read.

Clave de cliente: solo si el recurso es de su cliente; si no, 404.

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del presupuesto.

Consulta (query)

NombreTipoObligatorioDescripción
visibilitystring [all, public] · default allNoall: todo el registro; public: solo lo que ve el cliente.
limitinteger · default 50NoResultados por página (1–200).
cursorstringNoCursor opaco devuelto en next_cursor de la página anterior.

Ejemplo

curl "https://tu-taller.example/api/v1/budgets/1234/activity" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "data": [
    {
      "id": 88123,
      "at": "2026-10-06T09:30:00.000Z",
      "event": "web_accepted",
      "detail": "Presupuesto aceptado por el cliente desde la web de seguimiento",
      "source": "client",
      "public": true,
      "user": {
        "id": 3,
        "name": "Marta"
      }
    }
  ],
  "next_cursor": "aWQ6MTIzNA",
  "has_more": true
}

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.

Clave de cliente: no disponible (403 client_key_forbidden).

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del presupuesto.

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringSí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,
    "estimated_ready_at": null,
    "delivered_at": null,
    "reject_reason": null,
    "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.

Clave de cliente: no disponible (403 client_key_forbidden).

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del presupuesto.
lineIdintegerSíId de la partida.

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringNoClave ú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,
    "estimated_ready_at": null,
    "delivered_at": null,
    "reject_reason": null,
    "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.

Clave de cliente: no disponible (403 client_key_forbidden).

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del presupuesto.
lineIdintegerSí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,
  "estimated_ready_at": null,
  "delivered_at": null,
  "reject_reason": null,
  "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: la aceptación la firma el cliente desde el enlace que le envía el taller, o la registra tu integración en su nombre con POST /budgets/{id}/accept (permiso budgets:accept). Rechazar va por POST /budgets/{id}/reject y facturar por POST /budgets/{id}/invoice (permiso invoices:write); aquí responden 403 status_transition_forbidden, igual que desistir; 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.

Clave de cliente: no disponible (403 client_key_forbidden).

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del presupuesto.

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringSí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,
  "estimated_ready_at": null,
  "delivered_at": null,
  "reject_reason": null,
  "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}/accept budgets:accept

Aceptar en nombre del cliente

Para cuando el cliente acepta por otra vía (teléfono, en persona, tu web…). Obligatoria la evidencia: accepted_by y channel (phone, in_person, email, whatsapp, other); note es opcional. Pasa por la misma regla de aceptación del programa: estado Aprobado, transición, datos fiscales mínimos del cliente (si faltan, 422 fiscal_data_incomplete con missing), descuento de campaña, aviso interno al taller, confirmación al cliente según los avisos configurados y webhooks lead.accepted y budget.accepted. Solo desde «Enviado» (si ya está aceptado responde 200 con already=true); Cancelado, Rechazado, Desistido, Finalizado o Facturado responden 409 budget_locked. Si el taller exige firma, la API acepta igualmente y lo deja escrito: «sin firma, aceptado por integración» (signature=waived_by_integration). En el historial queda «Aceptado por API en nombre del cliente · integración <clave> · <accepted_by> por <canal>».

Permiso necesario: budgets:accept.

Clave de cliente: solo si el recurso es de su cliente; si no, 404.

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del presupuesto.

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringSí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

{
  "accepted_by": "Laura Gómez",
  "channel": "phone",
  "note": "Confirmado por teléfono a las 10:12"
}

Ejemplo

curl -X POST "https://tu-taller.example/api/v1/budgets/1234/accept" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"accepted_by":"Laura Gómez","channel":"phone","note":"Confirmado por teléfono a las 10:12"}'

Respuesta

{
  "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": 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,
    "estimated_ready_at": null,
    "delivered_at": null,
    "reject_reason": null,
    "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
      }
    ]
  },
  "already": false,
  "signature": "not_required"
}

POST /api/v1/budgets/{id}/reject budgets:accept

Rechazar en nombre del cliente

Con motivo obligatorio. Misma regla que el rechazo del cliente: desde Pendiente, En cotización, Enviado o Aprobado (si el vehículo aún no ha entrado). Emite lead.rejected y budget.rejected y queda en el historial con el nombre de la clave.

Permiso necesario: budgets:accept.

Clave de cliente: solo si el recurso es de su cliente; si no, 404.

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del presupuesto.

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringSí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

{
  "reason": "Lo hará en otro taller",
  "rejected_by": "Laura Gómez",
  "channel": "phone"
}

Ejemplo

curl -X POST "https://tu-taller.example/api/v1/budgets/1234/reject" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"reason":"Lo hará en otro taller","rejected_by":"Laura Gómez","channel":"phone"}'

Respuesta

{
  "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": 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,
    "estimated_ready_at": null,
    "delivered_at": null,
    "reject_reason": null,
    "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
      }
    ]
  },
  "already": false,
  "signature": "not_required"
}

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.

Clave de cliente: solo si el recurso es de su cliente; si no, 404.

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del presupuesto.

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringSí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)

NombreTipoObligatorioDescripción
filestring (binary)SíFichero
client_visiblestring [true, false]No
mechanic_visiblestring [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"
}

Facturas

POST /api/v1/budgets/{id}/invoice invoices:write

Facturar un presupuesto

Usa la misma facturación del programa: copia las partidas y el impuesto del presupuesto, crea el asiento contable y, con issue=true (por defecto), emite la factura con la serie que toque, número correlativo, hash encadenado y QR Verifactu. El presupuesto pasa a Facturado. Se factura uno Aprobado, En curso, En espera o Finalizado con importe; si ya tiene factura se devuelve esa (created=false), nunca una segunda. Emite invoice.created e invoice.issued. Las facturas rectificativas no están disponibles por API: se hacen desde el programa.

Permiso necesario: invoices:write.

Clave de cliente: no disponible (403 client_key_forbidden).

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del presupuesto.

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringSí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

{
  "issue": true,
  "pay_method": "TARJETA"
}

Ejemplo

curl -X POST "https://tu-taller.example/api/v1/budgets/1234/invoice" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"issue":true,"pay_method":"TARJETA"}'

Respuesta

{
  "invoice": {
    "id": 412,
    "number": 87,
    "series": "F26",
    "full_number": "F2687",
    "kind": "invoice",
    "rectifies_number": null,
    "status": "issued",
    "date": "2026-10-06",
    "issued_at": "2026-10-06T09:30:00.000Z",
    "client_id": 1204,
    "client": {
      "id": 1204,
      "name": "Laura Gómez"
    },
    "budget_id": 1234,
    "vehicle_id": 871,
    "vehicle": {
      "id": 871,
      "plate": "1234 KLM",
      "brand": "Seat",
      "model": "León"
    },
    "plate": "1234 KLM",
    "km": 84500,
    "total": 78.65,
    "currency": "EUR",
    "payments": {
      "paid": 78.65,
      "pending": 0,
      "settled": true
    },
    "payment_method": "Tarjeta",
    "rebu": false,
    "verifactu_hash": "3f9a…",
    "registered_externally": false,
    "billing": {
      "name": "Laura Gómez",
      "tax_id": "12345678Z",
      "address": "C/ Mayor 12",
      "city": "Barcelona",
      "zip": "08001",
      "province": "Barcelona"
    },
    "date_in": null,
    "date_out": null,
    "public_notes": null,
    "totals": {
      "base": 65,
      "tax": 13.65,
      "total": 78.65,
      "currency": "EUR",
      "tax_rates": [
        {
          "rate": 21,
          "base": 65,
          "quota": 13.65
        }
      ]
    },
    "lines": [
      {
        "id": 9001,
        "description": "Cambio de aceite y filtro",
        "reference": null,
        "group_title": null,
        "quantity": 1,
        "unit_price": 65,
        "discount_pct": 0,
        "tax_rate": 21,
        "tax_exempt_code": null,
        "base": 65,
        "currency": "EUR",
        "sort_order": 0
      }
    ],
    "payment_list": [
      {
        "id": 77,
        "date": "2026-10-06T09:30:00.000Z",
        "amount": 78.65,
        "currency": "EUR",
        "method": "Tarjeta"
      }
    ]
  },
  "created": true
}

GET /api/v1/invoices invoices:read

Listar facturas

Facturas emitidas y borradores, ordenadas por id. Incluye el resumen de cobros.

Permiso necesario: invoices:read.

Clave de cliente: solo devuelve o crea datos de su cliente.

Consulta (query)

NombreTipoObligatorioDescripción
fromstring (date-time)NoFecha de factura desde.
tostring (date-time)NoFecha de factura hasta.
client_idintegerNoFiltrar por cliente.
statusstring [draft, issued, cancelled]NoEstado de la factura.
seriesstringNoSerie exacta.
limitinteger · default 50NoResultados por página (1–200).
cursorstringNoCursor opaco devuelto en next_cursor de la página anterior.

Ejemplo

curl "https://tu-taller.example/api/v1/invoices" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "data": [
    {
      "id": 412,
      "number": 87,
      "series": "F26",
      "full_number": "F2687",
      "kind": "invoice",
      "rectifies_number": null,
      "status": "issued",
      "date": "2026-10-06",
      "issued_at": "2026-10-06T09:30:00.000Z",
      "client_id": 1204,
      "client": {
        "id": 1204,
        "name": "Laura Gómez"
      },
      "budget_id": 1234,
      "vehicle_id": 871,
      "vehicle": {
        "id": 871,
        "plate": "1234 KLM",
        "brand": "Seat",
        "model": "León"
      },
      "plate": "1234 KLM",
      "km": 84500,
      "total": 78.65,
      "currency": "EUR",
      "payments": {
        "paid": 78.65,
        "pending": 0,
        "settled": true
      },
      "payment_method": "Tarjeta",
      "rebu": false,
      "verifactu_hash": "3f9a…",
      "registered_externally": false
    }
  ],
  "next_cursor": "aWQ6MTIzNA",
  "has_more": true
}

GET /api/v1/invoices/{id} invoices:read

Detalle de una factura

Permiso necesario: invoices:read.

Clave de cliente: solo si el recurso es de su cliente; si no, 404.

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId de la factura.

Ejemplo

curl "https://tu-taller.example/api/v1/invoices/1234" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "id": 412,
  "number": 87,
  "series": "F26",
  "full_number": "F2687",
  "kind": "invoice",
  "rectifies_number": null,
  "status": "issued",
  "date": "2026-10-06",
  "issued_at": "2026-10-06T09:30:00.000Z",
  "client_id": 1204,
  "client": {
    "id": 1204,
    "name": "Laura Gómez"
  },
  "budget_id": 1234,
  "vehicle_id": 871,
  "vehicle": {
    "id": 871,
    "plate": "1234 KLM",
    "brand": "Seat",
    "model": "León"
  },
  "plate": "1234 KLM",
  "km": 84500,
  "total": 78.65,
  "currency": "EUR",
  "payments": {
    "paid": 78.65,
    "pending": 0,
    "settled": true
  },
  "payment_method": "Tarjeta",
  "rebu": false,
  "verifactu_hash": "3f9a…",
  "registered_externally": false,
  "billing": {
    "name": "Laura Gómez",
    "tax_id": "12345678Z",
    "address": "C/ Mayor 12",
    "city": "Barcelona",
    "zip": "08001",
    "province": "Barcelona"
  },
  "date_in": null,
  "date_out": null,
  "public_notes": null,
  "totals": {
    "base": 65,
    "tax": 13.65,
    "total": 78.65,
    "currency": "EUR",
    "tax_rates": [
      {
        "rate": 21,
        "base": 65,
        "quota": 13.65
      }
    ]
  },
  "lines": [
    {
      "id": 9001,
      "description": "Cambio de aceite y filtro",
      "reference": null,
      "group_title": null,
      "quantity": 1,
      "unit_price": 65,
      "discount_pct": 0,
      "tax_rate": 21,
      "tax_exempt_code": null,
      "base": 65,
      "currency": "EUR",
      "sort_order": 0
    }
  ],
  "payment_list": [
    {
      "id": 77,
      "date": "2026-10-06T09:30:00.000Z",
      "amount": 78.65,
      "currency": "EUR",
      "method": "Tarjeta"
    }
  ]
}

GET /api/v1/invoices/{id}/pdf invoices:read

PDF de una factura

El mismo PDF que genera el programa (con QR Verifactu si la factura está emitida). Respuesta application/pdf.

Permiso necesario: invoices:read.

Clave de cliente: solo si el recurso es de su cliente; si no, 404.

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId de la factura.

Ejemplo

curl "https://tu-taller.example/api/v1/invoices/1234/pdf" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

(application/pdf)

POST /api/v1/invoices/{id}/payments invoices:write

Anotar un cobro

Mismo registro de cobros que la ficha de la factura: la marca «pagada» se recalcula con todos los cobros (nunca se fuerza) y se emite payment.received. Solo sobre facturas emitidas. Devuelve la factura con su resumen de cobros.

Permiso necesario: invoices:write.

Clave de cliente: no disponible (403 client_key_forbidden).

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId de la factura.

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringSí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

{
  "amount": 245.3,
  "date": "2026-10-13",
  "pay_method": "TARJETA",
  "notes": "Cobrado en mostrador"
}

Ejemplo

curl -X POST "https://tu-taller.example/api/v1/invoices/1234/payments" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"amount":245.3,"date":"2026-10-13","pay_method":"TARJETA","notes":"Cobrado en mostrador"}'

Respuesta

{
  "id": 412,
  "number": 87,
  "series": "F26",
  "full_number": "F2687",
  "kind": "invoice",
  "rectifies_number": null,
  "status": "issued",
  "date": "2026-10-06",
  "issued_at": "2026-10-06T09:30:00.000Z",
  "client_id": 1204,
  "client": {
    "id": 1204,
    "name": "Laura Gómez"
  },
  "budget_id": 1234,
  "vehicle_id": 871,
  "vehicle": {
    "id": 871,
    "plate": "1234 KLM",
    "brand": "Seat",
    "model": "León"
  },
  "plate": "1234 KLM",
  "km": 84500,
  "total": 78.65,
  "currency": "EUR",
  "payments": {
    "paid": 78.65,
    "pending": 0,
    "settled": true
  },
  "payment_method": "Tarjeta",
  "rebu": false,
  "verifactu_hash": "3f9a…",
  "registered_externally": false,
  "billing": {
    "name": "Laura Gómez",
    "tax_id": "12345678Z",
    "address": "C/ Mayor 12",
    "city": "Barcelona",
    "zip": "08001",
    "province": "Barcelona"
  },
  "date_in": null,
  "date_out": null,
  "public_notes": null,
  "totals": {
    "base": 65,
    "tax": 13.65,
    "total": 78.65,
    "currency": "EUR",
    "tax_rates": [
      {
        "rate": 21,
        "base": 65,
        "quota": 13.65
      }
    ]
  },
  "lines": [
    {
      "id": 9001,
      "description": "Cambio de aceite y filtro",
      "reference": null,
      "group_title": null,
      "quantity": 1,
      "unit_price": 65,
      "discount_pct": 0,
      "tax_rate": 21,
      "tax_exempt_code": null,
      "base": 65,
      "currency": "EUR",
      "sort_order": 0
    }
  ],
  "payment_list": [
    {
      "id": 77,
      "date": "2026-10-06T09:30:00.000Z",
      "amount": 78.65,
      "currency": "EUR",
      "method": "Tarjeta"
    }
  ]
}

Citas

GET /api/v1/appointments appointments:read

Citas confirmadas y propuestas

Por defecto los próximos 30 días. status=confirmed son citas fijadas en la agenda; status=proposed son propuestas del cliente pendientes de que el taller confirme (bloquean el hueco).

Permiso necesario: appointments:read.

Clave de cliente: solo devuelve o crea datos de su cliente.

Consulta (query)

NombreTipoObligatorioDescripción
fromstring (date-time)NoInicio del rango (por defecto ahora).
tostring (date-time)NoFin del rango (por defecto +30 días, máximo 1 año).
statusstring [confirmed, proposed]NoSolo un tipo.
box_idintegerNoFiltrar por elevador/box.

Ejemplo

curl "https://tu-taller.example/api/v1/appointments" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "data": [
    {
      "id": "b1234",
      "budget_id": 1234,
      "status": "confirmed",
      "start": "2026-10-06T09:30:00.000Z",
      "end": "2026-10-06T10:30:00.000Z",
      "estimated_duration_minutes": 60,
      "client_confirmed_at": null,
      "budget_status": "Aprobado",
      "checked_in_at": null,
      "client": {
        "id": 1204,
        "name": "Laura Gómez"
      },
      "vehicle": {
        "id": 871,
        "plate": "1234 KLM",
        "brand": "Seat",
        "model": "León"
      },
      "mechanic": {
        "id": 3,
        "name": "Marta"
      },
      "box": {
        "id": 2,
        "name": "Elevador 2"
      },
      "category": {
        "id": 5,
        "name": "Mantenimiento"
      },
      "channel": null,
      "proposed_at": null
    }
  ],
  "from": "2026-10-06T00:00:00.000Z",
  "to": "2026-11-05T00:00:00.000Z"
}

POST /api/v1/appointments appointments:write

Proponer o reservar una cita

Sigue el modo de reserva del taller (booking_mode en GET /workshop). En «propose» se crea una propuesta (status=proposed) que bloquea el hueco hasta que el taller la confirma; pedir mode=book responde 403 booking_mode_propose_only. En «book» la cita queda en firme (status=confirmed), salvo que pidas mode=propose. El hueco se valida con la misma lógica que /appointments/availability; si no está libre, 409 slot_unavailable con hasta 3 alternativas en error.alternatives. No se avisa al cliente salvo notify_client=true con communications:send.

Permiso necesario: appointments:write.

Clave de cliente: solo devuelve o crea datos de su cliente.

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringSí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

{
  "budget_id": 1234,
  "start": "2026-10-14T09:00:00+02:00",
  "box_id": 2,
  "duration_minutes": 60,
  "mode": "propose",
  "notify_client": false
}

Ejemplo

curl -X POST "https://tu-taller.example/api/v1/appointments" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"budget_id":1234,"start":"2026-10-14T09:00:00+02:00","box_id":2,"duration_minutes":60,"mode":"propose","notify_client":false}'

Respuesta

{
  "id": "b1234",
  "budget_id": 1234,
  "status": "confirmed",
  "start": "2026-10-06T09:30:00.000Z",
  "end": "2026-10-06T10:30:00.000Z",
  "estimated_duration_minutes": 60,
  "client_confirmed_at": null,
  "budget_status": "Aprobado",
  "checked_in_at": null,
  "client": {
    "id": 1204,
    "name": "Laura Gómez"
  },
  "vehicle": {
    "id": 871,
    "plate": "1234 KLM",
    "brand": "Seat",
    "model": "León"
  },
  "mechanic": {
    "id": 3,
    "name": "Marta"
  },
  "box": {
    "id": 2,
    "name": "Elevador 2"
  },
  "category": {
    "id": 5,
    "name": "Mantenimiento"
  },
  "channel": null,
  "proposed_at": null
}

GET /api/v1/appointments/availability appointments:read

Huecos libres

Misma lógica que la agenda y la web de seguimiento: horario del taller y de cada box, comida, festivos nacionales, autonómicos y locales, citas abiertas y propuestas pendientes (que bloquean su hueco). Inicios cada 30 min y al menos 1 h desde ahora. Por defecto los próximos 7 días (máximo 31). Incluye booking_mode del taller.

Permiso necesario: appointments:read.

Clave de cliente: permitido (no lleva datos de otros clientes).

Consulta (query)

NombreTipoObligatorioDescripción
fromstring (date-time)NoDesde (por defecto ahora).
tostring (date-time)NoHasta (por defecto +7 días; máximo 31 días).
duration_minutesintegerNoDuración de la cita (15–720). Por defecto la mínima de la agenda.
box_idintegerNoSolo ese box.
limitinteger · default 100NoMáximo de huecos (1–500).

Ejemplo

curl "https://tu-taller.example/api/v1/appointments/availability" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "data": [
    {
      "start": "2026-10-14T07:00:00.000Z",
      "end": "2026-10-14T08:00:00.000Z",
      "box": {
        "id": 2,
        "name": "Elevador 2"
      }
    }
  ],
  "duration_minutes": 60,
  "from": "2026-10-14T00:00:00.000Z",
  "to": "2026-10-21T00:00:00.000Z",
  "booking_mode": "propose"
}

DELETE /api/v1/appointments/{id} appointments:write

Anular una cita o retirar una propuesta

b<presupuesto>: anula la cita confirmada (igual que «Cancelar cita» en la ficha). p<propuesta>: retira la propuesta pendiente; con notify_client=true y communications:send se invita al cliente a elegir otra hora. Emite appointment.cancelled.

Permiso necesario: appointments:write.

Clave de cliente: solo si el recurso es de su cliente; si no, 404.

Parámetros

NombreTipoObligatorioDescripción
idstringSíId de la cita (b1234 o p88).

Consulta (query)

NombreTipoObligatorioDescripción
notify_clientboolean · default falseNoSolo propuestas: avisar al cliente.

Ejemplo

curl -X DELETE "https://tu-taller.example/api/v1/appointments/b1234" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "ok": true,
  "id": "b1234",
  "status": "cancelled"
}

GET /api/v1/appointments/{id} appointments:read

Detalle de una cita

Permiso necesario: appointments:read.

Clave de cliente: solo si el recurso es de su cliente; si no, 404.

Parámetros

NombreTipoObligatorioDescripción
idstringSíId de la cita (b1234 o p88).

Ejemplo

curl "https://tu-taller.example/api/v1/appointments/b1234" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "id": "b1234",
  "budget_id": 1234,
  "status": "confirmed",
  "start": "2026-10-06T09:30:00.000Z",
  "end": "2026-10-06T10:30:00.000Z",
  "estimated_duration_minutes": 60,
  "client_confirmed_at": null,
  "budget_status": "Aprobado",
  "checked_in_at": null,
  "client": {
    "id": 1204,
    "name": "Laura Gómez"
  },
  "vehicle": {
    "id": 871,
    "plate": "1234 KLM",
    "brand": "Seat",
    "model": "León"
  },
  "mechanic": {
    "id": 3,
    "name": "Marta"
  },
  "box": {
    "id": 2,
    "name": "Elevador 2"
  },
  "category": {
    "id": 5,
    "name": "Mantenimiento"
  },
  "channel": null,
  "proposed_at": null
}

PATCH /api/v1/appointments/{id} appointments:write

Mover una cita o cambiar una propuesta

b<presupuesto>: mueve la cita en firme a la nueva hora (solo si el taller trabaja en modo «reservar»; en modo «proponer» responde 403 booking_mode_propose_only: crea una propuesta con POST /appointments). p<propuesta>: retira la propuesta y crea otra a la nueva hora (cambia el id). El hueco se valida igual que al crear (409 slot_unavailable con alternativas). Emite appointment.updated.

Permiso necesario: appointments:write.

Clave de cliente: solo si el recurso es de su cliente; si no, 404.

Parámetros

NombreTipoObligatorioDescripción
idstringSíId de la cita (b1234 o p88).

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringNoClave ú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

{
  "start": "2026-10-15T10:00:00+02:00",
  "box_id": 2,
  "duration_minutes": 60,
  "notify_client": false
}

Ejemplo

curl -X PATCH "https://tu-taller.example/api/v1/appointments/b1234" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"start":"2026-10-15T10:00:00+02:00","box_id":2,"duration_minutes":60,"notify_client":false}'

Respuesta

{
  "id": "b1234",
  "budget_id": 1234,
  "status": "confirmed",
  "start": "2026-10-06T09:30:00.000Z",
  "end": "2026-10-06T10:30:00.000Z",
  "estimated_duration_minutes": 60,
  "client_confirmed_at": null,
  "budget_status": "Aprobado",
  "checked_in_at": null,
  "client": {
    "id": 1204,
    "name": "Laura Gómez"
  },
  "vehicle": {
    "id": 871,
    "plate": "1234 KLM",
    "brand": "Seat",
    "model": "León"
  },
  "mechanic": {
    "id": 3,
    "name": "Marta"
  },
  "box": {
    "id": 2,
    "name": "Elevador 2"
  },
  "category": {
    "id": 5,
    "name": "Mantenimiento"
  },
  "channel": null,
  "proposed_at": null
}

Recambios

GET /api/v1/stock stock:read

Listar recambios del stock

Existencias con unidad y decimales (quantity, reserved, pending, planned, available, min_stock), referencias y precio de venta. Disponible = cantidad − bloqueado por presupuestos aceptados o en curso; lo previsto en presupuestos abiertos no bloquea. El coste de compra, el proveedor y el historial de compras nunca salen por la API. Por defecto solo piezas activas.

Permiso necesario: stock:read.

Clave de cliente: permitido (no lleva datos de otros clientes).

Consulta (query)

NombreTipoObligatorioDescripción
searchstringNoBusca en nombre, marca, referencias y EAN (las referencias sin tener en cuenta espacios, puntos ni guiones).
referencestringNoReferencia o EAN exactos, sin tener en cuenta espacios, puntos ni guiones.
in_stockbooleanNotrue: solo con cantidad en el almacén; false: solo con cantidad cero (mira available para lo libre).
include_inactiveboolean · default falseNoIncluir piezas dadas de baja del inventario.
updated_sincestring (date-time)NoSolo registros modificados a partir de esta fecha (ISO 8601).
limitinteger · default 50NoResultados por página (1–200).
cursorstringNoCursor opaco devuelto en next_cursor de la página anterior.

Ejemplo

curl "https://tu-taller.example/api/v1/stock" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "data": [
    {
      "id": 88,
      "name": "Turbocompresor",
      "brand": "Garrett",
      "references": {
        "original": "A6540907801",
        "supplier": "MA654 090 78 01",
        "own": null
      },
      "ean": null,
      "unit": "ud",
      "quantity": 4.5,
      "reserved": 1,
      "pending": 0,
      "planned": 2,
      "available": 3.5,
      "min_stock": 2,
      "location": "Estantería B3",
      "status": "nuevo",
      "sale_price": 2190,
      "currency": "EUR",
      "active": true,
      "updated_at": "2026-10-06T09:30:00.000Z"
    }
  ],
  "next_cursor": "aWQ6MTIzNA",
  "has_more": true
}

GET /api/v1/stock/{id} stock:read

Detalle de un recambio

Permiso necesario: stock:read.

Clave de cliente: permitido (no lleva datos de otros clientes).

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del recambio.

Ejemplo

curl "https://tu-taller.example/api/v1/stock/1234" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "id": 88,
  "name": "Turbocompresor",
  "brand": "Garrett",
  "references": {
    "original": "A6540907801",
    "supplier": "MA654 090 78 01",
    "own": null
  },
  "ean": null,
  "unit": "ud",
  "quantity": 4.5,
  "reserved": 1,
  "pending": 0,
  "planned": 2,
  "available": 3.5,
  "min_stock": 2,
  "location": "Estantería B3",
  "status": "nuevo",
  "sale_price": 2190,
  "currency": "EUR",
  "active": true,
  "updated_at": "2026-10-06T09:30:00.000Z"
}

Comunicaciones

GET /api/v1/communications communications:read

Registro de comunicaciones

Últimas comunicaciones (email, SMS, WhatsApp, llamadas, push) con su contenido completo, de más reciente a más antigua.

Permiso necesario: communications:read.

Clave de cliente: solo devuelve o crea datos de su cliente.

Consulta (query)

NombreTipoObligatorioDescripción
channelstring [all, email, sms, whatsapp, call, push] · default allNoCanal.
limitinteger · default 50NoMáximo de resultados (1–200).

Ejemplo

curl "https://tu-taller.example/api/v1/communications" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "data": [
    {
      "id": "c_8812",
      "channel": "email",
      "direction": "out",
      "recipient": "laura@ejemplo.com",
      "subject": "Su presupuesto",
      "body": "Hola Laura, …",
      "idlead": 1234,
      "idclient": 1204,
      "clientName": "Laura Gómez",
      "status": "sent",
      "created_at": "2026-10-06T09:30:00.000Z",
      "duration": null,
      "recordingUrl": null,
      "agent": null,
      "fromNumber": null,
      "toNumber": null
    }
  ],
  "next_cursor": null,
  "has_more": false
}

GET /api/v1/logs communications:read

Registro de comunicaciones (alias antiguo)

Misma consulta que /communications pero devuelve { items }. Se mantiene por compatibilidad; usa /communications.

Permiso necesario: communications:read.

Clave de cliente: solo devuelve o crea datos de su cliente.

Consulta (query)

NombreTipoObligatorioDescripción
channelstring [all, email, sms, whatsapp, call, push] · default allNoCanal.
limitinteger · default 50NoMáximo de resultados (1–200).

Ejemplo

curl "https://tu-taller.example/api/v1/logs" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "items": [
    {
      "id": "c_8812",
      "channel": "email",
      "direction": "out",
      "recipient": "laura@ejemplo.com",
      "subject": "Su presupuesto",
      "body": "Hola Laura, …",
      "idlead": 1234,
      "idclient": 1204,
      "clientName": "Laura Gómez",
      "status": "sent",
      "created_at": "2026-10-06T09:30:00.000Z",
      "duration": null,
      "recordingUrl": null,
      "agent": null,
      "fromNumber": null,
      "toNumber": null
    }
  ]
}

POST /api/v1/email communications:send

Enviar un email transaccional

Solo texto: sale con la plantilla, el nombre y la cuenta de correo del taller, y queda en el registro de Comunicación. No admite HTML ni remitente propio, y replyTo solo puede ser una dirección del dominio del taller. Si rebota, el email del cliente se marca como no válido.

Permiso necesario: communications:send.

Clave de cliente: no disponible (403 client_key_forbidden).

Cuerpo

{
  "to": "cliente@ejemplo.com",
  "subject": "Su vehículo está listo",
  "text": "Puede pasar a recogerlo.",
  "replyTo": "taller@ejemplo.com"
}

Ejemplo

curl -X POST "https://tu-taller.example/api/v1/email" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"to":"cliente@ejemplo.com","subject":"Su vehículo está listo","text":"Puede pasar a recogerlo.","replyTo":"taller@ejemplo.com"}'

Respuesta

{
  "ok": true
}

POST /api/v1/sms communications:send

Enviar un SMS transaccional

Sale con el servicio de SMS configurado en el taller y queda en el registro de Comunicación, donde se actualiza su estado de entrega.

Permiso necesario: communications:send.

Clave de cliente: no disponible (403 client_key_forbidden).

Cuerpo

{
  "to": "+34600111222",
  "body": "Su vehículo está listo para recoger."
}

Ejemplo

curl -X POST "https://tu-taller.example/api/v1/sms" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"to":"+34600111222","body":"Su vehículo está listo para recoger."}'

Respuesta

{
  "ok": true
}

Conversaciones

GET /api/v1/conversations communications:read

Conversaciones de la Bandeja

WhatsApp, SMS, chat de la web y email en una lista, como la Bandeja del programa, de la más reciente a la más antigua. Cada una con su clave estable (canal:número o canal:id), cliente, presupuesto activo, estado (open: falta contestar; pending: esperando al cliente; closed) y si la ventana de 24 h de WhatsApp sigue abierta.

Permiso necesario: communications:read.

Clave de cliente: solo devuelve o crea datos de su cliente.

Consulta (query)

NombreTipoObligatorioDescripción
channelstring [whatsapp, sms, chat, email]NoSolo un canal.
statusstring [open, pending, closed]NoSolo un estado.
daysinteger · default 30NoActividad de los últimos N días (1–365).
limitinteger · default 50NoMáximo de resultados (1–200).

Ejemplo

curl "https://tu-taller.example/api/v1/conversations" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "data": [
    {
      "key": "whatsapp:600111222",
      "channel": "whatsapp",
      "contact": "+34600111222",
      "client": {
        "id": 1204,
        "name": "Laura Gómez"
      },
      "budget_id": 1234,
      "status": "open",
      "unread": 1,
      "last_message_at": "2026-10-06T09:30:00.000Z",
      "last_inbound_at": "2026-10-06T09:29:00.000Z",
      "last_message": "¿A qué hora puedo pasar a por el coche?",
      "last_author": "cliente",
      "whatsapp_window_open": true,
      "external_bot": false
    }
  ]
}

GET /api/v1/conversations/{key}/messages communications:read

Mensajes de una conversación

El hilo completo en orden cronológico, con autor (cliente, ia, equipo, automatico o integracion), texto y adjuntos. Los emails se devuelven como texto, sin la plantilla ni el hilo citado.

Permiso necesario: communications:read.

Clave de cliente: solo si el recurso es de su cliente; si no, 404.

Parámetros

NombreTipoObligatorioDescripción
keystringSíClave de la conversación: whatsapp:<9 dígitos>, sms:<9 dígitos>, chat:<id> o email:<id>.

Ejemplo

curl "https://tu-taller.example/api/v1/conversations/whatsapp:600111222/messages" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "conversation": {
    "key": "whatsapp:600111222",
    "channel": "whatsapp",
    "contact": "+34600111222",
    "client": {
      "id": 1204,
      "name": "Laura Gómez"
    },
    "budget_id": 1234,
    "status": "open",
    "unread": 1,
    "last_message_at": "2026-10-06T09:30:00.000Z",
    "last_inbound_at": "2026-10-06T09:29:00.000Z",
    "last_message": "¿A qué hora puedo pasar a por el coche?",
    "last_author": "cliente",
    "whatsapp_window_open": true,
    "external_bot": false
  },
  "data": [
    {
      "id": 77120,
      "at": "2026-10-06T09:30:00.000Z",
      "direction": "in",
      "author": "cliente",
      "author_name": null,
      "subject": null,
      "text": "¿A qué hora puedo pasar a por el coche?",
      "attachments": [
        "https://…/whatsapp-in/SM123-0.jpg"
      ],
      "status": "received"
    }
  ]
}

POST /api/v1/conversations/{key}/messages messages:send

Responder en una conversación

Sale por el mismo canal y con las mismas funciones que la Bandeja del programa. WhatsApp: text y/o media_url dentro de la ventana de 24 h; fuera de ella solo con template_id (plantilla aprobada; si no, 422 whatsapp_window_closed). SMS: text (un fragmento). Chat web: text. Email: subject y text. El mensaje queda registrado con la integración como autor («API · <clave>»), marca la conversación como respondida y, si había un mensaje esperando al asistente externo, cancela la respuesta de respaldo del asistente del programa. En el entorno de pruebas nada sale de verdad (simulated=true).

Permiso necesario: messages:send.

Clave de cliente: solo si el recurso es de su cliente; si no, 404.

Parámetros

NombreTipoObligatorioDescripción
keystringSíClave de la conversación: whatsapp:<9 dígitos>, sms:<9 dígitos>, chat:<id> o email:<id>.

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringSí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

{
  "text": "Su coche estará listo a las 18:00.",
  "media_url": null,
  "template_id": 12,
  "template_variables": {
    "#nombre#": "Laura"
  },
  "subject": "Su presupuesto"
}

Ejemplo

curl -X POST "https://tu-taller.example/api/v1/conversations/whatsapp:600111222/messages" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"text":"Su coche estará listo a las 18:00.","media_url":null,"template_id":12,"template_variables":{"#nombre#":"Laura"},"subject":"Su presupuesto"}'

Respuesta

{
  "ok": true,
  "key": "whatsapp:600111222",
  "channel": "whatsapp",
  "mode": "free",
  "simulated": false
}

GET /api/v1/messages/templates communications:read

Plantillas de WhatsApp aprobadas

Las plantillas que Meta ha aprobado al taller: su id es el template_id para escribir fuera de la ventana de 24 h.

Permiso necesario: communications:read.

Clave de cliente: permitido (no lleva datos de otros clientes).

Ejemplo

curl "https://tu-taller.example/api/v1/messages/templates" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "data": [
    {
      "id": 12,
      "name": "presupuesto_listo",
      "language": "es",
      "category": "UTILITY",
      "body": "Hola #nombre#, su presupuesto está listo.",
      "variables": [
        "#nombre#"
      ]
    }
  ]
}

Ajustes

GET /api/v1/settings/assistant settings:write

Modo asistente externo

En qué canales contesta tu asistente en lugar del asistente del programa, la clave que los atiende, los minutos de respaldo y los canales que de verdad atiendes ahora (clave viva y webhook message.received activo).

Permiso necesario: settings:write.

Clave de cliente: no disponible (403 client_key_forbidden).

Ejemplo

curl "https://tu-taller.example/api/v1/settings/assistant" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "external_channels": [
    "whatsapp"
  ],
  "api_key_id": 7,
  "fallback_minutes": 10,
  "channels_owned_now": [
    "whatsapp"
  ]
}

PUT /api/v1/settings/assistant settings:write

Cambiar el modo asistente externo

En los canales de external_channels el asistente del programa deja de contestar: cada mensaje entra, se registra, se emite message.received y se espera tu respuesta (POST /conversations/{key}/messages). Salvaguarda: si no respondes en fallback_minutes (10 por defecto), si la clave se revoca o caduca, o si no hay webhook message.received activo o su entrega falla, contesta el asistente del programa a ese mensaje y queda registrado. La clave que hace la petición pasa a ser la que atiende. external_channels=[] lo devuelve todo al programa.

Permiso necesario: settings:write.

Clave de cliente: no disponible (403 client_key_forbidden).

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringNoClave ú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

{
  "external_channels": [
    "whatsapp"
  ],
  "fallback_minutes": 10
}

Ejemplo

curl -X PUT "https://tu-taller.example/api/v1/settings/assistant" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"external_channels":["whatsapp"],"fallback_minutes":10}'

Respuesta

{
  "external_channels": [
    "whatsapp"
  ],
  "api_key_id": 7,
  "fallback_minutes": 10,
  "channels_owned_now": [
    "whatsapp"
  ]
}

Entorno de pruebas

POST /api/v1/sandbox/keys Sin autenticación

Pedir una clave de pruebas

Solo en https://demo.programa-taller.es (en las demás instancias responde 404 sandbox_only). Sin autenticación ni coste: devuelve una clave pt_test_… con todos los permisos, válida 30 días, que solo se muestra una vez. Máximo 3 al día por IP y por email. Los datos de la demo son ficticios y se restauran cada 2 horas; las claves, los webhooks y el registro de peticiones se conservan. Nada sale de verdad: los envíos se registran como simulados y los webhooks se emiten igual.

Sin autenticación.

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringNoClave ú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

{
  "email": "dev@ejemplo.com",
  "company": "Integraciones Ejemplo SL",
  "name": "Ana Pérez"
}

Ejemplo

curl -X POST "https://tu-taller.example/api/v1/sandbox/keys" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"email":"dev@ejemplo.com","company":"Integraciones Ejemplo SL","name":"Ana Pérez"}'

Respuesta

{
  "key": "pt_test_0123456789abcdef0123456789abcdef",
  "expires_at": "2026-11-12T10:00:00.000Z",
  "base_url": "https://demo.programa-taller.es/api/v1",
  "docs_url": "https://demo.programa-taller.es/api/v1/openapi.json",
  "scopes": [
    "budgets:accept"
  ]
}

POST /api/v1/sandbox/simulate/message Cualquier clave

Simular un mensaje entrante

Solo en el entorno de pruebas. Crea un mensaje entrante ficticio por WhatsApp, SMS, chat web o email, como si lo hubiera escrito un cliente: entra en la Bandeja, se enlaza con el cliente si el teléfono o el email coinciden y dispara message.received para probar tu bot de principio a fin. Contesta con POST /conversations/{key}/messages (la respuesta se simula).

Cualquier clave válida, sin permiso concreto.

Clave de cliente: no disponible (403 client_key_forbidden).

Cabeceras

NombreTipoObligatorioDescripción
Idempotency-KeystringSí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

{
  "channel": "whatsapp",
  "from": "+34600111222",
  "text": "Hola, ¿tienen hueco mañana para cambiar el aceite?",
  "subject": "Consulta",
  "media_urls": []
}

Ejemplo

curl -X POST "https://tu-taller.example/api/v1/sandbox/simulate/message" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"channel":"whatsapp","from":"+34600111222","text":"Hola, ¿tienen hueco mañana para cambiar el aceite?","subject":"Consulta","media_urls":[]}'

Respuesta

{
  "ok": true,
  "key": "whatsapp:600111222"
}

Webhooks

GET /api/v1/webhooks webhooks:manage

Listar los webhooks de la clave

Incluye el catálogo de eventos disponibles. Nunca devuelve los secretos.

Permiso necesario: webhooks:manage.

Clave de cliente: permitido (no lleva datos de otros clientes).

Ejemplo

curl "https://tu-taller.example/api/v1/webhooks" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "items": [
    {
      "id": 3,
      "url": "https://tu-sistema.com/webhooks/taller",
      "events": [
        "lead.accepted"
      ],
      "description": "CRM",
      "active": true,
      "created_at": "2026-10-06T09:30:00.000Z"
    }
  ],
  "events": [
    {
      "event": "lead.accepted",
      "label": "Presupuesto aceptado",
      "description": "texto"
    }
  ]
}

POST /api/v1/webhooks webhooks:manage

Crear un webhook

El secreto de firma (whsec_…) solo viaja en esta respuesta. Con una clave de cliente solo se entregan los eventos de ese cliente (los que no pertenecen a ningún cliente, como campaign.finished, no llegan).

Permiso necesario: webhooks:manage.

Clave de cliente: permitido (no lleva datos de otros clientes).

Cuerpo

{
  "url": "https://tu-sistema.com/webhooks/taller",
  "events": [
    "lead.accepted"
  ],
  "description": "CRM"
}

Ejemplo

curl -X POST "https://tu-taller.example/api/v1/webhooks" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://tu-sistema.com/webhooks/taller","events":["lead.accepted"],"description":"CRM"}'

Respuesta

{
  "item": {
    "id": 3,
    "url": "https://tu-sistema.com/webhooks/taller",
    "events": [
      "lead.accepted"
    ],
    "description": "CRM",
    "active": true,
    "created_at": "2026-10-06T09:30:00.000Z"
  },
  "secret": "whsec_…"
}

GET /api/v1/webhooks/{id} webhooks:manage

Detalle de un webhook y sus últimas entregas

Permiso necesario: webhooks:manage.

Clave de cliente: permitido (no lleva datos de otros clientes).

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del webhook.

Ejemplo

curl "https://tu-taller.example/api/v1/webhooks/1234" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "item": {
    "id": 3,
    "url": "https://tu-sistema.com/webhooks/taller",
    "events": [
      "lead.accepted"
    ],
    "description": "CRM",
    "active": true,
    "created_at": "2026-10-06T09:30:00.000Z"
  },
  "deliveries": [
    {}
  ]
}

PATCH /api/v1/webhooks/{id} webhooks:manage

Modificar un webhook

Permiso necesario: webhooks:manage.

Clave de cliente: permitido (no lleva datos de otros clientes).

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del webhook.

Cuerpo

{
  "url": "texto",
  "events": [
    "texto"
  ],
  "description": "texto",
  "active": false
}

Ejemplo

curl -X PATCH "https://tu-taller.example/api/v1/webhooks/1234" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"url":"texto","events":["texto"],"description":"texto","active":false}'

Respuesta

{
  "item": {
    "id": 3,
    "url": "https://tu-sistema.com/webhooks/taller",
    "events": [
      "lead.accepted"
    ],
    "description": "CRM",
    "active": true,
    "created_at": "2026-10-06T09:30:00.000Z"
  }
}

DELETE /api/v1/webhooks/{id} webhooks:manage

Borrar un webhook

Permiso necesario: webhooks:manage.

Clave de cliente: permitido (no lleva datos de otros clientes).

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del webhook.

Ejemplo

curl -X DELETE "https://tu-taller.example/api/v1/webhooks/1234" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "ok": true
}

POST /api/v1/webhooks/{id}/test webhooks:manage

Enviar una entrega de prueba (test.ping) al webhook

Permiso necesario: webhooks:manage.

Clave de cliente: permitido (no lleva datos de otros clientes).

Parámetros

NombreTipoObligatorioDescripción
idintegerSíId del webhook.

Ejemplo

curl -X POST "https://tu-taller.example/api/v1/webhooks/1234/test" \
  -H "Authorization: Bearer pt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Respuesta

{
  "ok": true,
  "status": 200
}

Webhooks

Carga

{
  "event": "lead.accepted",
  "timestamp": "2026-10-06T10:15:00.000Z",
  "data": {
    "leadId": 1234,
    "from": "Enviado",
    "to": "Aprobado"
  }
}
EventoDescripción
lead.createdPresupuesto creado
Se ha creado un presupuesto nuevo (desde el programa, la web, el email o el asistente).
lead.sentPresupuesto enviado
El presupuesto se ha enviado al cliente.
lead.acceptedPresupuesto aceptado
El cliente o el taller han aprobado el presupuesto.
lead.rejectedPresupuesto rechazado
El presupuesto se ha rechazado, con su motivo si lo hay.
lead.status_changedCambio de estado
Cualquier cambio de estado del presupuesto (incluye los anteriores).
budget.acceptedPresupuesto aceptado (budget)
Igual que lead.accepted, con el nombre nuevo: el presupuesto ha pasado a Aprobado (cliente, taller o integración con budgets:accept).
budget.rejectedPresupuesto rechazado (budget)
Igual que lead.rejected, con el nombre nuevo: el presupuesto ha pasado a Rechazado, con su motivo.
appointment.proposedCita propuesta
Un cliente propone una cita pendiente de confirmar por el taller.
appointment.confirmedCita confirmada
Una cita queda confirmada en la agenda.
appointment.cancelledCita cancelada
Se ha anulado la cita de un presupuesto.
appointment.createdCita creada
Un presupuesto que no tenía cita queda citado en la agenda (programa, web de seguimiento, asistente o API).
appointment.updatedCita cambiada
Se ha movido la fecha, la hora o el box de una cita existente, o se ha cambiado una propuesta por API.
vehicle.createdVehículo creado
Se ha dado de alta un vehículo.
vehicle.updatedVehículo actualizado
Se han modificado los datos o el estado de un vehículo.
vehicle.checked_inVehículo recibido
El vehículo ha entrado en el taller (recepción o sin cita).
vehicle.arrivingCliente de camino
El cliente avisa desde su enlace de entrada de que va de camino, llegará tarde o ya está en el taller, con la hora estimada.
vehicle.readyVehículo listo
La reparación ha terminado y el vehículo está listo para recoger.
vehicle.arrivedCliente en recepción
El personal marca en la cola de recepción que el cliente ha llegado al mostrador (la entrada por kiosco o móvil emite vehicle.checked_in).
vehicle.eta_updatedHora prevista de entrega
Se ha fijado o cambiado la hora prevista de entrega del vehículo; indica si se ha comunicado al cliente.
vehicle.deliveredVehículo entregado
El cliente ha retirado el vehículo.
vehicle.pickup_plannedRecogida planificada
El cliente ha elegido el día y la franja en que pasará a recoger el vehículo (date, slot: morning/afternoon/any).
courtesy_car.loanedCoche de cortesía prestado
Se ha entregado un coche de cortesía al cliente con contrato firmado (matrícula, km y combustible de salida, devolución prevista).
courtesy_car.returnedCoche de cortesía devuelto
El cliente ha devuelto el coche de cortesía (km y combustible de entrada, días y km extra calculados).
stock.lowStock bajo
Lo disponible de un recambio ha bajado de su stock mínimo (partId, name, unit, quantity, available, reserved, planned, minStock). Se envía una vez por cambio de nivel, no cada día.
stock.outRecambio agotado
No queda nada disponible de un recambio con mínimo o con consumo (mismos campos que stock.low). Una vez por cambio de nivel.
stock.shortageFalta stock en un presupuesto aceptado
Un presupuesto ha pasado a Aprobado o En curso y no había stock suficiente: leadId e items [{ partId, missing }] con lo que queda pendiente de pedir.
invoice.createdFactura creada
Se ha creado una factura (aún en borrador, sin número definitivo), por ejemplo al facturar un presupuesto.
invoice.issuedFactura emitida
Se ha emitido una factura con número definitivo.
payment.receivedCobro registrado
Se ha anotado un cobro sobre una factura.
client.createdCliente creado
Se ha dado de alta un cliente.
client.updatedCliente actualizado
Se han modificado los datos de un cliente.
communication.inboundMensaje entrante
Ha llegado un email, SMS, WhatsApp o llamada de un cliente.
message.receivedMensaje recibido en una conversación
Un cliente ha escrito por WhatsApp, SMS, chat web o email: conversación (clave estable canal:número), cliente, presupuesto activo, texto, adjuntos y fecha, y si el asistente externo debe contestar (external_bot).
call.completedLlamada analizada
Una llamada (del equipo o del asistente de voz) ha terminado y se ha analizado: resumen, motivo, sentimiento y transcripción si existe.
email.sentEmail enviado
Se ha enviado un email (campañas, avisos o API).
email.failedEmail fallido
Un email no se ha podido enviar.
email.openedEmail abierto
El destinatario ha abierto el email.
email.clickedClic en email
El destinatario ha pulsado un enlace del email.
email.unsubscribedBaja de email
El destinatario se ha dado de baja de los emails.
email.bouncedEmail rebotado
El email ha rebotado.
sms.sentSMS enviado
Se ha enviado un SMS.
sms.failedSMS fallido
Un SMS no se ha podido enviar.
sms.unsubscribedBaja de SMS
El destinatario ha pedido no recibir SMS.
whatsapp.unsubscribedBaja de WhatsApp
El destinatario ha pedido no recibir WhatsApp.
campaign.finishedCampaña terminada
Una campaña ha terminado de enviarse.
3 meses por 1 € →