Saltar al contenido
FlotaDoc

API para integraciones

API de FlotaDoc

Conecta tu ERP, tu programa de nóminas, tu telemática o tu propia app con FlotaDoc. Consulta y registra viajes y gastos, gestiona vehículos y conductores, y descarga los informes del mes sin pasar por la web.

El acceso se activa por empresa y bajo petición: lo pide el titular de la cuenta y el equipo de FlotaDoc lo aprueba con los permisos que necesitáis.

URL base
https://app.flotadoc.com/api
Autenticación
Bearer fd_live_…
Llamadas
POST con JSON
Especificación
OpenAPI 3.1.0 · versión 1.0.0
En esta página

Referencia por área

Cada operación con su permiso, sus campos, un ejemplo con curl y sus errores. Todas son POST a la URL base más la ruta.

Para quien conecta un ERP, un programa de nóminas, una telemática o una app propia con FlotaDoc. La referencia exacta de cada operación (campos, tipos, límites y errores) está en openapi.yaml; esta guía explica cómo usarla, con ejemplos completos.

1. Cómo conseguir el acceso

La API se activa por empresa. Hay dos vías:

  1. Lo activa el equipo de FlotaDoc cuando lo habéis pactado con nosotros.
  2. Lo pide el titular de la empresa (owner) en la web, en Mi cuenta › Integraciones › Solicitar acceso, con un formulario guiado: qué sistema queréis conectar, qué permisos necesitáis, cada cuánto se conectará, quién lo desarrolla, el contacto técnico, la fecha prevista y qué queréis conseguir. El equipo puede pedir más información, aprobarla (con los permisos pedidos o menos) o rechazarla con un motivo. Los avisos llegan por email.

El acceso queda limitado a los permisos aprobados. Para ampliarlos, el owner pide más permisos desde la misma página.

Con el acceso activo, el owner crea las claves en Mi cuenta › Integraciones › Claves: nombre, permisos y caducidad opcional (máximo 10 claves activas por empresa). En la misma página ve el uso (peticiones, errores, tiempos) y el historial de peticiones de los últimos 90 días.

2. Autenticación

Authorization: Bearer fd_live_7Abcdefghijklmnopqrstuvwxyz0123456789ABCDE
  • La clave es fd_live_ seguida de 43 caracteres (letras y cifras). Se muestra una sola vez, al crearla: guárdala en el gestor de secretos de tu sistema. Nunca en el código, en una URL, en un fichero compartido ni en los logs.
  • La clave actúa con los derechos del owner que la creó y solo en su empresa. Si esa persona deja de ser owner o se da de baja, la clave deja de funcionar (api_key_inactive).
  • Para rotar una clave: crea otra, cambia tu sistema y revoca la antigua. La revocación es inmediata.
HTTP error.code Qué pasa Qué hacer
401 api_key_invalid La clave está mal formada o no existe Revisa la cabecera y la clave
401 api_key_revoked Se revocó, o la empresa ya no tiene el acceso API activo Pide al owner una clave nueva
401 api_key_expired Caducó Pide al owner una clave nueva
401 api_key_inactive Quien la creó ya no es owner activo Pide a un owner activo una clave nueva
403 account_closed La cuenta de la empresa está cerrada —

No reintentes un 401: no se arregla solo.

3. Llamadas y respuestas

  • URL base: https://app.flotadoc.com/api. Todas las operaciones son POST {URL base}/{módulo}/{submódulo}/{acción} con un cuerpo JSON (Content-Type: application/json). Son los mismos endpoints que usa la web de FlotaDoc.

  • Los identificadores van en el cuerpo, nunca en la ruta: {"tripId": 812}.

  • Respuesta correcta: { "data": … }.

  • Error: { "error": { "code": "…", "message": "…" } }. Interpreta siempre error.code; message es un texto técnico que puede cambiar. Un 422 invalid_input trae además error.fields con el error de cada campo, todos a la vez:

    { "error": { "code": "invalid_input", "message": "invalid fields", "fields": { "date": "invalid", "km": "out_of_range" } } }
  • X-Request-Id: toda respuesta a una clave conocida lleva esta cabecera. Es el id de la petición en el historial que ve el owner. Guárdalo en tus logs junto al error: es lo que te pedirá soporte.

  • Se aplican las mismas reglas que en la web: una cuenta en solo lectura (prueba vencida, pago pendiente…) consulta y exporta pero no registra; un mes cerrado no admite cambios; con los registros inalterables activados, corregir crea una versión con motivo y borrar es anular con motivo; los límites del plan (vehículos y usuarios) son los de la empresa.

