P Promec
ENESPT
▶ 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 solicitud y respuesta, permisos, errores y eventos de webhook. Los nombres de la API son los del sistema: lo que en el taller llamas cotización aparece como presupuesto (budget).

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.

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_requiredLa aceptación del presupuesto tiene que hacerla el cliente desde su enlace de seguimiento firmado.
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.

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).

Guías

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 y tracking_url: compártela con el cliente para que acepte y firme.

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.

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.
invoices:readLeer facturasNoConsultar facturas emitidas, importes y cobros. Emitir facturas no está disponible por API.
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.
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.
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 y el estado de sus límites. Vale cualquier clave válida.

Cualquier clave válida, sin permiso concreto.

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
  },
  "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.

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.

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.

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.

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.

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.

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.

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.

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.

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"
}

Presupuestos

GET /api/v1/budgets budgets:read

Listar presupuestos

Nunca incluye los presupuestos de uso interno del taller ni datos de coste. El detalle (con partidas y totales) está en /budgets/{id}.

Permiso necesario: budgets:read.

Consulta (query)

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,
      "delivered_at": null,
      "reject_reason": null,
      "tracking_url": "https://taller.ejemplo.com/public/seguimiento?id=…"
    }
  ],
  "next_cursor": "aWQ6MTIzNA",
  "has_more": true
}

POST /api/v1/budgets budgets:write

Crear un presupuesto

Entra en «Pendiente» con canal «API», igual que un alta desde el programa (registro, hito de apertura, webhook lead.created). Los totales se calculan en el servidor con el impuesto del taller; si una partida no trae tax_rate usa el del taller. El vehículo, si se indica, debe ser del cliente. No admite categorías de uso interno. Con notify_client=true (y permiso communications:send) se envía al cliente el acuse con su enlace de seguimiento.

Permiso necesario: budgets:write.

Cabeceras

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,
  "delivered_at": null,
  "reject_reason": null,
  "tracking_url": "https://taller.ejemplo.com/public/seguimiento?id=…",
  "public_notes": "Revisar también el ruido en la suspensión.",
  "totals": {
    "base": 65,
    "tax": 13.65,
    "total": 78.65,
    "currency": "EUR",
    "tax_rates": [
      {
        "rate": 21,
        "base": 65,
        "quota": 13.65
      }
    ]
  },
  "lines": [
    {
      "id": 5501,
      "description": "Cambio de aceite y filtro",
      "extended_detail": null,
      "reference": null,
      "line_type": "labor",
      "group_title": null,
      "quantity": 1,
      "unit_price": 65,
      "discount_pct": 0,
      "tax_rate": 21,
      "tax_exempt_code": null,
      "price_estimated": false,
      "base": 65,
      "currency": "EUR",
      "sort_order": 0
    }
  ]
}

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

Detalle de un presupuesto

Partidas (descripción, cantidad, precio unitario, impuesto, tipo, descuento), totales con desglose, cita, fechas de entrada y salida, responsable y URL pública de seguimiento.

Permiso necesario: budgets:read.

Parámetros

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,
  "delivered_at": null,
  "reject_reason": null,
  "tracking_url": "https://taller.ejemplo.com/public/seguimiento?id=…",
  "public_notes": "Revisar también el ruido en la suspensión.",
  "totals": {
    "base": 65,
    "tax": 13.65,
    "total": 78.65,
    "currency": "EUR",
    "tax_rates": [
      {
        "rate": 21,
        "base": 65,
        "quota": 13.65
      }
    ]
  },
  "lines": [
    {
      "id": 5501,
      "description": "Cambio de aceite y filtro",
      "extended_detail": null,
      "reference": null,
      "line_type": "labor",
      "group_title": null,
      "quantity": 1,
      "unit_price": 65,
      "discount_pct": 0,
      "tax_rate": 21,
      "tax_exempt_code": null,
      "price_estimated": false,
      "base": 65,
      "currency": "EUR",
      "sort_order": 0
    }
  ]
}

POST /api/v1/budgets/{id}/lines budgets:write

Añadir una partida

Las demás partidas conservan su id. Deja instantánea previa y registro como cualquier edición del programa. 409 budget_locked si el presupuesto está Facturado, Facturado externamente, Cancelado, Rechazado, Desistido o ya tiene factura.

Permiso necesario: budgets:write.

Parámetros

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,
    "delivered_at": null,
    "reject_reason": null,
    "tracking_url": "https://taller.ejemplo.com/public/seguimiento?id=…",
    "public_notes": "Revisar también el ruido en la suspensión.",
    "totals": {
      "base": 65,
      "tax": 13.65,
      "total": 78.65,
      "currency": "EUR",
      "tax_rates": [
        {
          "rate": 21,
          "base": 65,
          "quota": 13.65
        }
      ]
    },
    "lines": [
      {
        "id": 5501,
        "description": "Cambio de aceite y filtro",
        "extended_detail": null,
        "reference": null,
        "line_type": "labor",
        "group_title": null,
        "quantity": 1,
        "unit_price": 65,
        "discount_pct": 0,
        "tax_rate": 21,
        "tax_exempt_code": null,
        "price_estimated": false,
        "base": 65,
        "currency": "EUR",
        "sort_order": 0
      }
    ]
  }
}

PATCH /api/v1/budgets/{id}/lines/{lineId} budgets:write

Modificar una partida

Permiso necesario: budgets:write.

Parámetros

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,
    "delivered_at": null,
    "reject_reason": null,
    "tracking_url": "https://taller.ejemplo.com/public/seguimiento?id=…",
    "public_notes": "Revisar también el ruido en la suspensión.",
    "totals": {
      "base": 65,
      "tax": 13.65,
      "total": 78.65,
      "currency": "EUR",
      "tax_rates": [
        {
          "rate": 21,
          "base": 65,
          "quota": 13.65
        }
      ]
    },
    "lines": [
      {
        "id": 5501,
        "description": "Cambio de aceite y filtro",
        "extended_detail": null,
        "reference": null,
        "line_type": "labor",
        "group_title": null,
        "quantity": 1,
        "unit_price": 65,
        "discount_pct": 0,
        "tax_rate": 21,
        "tax_exempt_code": null,
        "price_estimated": false,
        "base": 65,
        "currency": "EUR",
        "sort_order": 0
      }
    ]
  }
}

DELETE /api/v1/budgets/{id}/lines/{lineId} budgets:write

Quitar una partida

Devuelve el presupuesto con los totales recalculados. La partida queda en la instantánea previa del historial de versiones.

Permiso necesario: budgets:write.

Parámetros

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,
  "delivered_at": null,
  "reject_reason": null,
  "tracking_url": "https://taller.ejemplo.com/public/seguimiento?id=…",
  "public_notes": "Revisar también el ruido en la suspensión.",
  "totals": {
    "base": 65,
    "tax": 13.65,
    "total": 78.65,
    "currency": "EUR",
    "tax_rates": [
      {
        "rate": 21,
        "base": 65,
        "quota": 13.65
      }
    ]
  },
  "lines": [
    {
      "id": 5501,
      "description": "Cambio de aceite y filtro",
      "extended_detail": null,
      "reference": null,
      "line_type": "labor",
      "group_title": null,
      "quantity": 1,
      "unit_price": 65,
      "discount_pct": 0,
      "tax_rate": 21,
      "tax_exempt_code": null,
      "price_estimated": false,
      "base": 65,
      "currency": "EUR",
      "sort_order": 0
    }
  ]
}

POST /api/v1/budgets/{id}/status budgets:write

Cambiar el estado

Admite Pendiente/En cotización/Enviado (antes de la aceptación), En curso (presupuesto ya aceptado), Finalizado (desde Aprobado, En curso o En espera) y Cancelado. «Aprobado» responde 403 client_acceptance_required con la tracking_url: la aceptación la firma el cliente. Facturar, rechazar o desistir responden 403 status_transition_forbidden; desde Facturado o Cancelado, 409 budget_locked. «Enviado» exige además communications:send y sent_via: registra que TU sistema ya lo envió (no lo envía). Ningún cambio avisa al cliente salvo Finalizado con notify_client=true y communications:send.

Permiso necesario: budgets:write.

Parámetros

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,
  "delivered_at": null,
  "reject_reason": null,
  "tracking_url": "https://taller.ejemplo.com/public/seguimiento?id=…",
  "public_notes": "Revisar también el ruido en la suspensión.",
  "totals": {
    "base": 65,
    "tax": 13.65,
    "total": 78.65,
    "currency": "EUR",
    "tax_rates": [
      {
        "rate": 21,
        "base": 65,
        "quota": 13.65
      }
    ]
  },
  "lines": [
    {
      "id": 5501,
      "description": "Cambio de aceite y filtro",
      "extended_detail": null,
      "reference": null,
      "line_type": "labor",
      "group_title": null,
      "quantity": 1,
      "unit_price": 65,
      "discount_pct": 0,
      "tax_rate": 21,
      "tax_exempt_code": null,
      "price_estimated": false,
      "base": 65,
      "currency": "EUR",
      "sort_order": 0
    }
  ]
}

POST /api/v1/budgets/{id}/documents budgets:write

Adjuntar un documento

multipart/form-data con el campo «file» (JPEG, PNG, WebP o PDF, comprobado por su contenido; máximo 4 MB). Por defecto solo lo ve el taller; client_visible=true lo enseña en el enlace de seguimiento y mechanic_visible=true en la app del mecánico. La huella de idempotencia incluye el fichero.

Permiso necesario: budgets:write.

Parámetros

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

GET /api/v1/invoices invoices:read

Listar facturas

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

Permiso necesario: invoices:read.

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…"
    }
  ],
  "next_cursor": "aWQ6MTIzNA",
  "has_more": true
}

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

Detalle de una factura

Permiso necesario: invoices:read.

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…",
  "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.

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)

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.

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.

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.

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.

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"
}

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.

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.

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

Sale con la cuenta de correo configurada en el taller y queda en el registro de Comunicación. Si rebota, el email del cliente se marca como no válido.

Permiso necesario: communications:send.

Cuerpo

{
  "to": "cliente@ejemplo.com",
  "subject": "Su vehículo está listo",
  "text": "Puede pasar a recogerlo.",
  "html": "<p>Puede pasar a recogerlo.</p>",
  "fromName": "Taller",
  "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.","html":"<p>Puede pasar a recogerlo.</p>","fromName":"Taller","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.

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
}

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.

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.

Permiso necesario: webhooks:manage.

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.

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.

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.

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.

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).
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.
vehicle.checked_inVehículo recibido
El vehículo ha entrado en el taller (recepción o sin cita).
vehicle.readyVehículo listo
La reparación ha terminado y el vehículo está listo para recoger.
vehicle.deliveredVehículo entregado
El cliente ha retirado el vehículo.
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.
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 € →