Cada clave tiene uno o más permisos. Una clave solo usa los permisos que su empresa sigue teniendo permitidos: si el equipo de FlotaDoc retira un permiso a la empresa, las claves dejan de poder usarlo en el acto (403 api_scope_missing, con scope en el cuerpo).

Permiso Para qué Operaciones
lectura Consultar viajes, gastos, vehículos, conductores y cierres, y las descargas del gestor movimientos/viajes: list, get, history, reasons-list · movimientos/gastos: list, get, history, types-list, attachment-download-url · movimientos/flota: list, get, models-search, company-models-list · movimientos/cierres: list, get, pending · movimientos/informes: monthly-report, export-create, export-status, export-download-url · movimientos/integridad: verify · auth/usuarios: list, get
registros Crear, corregir y anular viajes y gastos (con ticket) en nombre de un conductor movimientos/viajes: save, delete · movimientos/gastos: save, delete, attachment-upload-url, attachment-attach
flota Vehículos, asignaciones y conductores (solo con rol conductor) movimientos/flota: save, delete, assign, company-models-save, company-models-delete · auth/usuarios: invite, resend-invite, deactivate, reactivate

Cualquier otra ruta responde 404 not_found a una clave, aunque exista para la web (claves, uso, facturación, equipo, ajustes de la empresa…). En el OpenAPI, cada operación indica su permiso en x-flotadoc-scope.

5. Límites (429)

  • Por clave: cada clave admite un número de peticiones por minuto natural (por defecto 120; lo fija el equipo de FlotaDoc por empresa, entre 30 y 600). Al pasarlo recibes 429 rate_limited con la cabecera Retry-After (segundos hasta el minuto siguiente) y, en el cuerpo, retryAfterSeconds y limitPerMinute. Cuentan todas las peticiones de la clave, también las que fallan y las repetidas por idempotencia.
  • Global: la API entera tiene un techo de 50 peticiones por segundo (ráfaga de 100) para todos los clientes juntos. Ese 429 llega sin cuerpo JSON, sin X-Request-Id y sin Retry-After, y no aparece en el historial.
  • En los dos casos: espera y reintenta. Usa Retry-After si viene; si no, una espera exponencial con algo de azar (1 s, 2 s, 4 s… hasta 60 s). Reparte las cargas grandes (por ejemplo, la sincronización nocturna) en vez de lanzarlas en ráfaga.
async function callFlotaDoc(path, body, { idempotencyKey } = {}) {
  for (let attempt = 0; attempt < 6; attempt++) {
    const res = await fetch(`https://app.flotadoc.com/api${path}`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.FLOTADOC_API_KEY}`,
        'Content-Type': 'application/json',
        ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}),
      },
      body: JSON.stringify(body),
    });
    if (res.status !== 429 && res.status !== 503) return res;
    const retryAfter = Number(res.headers.get('retry-after'));
    const waitMs = retryAfter > 0 ? retryAfter * 1000 : Math.min(60000, 1000 * 2 ** attempt) + Math.random() * 500;
    await new Promise((r) => setTimeout(r, waitMs));
  }
  throw new Error('FlotaDoc no responde: reintenta más tarde');
}

6. Idempotencia (Idempotency-Key)

Las escrituras (registros y flota) admiten la cabecera Idempotency-Key. Úsala siempre: si se corta la conexión, puedes repetir la petición sin crear un viaje o un gasto dos veces. La excepción es movimientos/gastos/attachment-upload-url, que solo firma una URL (ver al final de esta sección).

Idempotency-Key: erp-viaje-2026-10-000123
  • De 1 a 100 caracteres A-Z a-z 0-9 _ -. Usa un id estable de tu sistema (el del registro que estás volcando), no uno aleatorio por intento.
  • Vale 24 horas por clave API.
Caso Respuesta
Primera vez Se ejecuta y se guarda la respuesta, también si es un error 4xx (un 422 o un 409 se repite igual)
Misma Idempotency-Key, misma ruta y mismo cuerpo exacto La respuesta guardada, con Idempotent-Replayed: true (no se ejecuta otra vez)
Misma Idempotency-Key con otro cuerpo u otra ruta 409 idempotency_conflict
La primera petición aún está en curso 409 idempotency_in_progress: reintenta en unos segundos
La primera respondió 5xx No se guarda: el reintento se ejecuta de nuevo
La primera se perdió (sin respuesta tras 2 minutos) El reintento se ejecuta de nuevo
Cabecera mal formada 422 invalid_idempotency_key

«Mismo cuerpo exacto» quiere decir los mismos bytes: serializa el cuerpo una vez y reutilízalo en los reintentos. Tras un 5xx o un corte, reintenta con la misma Idempotency-Key; tras corregir un 4xx (datos, mes cerrado…), manda la petición corregida con otra. En las lecturas la cabecera se ignora (por eso informes/export-create, que es de lectura, crea un trabajo nuevo en cada llamada).

attachment-upload-url no guarda respuesta. Solo firma una URL de subida que caduca a los 5 minutos: repetir la respuesta guardada te daría una URL caducada. Por eso ahí la cabecera se ignora, como en las lecturas: cada llamada devuelve una url y una key nuevas, sin Idempotent-Replayed. Si se corta la conexión, vuelve a pedirla; la subida que no uses se borra sola a las 24 horas. La idempotencia que importa está en el paso siguiente: gastos/save o attachment-attach con su Idempotency-Key.

7. Listados, paginación y sincronización

  • Listados completos, sin paginar: movimientos/flota/list, auth/usuarios/list, movimientos/viajes/reasons-list, movimientos/gastos/types-list y los de cierres devuelven todo de una vez.
  • movimientos/viajes/list sin updatedSince (el libro): los viajes activos del filtro (from, to, userId, vehicleId, status), con order (asc | desc) y limit opcional (1-500; sin él, todos).
  • movimientos/gastos/list sin updatedSince: los gastos activos del filtro con sus totales, hasta 1000 (truncated: true si hay más: acota el filtro, por ejemplo por mes).

Sincronización incremental con updatedSince (viajes y gastos)

Para mantener una copia en tu sistema, pide solo lo que ha cambiado desde la última vez:

  1. Primera página: { "updatedSince": "<instante>", "limit": 200, …filtros }. updatedSince es una fecha y hora ISO 8601 con zona (2026-10-13T08:00:00Z) y es exclusiva. La primera vez, usa 1970-01-01T00:00:00Z.
  2. La respuesta trae { "trips" | "expenses", "nextCursor", "syncedUntil" }.
  3. Páginas siguientes: el mismo cuerpo con "cursor": "<nextCursor>". Repite los filtros: el cursor no los guarda.
  4. Cuando nextCursor es null, guarda syncedUntil y úsalo como updatedSince en la próxima sincronización.

Detalles que importan:

  • Llegan también los anulados (active: false): bórralos o márcalos en tu copia. En viajes llegan además los que están en curso y los descartados.
  • syncedUntil lleva 60 segundos de margen sobre la hora del servidor: no se pierde un cambio que se estuviera guardando mientras sincronizabas.
  • Los filtros se aplican al estado actual del registro: si un gasto se corrige y sale del filtro (otro vehículo, otra fecha), deja de llegar sin aviso. Para una copia fiel, sincroniza sin filtros.
  • En viajes, el coste calculado y los nombres pueden cambiar sin que cambie updatedAt (por ejemplo, al llegar los tickets del mes). Para las cifras del mes, vuelve a pedir el libro o el informe mensual.
  • En sincronización, limit va de 1 a 200 (200 por defecto) y en viajes no se admite order.
API=https://app.flotadoc.com/api
curl -sS -X POST "$API/movimientos/gastos/list" \
  -H "Authorization: Bearer $FLOTADOC_API_KEY" -H "Content-Type: application/json" \
  -d '{"updatedSince":"2026-10-12T22:00:00Z","limit":200}'
{ "data": { "expenses": [ { "expenseId": 81, "active": true, "updatedAt": "2026-10-13T09:30:12.345Z", "…": "…" } ],
            "nextCursor": null, "syncedUntil": "2026-10-13T10:14:02.123456Z" } }

8. Flujos completos

En los ejemplos, API=https://app.flotadoc.com/api y la clave está en la variable FLOTADOC_API_KEY.

8.1 Registrar un viaje para un conductor

Permisos: lectura (para buscar conductor y vehículo) y registros.

  1. Conductor: POST /auth/usuarios/list devuelve los usuarios de la empresa. El conductor es un usuario con rol user (o un owner, que también conduce) que no esté desactivado; uno invitado que aún no ha entrado también vale. Necesitas su id.
  2. Vehículo: POST /movimientos/flota/list. Un conductor user puede usar los vehículos variables y los fijos en los que está asignado (drivers).
  3. Categoría del motivo (opcional): POST /movimientos/viajes/reasons-list.
  4. Guardar: POST /movimientos/viajes/save sin tripId y con driverUserId, que es obligatorio en un alta con clave.
curl -sS -X POST "$API/movimientos/viajes/save" \
  -H "Authorization: Bearer $FLOTADOC_API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: erp-viaje-2026-10-000123" \
  -d '{
    "driverUserId": 15,
    "vehicleId": 9,
    "tripDate": "2026-10-09",
    "origin": "Calle Mayor 1, Logroño",
    "destination": "Polígono Cantabria, Logroño",
    "distanceKm": 7.4,
    "reason": "Revisión de la instalación del cliente",
    "reasonCategoryId": 3
  }'

La respuesta (200) trae el viaje en data.trip, a nombre del conductor, con status: "recorded" y la clave que lo registró (apiKeyId).

  • Obligatorios: driverUserId, vehicleId, tripDate (YYYY-MM-DD, no posterior a hoy en Madrid), origin, destination (1-200 caracteres), distanceKm (mayor que 0, hasta 2 decimales) y reason (el motivo, 1-500). Opcionales: coordenadas (por parejas), odometerKm, routeKm, routeDurationMin, consumptionL100km y reasonCategoryId.
  • El importe lo calcula FlotaDoc: en un vehículo propio (km_rate) copia la tarifa por km de la empresa (kmRate) y calcula amount; en uno de empresa (fuel) el coste sale de los tickets de energía del mes. No se manda.
  • Errores propios: 422 con fields.driverUserId = required (falta), not_found (no es conductor de la empresa) o vehicle_not_allowed (no puede usar ese vehículo); 404 vehicle_not_found; 409 period_closed (el mes de ese vehículo está cerrado, con error.periods: lo reabre el owner en la web); 423 account_read_only (la cuenta no registra: consulta y exporta).
  • Corregir: la misma llamada con tripId. Es un guardado completo: lee antes el viaje (viajes/get) y manda todos los campos. El conductor no se puede cambiar; si cambias el vehículo, el conductor tiene que poder usarlo, como en el alta (422 con fields.vehicleId = vehicle_not_allowed). Manda changeReason (motivo de la corrección): es obligatorio si la empresa tiene activados los registros inalterables (422 reason_required).
  • Anular: POST /movimientos/viajes/delete con { "tripId": 412, "changeReason": "Duplicado" } (changeReason, obligatorio con registros inalterables).

8.2 Gasto con ticket (URL firmada y PUT)

Permiso: registros. El ticket se sube directamente al almacenamiento de FlotaDoc con una URL firmada; después se liga al gasto.

1. Pedir la URL de subida:

curl -sS -X POST "$API/movimientos/gastos/attachment-upload-url" \
  -H "Authorization: Bearer $FLOTADOC_API_KEY" -H "Content-Type: application/json" \
  -d '{"filename":"ticket-repsol.jpg","contentType":"image/jpeg","sizeBytes":184320}'
{ "data": { "url": "https://…amazonaws.com/…/ticket-repsol.jpg?X-Amz-…", "key": "42/expenses/u10/9f1c…/ticket-repsol.jpg",
            "filename": "ticket-repsol.jpg", "contentType": "image/jpeg", "sizeBytes": 184320, "expiresIn": 300 } }
  • contentType: application/pdf, image/jpeg, image/png o image/webp. sizeBytes: el tamaño exacto del fichero, hasta 20 MB (más: 413 payload_too_large).
  • Sin Idempotency-Key (§6): cada llamada da una URL nueva. Si la URL caduca antes del PUT, pide otra.

2. Subir el fichero con un PUT a url, en menos de 5 minutos y sin la cabecera Authorization. Content-Type tiene que ser exactamente el declarado y el tamaño exactamente sizeBytes (van firmados: si no coinciden, el almacenamiento responde 403).

curl -sS -X PUT "$UPLOAD_URL" -H "Content-Type: image/jpeg" --upload-file ticket-repsol.jpg

3. Guardar el gasto con el ticket (antes de 24 horas desde el paso 1), con su key en attachmentKey. El gasto y el ticket se guardan juntos o ninguno:

curl -sS -X POST "$API/movimientos/gastos/save" \
  -H "Authorization: Bearer $FLOTADOC_API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: erp-gasto-2026-10-000456" \
  -d '{
    "driverUserId": 11,
    "vehicleId": 7,
    "type": "fuel",
    "expenseDate": "2026-10-01",
    "totalAmount": 50.00,
    "baseAmount": 41.32,
    "vatRate": 21,
    "vatAmount": 8.68,
    "supplierName": "Repsol Getafe",
    "supplierTaxId": "A12345678",
    "liters": 30.12,
    "odometerKm": 120800,
    "attachmentKey": "42/expenses/u10/9f1c2a1b0d4e5f60718293a4b5c6d7e8/ticket-repsol.jpg"
  }'
  • Obligatorios: vehicleId, type, expenseDate y totalAmount (y driverUserId en el alta). Los tipos disponibles para tu empresa salen de POST /movimientos/gastos/types-list (available): los de peajes y aparcamiento, mantenimiento e ITV, y seguro, impuestos y renting dependen de los complementos que tenga contratados.
  • fuel exige liters y un vehículo de combustión o híbrido; ev_charge exige kwh y uno eléctrico o híbrido. Si mandas baseAmount y vatAmount, tienen que sumar totalAmount (±0,02 €).
  • Ligar el ticket después: guarda el gasto sin attachmentKey y llama a POST /movimientos/gastos/attachment-attach con { "expenseId": 81, "key": "<key>" }.
  • Errores del ticket: 422 attachment_not_uploaded (no se subió, pasaron más de 24 horas o ya está en otro gasto) y 422 attachment_invalid (el fichero no es una imagen o un PDF válidos). La subida es del owner que creó la clave: solo sus claves pueden ligarla.
  • Descargar un ticket: POST /movimientos/gastos/attachment-download-url con { "expenseId": 81 } devuelve una URL de descarga temporal.
  • Corregir y anular: como en viajes, gastos/save con expenseId (guardado completo) y POST /movimientos/gastos/delete con { "expenseId": 81, "reason": "Duplicado" } (aquí el motivo se llama reason; obligatorio con registros inalterables).

8.3 Descarga asíncrona (Excel, ZIP de tickets, PDF)

Permiso: lectura. Son las descargas del gestor; se generan en el servidor.

type Fichero
monthly_pdf Informe mensual en PDF
trips_xlsx Libro de viajes en Excel
expenses_xlsx Gastos en Excel, con resumen por tipo
tickets_zip Tickets del mes en un ZIP con su índice en Excel

(trips_csv y expenses_csv existen solo por compatibilidad: no los uses.)

1. Pedirla: month (YYYY-MM) y, opcional, vehicleId (sin él, toda la flota). Responde 202 con el trabajo en pending.

curl -sS -X POST "$API/movimientos/informes/export-create" \
  -H "Authorization: Bearer $FLOTADOC_API_KEY" -H "Content-Type: application/json" \
  -d '{"type":"trips_xlsx","month":"2026-09","vehicleId":7}'
{ "data": { "export": { "exportId": 77, "type": "trips_xlsx", "month": "2026-09", "vehicleId": 7, "status": "pending", "…": "…" } } }

2. Consultar el estado cada 2-5 segundos, durante 5 minutos como mucho (cada consulta cuenta para el límite de la clave):

curl -sS -X POST "$API/movimientos/informes/export-status" \
  -H "Authorization: Bearer $FLOTADOC_API_KEY" -H "Content-Type: application/json" \
  -d '{"exportId":77}'
status Qué hacer
pending, running Esperar y volver a consultar
done Pedir el enlace. Disponible hasta expiresAt (7 días)
error Con errorCode: export_too_large (ZIP de más de 400 MB), pídelo por vehículo; si no, vuelve a pedirlo
expired Volver a pedirlo

3. Descargar: export-download-url devuelve una URL temporal (expiresIn: 300, 5 minutos). Descárgala con un GET tal cual, sin la cabecera Authorization (el almacenamiento rechaza una petición con dos autenticaciones):

curl -sS -X POST "$API/movimientos/informes/export-download-url" \
  -H "Authorization: Bearer $FLOTADOC_API_KEY" -H "Content-Type: application/json" \
  -d '{"exportId":77}'
# → { "data": { "url": "https://…", "filename": "libro-viajes-1111AAA-2026-09.xlsx", "contentType": "…", "expiresIn": 300 } }
curl -sS -o libro-viajes-1111AAA-2026-09.xlsx "$DOWNLOAD_URL"
  • Si el enlace caduca, pide otro con export-download-url (el fichero sigue disponible hasta expiresAt).
  • Errores: 409 export_not_ready (aún se está generando), 409 export_failed, 410 export_expired, 404 export_not_found y 503 export_unavailable (fallo pasajero: reintenta).
  • export-create no admite Idempotency-Key: cada llamada crea un trabajo. Guarda el exportId y reutilízalo en vez de volver a pedirlo.
  • Toda descarga queda registrada en la empresa.

8.4 Alta y baja de un conductor

Permiso: flota (y lectura para listar). Con una clave solo se gestionan conductores (rol user): invitar o tocar a un owner o a un gestor responde 403 api_role_not_allowed.

1. Invitar:

curl -sS -X POST "$API/auth/usuarios/invite" \
  -H "Authorization: Bearer $FLOTADOC_API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: rrhh-alta-000789" \
  -d '{"email":"luis.perez@transportesgarcia.es","fullName":"Luis Pérez","role":"user"}'
{ "data": { "user": { "id": 42, "email": "luis.perez@transportesgarcia.es", "fullName": "Luis Pérez", "role": "user",
                      "status": "invited", "inviteExpiresAt": "2026-10-20T09:30:00.000Z", "…": "…" }, "emailSent": true } }
  • El conductor recibe un email con un enlace (válido 7 días, de un solo uso) para crear su contraseña. Desde entonces entra en la app o en la web con su email.
  • Mientras está invitado ya se le pueden asignar vehículos y registrar viajes y gastos a su nombre.
  • Plazas: auth/usuarios/list devuelve las plazas ocupadas y el límite (data.seats). Sin plaza: 409 seat_limit (el owner amplía el plan o habla con FlotaDoc).
  • Email ya usado en la empresa: 409 email_in_use con userId y status. Si está invited, reenvía; si está inactive, reactívalo.

2. Reenviar la invitación (si emailSent fue false o el enlace caducó): POST /auth/usuarios/resend-invite con { "userId": 42 }. El enlace anterior deja de valer. Hay que esperar 60 segundos entre envíos (429 resend_too_soon, con la cabecera Retry-After y retryAfterSeconds en el cuerpo, como rate_limited; reintenta con otra Idempotency-Key).

3. Asignarle vehículos fijos (opcional): POST /movimientos/flota/assign con { "vehicleId": 7, "userIds": [42, 15] }. userIds es la lista completa de conductores del vehículo (sustituye a la anterior).

4. Darle de baja: POST /auth/usuarios/deactivate con { "userId": 42 }.

  • Pasa a inactive, pierde el acceso (sus sesiones caducan en unos minutos) y libera su plaza.
  • Sus viajes, gastos, tickets y firmas se conservan y siguen en los informes.
  • Sigue en la lista de conductores de sus vehículos fijos: quítalo con flota/assign (si lo mantienes, un assign posterior responde 422 con invalidUserIds).
  • La clave no puede darse de baja a sí misma (el owner que la creó): 409 cannot_modify_self.

5. Reactivarlo: POST /auth/usuarios/reactivate con { "userId": 42 }, si la cuenta registra y hay plaza. Vuelve a active (o a invited si nunca llegó a entrar; no se envía otro email). Si fue la propia persona quien pidió su baja: 409 deletion_pending (lo resuelve el owner en la web).

9. Errores comunes

HTTP error.code Qué significa Qué hacer
401 api_key_* La clave no sirve (ver §2) No reintentes; pide otra clave
403 api_scope_missing La clave (o la empresa) no tiene el permiso de la operación Pide al owner una clave con ese permiso o que la empresa pida más permisos
403 account_closed La cuenta de la empresa está cerrada —
404 not_found La ruta no está en el catálogo Revisa la ruta en el OpenAPI
404 *_not_found El registro no existe o no es de esta empresa Revisa el id
409 idempotency_conflict / idempotency_in_progress Ver §6 —
409 otros Choca con el estado (mes cerrado, límite del plan, duplicado…) Ver la operación en el OpenAPI
422 invalid_input Campos no válidos (error.fields) Corrige los campos
423 account_read_only y otros Cuenta en solo lectura o periodo bloqueado Consulta y exporta; el owner resuelve el estado de la cuenta en la web
429 rate_limited (o sin cuerpo) Límite por clave o global Espera y reintenta (§5)
503 service_unavailable No se ha podido atender Reintenta con espera (en escrituras, con la misma Idempotency-Key)

Para soporte, envía siempre el X-Request-Id de la respuesta, la fecha y hora, y la ruta. Nunca la clave.

10. Uso e historial

El owner ve en Mi cuenta › Integraciones:

  • Uso: peticiones, porcentaje de éxito, errores 4xx y 5xx, limitadas (429) y tiempo medio; gráfico por día y tablas por endpoint y por clave, de hoy a 13 meses.
  • Historial: las peticiones de los últimos 90 días con su resultado, duración, tamaño, IP de origen, user agent y X-Request-Id. No se guardan los cuerpos de las peticiones ni la clave.

Pon a tu sistema un User-Agent que lo identifique (por ejemplo erp-sync/2.3): así el owner distingue sus integraciones en el historial.

Respuestas comunes

Pueden llegar en cualquier operación; cada operación enlaza las que le aplican.

401Unauthorized

La clave no sirve. api_key_invalid: mal formada o desconocida. api_key_revoked: revocada, o la empresa ya no tiene el acceso API activo. api_key_expired: caducada. api_key_inactive: quien la creó ya no es owner activo de la empresa. No reintentes: avisa al owner para que cree otra.

Cabeceras

  • X-Request-Idstring (uuid)

    Id de la petición en el historial de la empresa (para soporte).

Ejemplo de respuesta
{
  "error": {
    "code": "api_key_revoked",
    "message": "La clave API esta revocada"
  }
}

403Forbidden

Sin permiso. api_scope_missing: la clave no tiene el permiso de la operación o la empresa ya no lo tiene permitido (scope en el cuerpo). account_closed: la cuenta de la empresa está cerrada. Otros códigos propios de la operación se indican en su descripción.

Cabeceras

  • X-Request-Idstring (uuid)

    Id de la petición en el historial de la empresa (para soporte).

Ejemplo de respuesta
{
  "error": {
    "code": "api_scope_missing",
    "message": "La clave API no tiene permiso para esta accion",
    "scope": "registros"
  }
}

404NotFound

No existe o no es de tu empresa (*_not_found), o la ruta no está en este documento (not_found).

Cabeceras

  • X-Request-Idstring (uuid)

    Id de la petición en el historial de la empresa (para soporte).

Ejemplo de respuesta
{
  "error": {
    "code": "not_found",
    "message": "Recurso no encontrado"
  }
}

409Conflict

Choca con el estado actual (los códigos de cada operación se indican en su descripción), o con la idempotencia: idempotency_conflict (la Idempotency-Key ya se usó con otra petición) o idempotency_in_progress (la primera aún está en curso: reintenta en unos segundos).

Cabeceras

  • X-Request-Idstring (uuid)

    Id de la petición en el historial de la empresa (para soporte).

Ejemplo de respuesta
{
  "error": {
    "code": "idempotency_conflict",
    "message": "Idempotency-Key ya usada con otra peticion"
  }
}

422ValidationError

Datos no válidos. invalid_input trae en fields el código del error de cada campo, todos a la vez. invalid_idempotency_key: la cabecera Idempotency-Key está mal formada.

Cabeceras

  • X-Request-Idstring (uuid)

    Id de la petición en el historial de la empresa (para soporte).

Ejemplo de respuesta
{
  "error": {
    "code": "invalid_input",
    "message": "invalid fields",
    "fields": {
      "date": "invalid"
    }
  }
}

423Locked

Bloqueado por el estado de la cuenta o del periodo (el código concreto se indica en la operación).

Cabeceras

  • X-Request-Idstring (uuid)

    Id de la petición en el historial de la empresa (para soporte).

429RateLimited

Demasiadas peticiones. Por clave (rate_limited): has pasado el límite por minuto de tu clave; lleva Retry-After y, en el cuerpo, retryAfterSeconds y limitPerMinute. Global: la API entera tiene un techo de 50 peticiones por segundo (ráfaga de 100) para todos los clientes juntos; ese 429 llega sin cuerpo JSON, sin X-Request-Id y sin Retry-After. En los dos casos, reintenta con espera (por ejemplo, exponencial con un poco de azar).

Cabeceras

  • Retry-Afterinteger

    Segundos que faltan para el minuto siguiente.

    Mínimo 1

  • X-Request-Idstring (uuid)

    Id de la petición en el historial de la empresa (para soporte).

Ejemplo de respuesta
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones con esta clave API: espera antes de reintentar",
    "retryAfterSeconds": 23,
    "limitPerMinute": 120
  }
}

503ServiceUnavailable

No se ha podido atender la petición (service_unavailable). No se ha ejecutado nada: reintenta con espera. Con una escritura, reintenta con la misma Idempotency-Key.

Ejemplo de respuesta
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible. Intentalo de nuevo en unos minutos"
  }
}

Formato de los errores

Todas las respuestas de error llevan este cuerpo. Decide siempre por el código del error, nunca por el texto del mensaje.

Errorobject

  • errorobject

    Según el error, puede traer más datos (por ejemplo scope, retryAfterSeconds o status).

    • codestring

      Código estable del error (snake_case). Es lo que debe interpretar tu programa.

      Ejemplo: api_key_revoked

    • messagestring

      Texto técnico para depurar. Puede cambiar; no lo interpretes.

    • fieldsobjectopcional

      Solo en 422 invalid_input: código del error de cada campo (required, invalid, too_long…).

      Admite cualquier clave, con valores string

¿Quieres conectar tu sistema?

Cuéntanos qué quieres integrar y lo vemos en una llamada: qué datos necesitas, con qué permisos y cada cuánto.

Si tu empresa ya usa FlotaDoc, el titular puede pedir el acceso desde la web, en Mi cuenta › Integraciones.

Habla con nosotros