Cairos
Facturación
Programa de facturaciónPresupuestosFacturas recurrentesGastos y proveedoresCobros y tesorería
Contabilidad e impuestos
ContabilidadModelos de HaciendaLibros registroInmovilizadoIGIC y Canarias
Operaciones
Inventario y almacenesCRMControl horarioProyectosSubvenciones y ayudas
Cumplimiento
VeriFactuTicketBAIFactura electrónicaToda la normativaSeguridad y datos
Por tipo de negocio
AutónomosPymesAsesorías y gestoríasExtranjeros en EspañaStartupsComercios y tiendas
Por sector
Hostelería y restaurantesConstrucción y reformasServicios profesionalesComercio electrónicoTodos los sectores
Por forma jurídica
AsociacionesFundacionesCooperativasClubes deportivosTodas las formas jurídicas
Cambiar de programa
ComparativasAlternativa a HoldedMigrar tus datos
Herramientas gratis
Plantilla de facturaCalculadora de IVACalculadora de IRPFTodas las herramientas
Aprender
GuíasGlosarioCalendario fiscalBlog
Desarrolladores
API y documentaciónEmpezar en cinco minutosReferencia de recursosWebhooks
Ayuda
Centro de ayudaContacto
Precios
Empieza gratis Iniciar sesión
Idempotencia

Una cabecera que evita dos facturas por el mismo pedido

Si la red se corta antes de la respuesta, quien llama no sabe si la factura se creó. Reintentar sin llave produce dos números correlativos por un pedido, y eso en una serie de facturación no se arregla borrando.

Una cabeceraVeinticuatro horas de memoriaEn todo lo que crea algo

La API está en marcha. Esto es lo que hay hoy y lo que no

Funciona. Las rutas de esta documentación están desplegadas y respondiendo en https://erp.cairos.es/api/v1/. Los ejemplos de estas páginas se pueden copiar y ejecutar. La especificación completa está en openapi.json, que es lo que importan Make y n8n.

Las claves te las creas tú, desde Desarrolladores en tu cuenta de Cairos. Empieza con una cai_test_: trabaja contra tus datos de verdad pero no registra en VeriFactu ni envía correos, así que puedes montar tu integración sin ensuciar una serie de facturación.

Y lo que todavía NO hay, dicho sin adornos: ningún conector oficial. Ni app de Shopify, ni plugin de WooCommerce, ni módulo publicado en Make o n8n. Con la API y su especificación se pueden construir —para eso están las guías de esta sección— pero construirlos es trabajo, y ese trabajo no está hecho.

Si montas algo con esto, escríbenos a hola@cairos.es. Nos interesa especialmente lo que te falte del contrato: es lo que decide qué se amplía primero.

El problema, contado como pasa de verdad

Tu tienda recibe el pedido 1042. Tu código llama a Cairos para crear la factura y emitirla. Y entonces se cae la red.

Fíjate en lo que sabes y en lo que no. Sabes que mandaste la petición. No sabes si llegó. No sabes si se procesó. Lo único que tienes es un tiempo de espera agotado, que es exactamente la misma respuesta tanto si la factura se creó como si no.

Y aquí es donde empieza el problema de verdad, porque la reacción natural es reintentar. Si la primera llamada había llegado, ahora tienes dos facturas, con dos números correlativos, por el mismo pedido.

Diagrama de secuencia en dos carriles: a la izquierda «tu sistema», a la derecha «Cairos». Flecha de ida POST /facturas que llega; Cairos crea la factura F27/0042; la flecha de vuelta 201 se corta a mitad con una marca de error. Debajo, el reintento sin llave crea F27/0043 duplicada, y con llave devuelve la misma F27/0042. Estilo de línea, colores del sistema, radio 0.

developers-idempotencia.svg · 1200×560 px

El corte se produce en la vuelta, no en la ida. Por eso quien llama no puede distinguir «no se hizo» de «se hizo y no me enteré».

Por qué eso no se arregla borrando

En casi cualquier otra API, dos registros duplicados se borran y no ha pasado nada. En una serie de facturación, no.

Los números de una serie son correlativos y sin huecos: es lo que exige el reglamento de facturación y lo que comprueba cualquiera que mire tus libros. Si has emitido la F27/0042 y la F27/0043 por el mismo pedido y borras la segunda, no te queda una serie limpia: te queda una serie con un hueco, y un hueco en una numeración correlativa es exactamente la señal que busca una inspección.

Lo que hay que hacer entonces es lo correcto, y es trabajo: emitir una factura rectificativa que anule la duplicada, dejando constancia de por qué. Con VeriFactu hay además un registro de anulación encadenado, porque la norma no permite que una factura desaparezca sin dejar rastro.

Reintentar sin llave

  • Dos facturas con dos números por un pedido.
  • Tu cliente recibe dos facturas y llama.
  • No se arregla borrando: hay que rectificar y justificar.
  • El IVA repercutido del trimestre sale de más hasta que lo corrijas.
  • Y si el reintento fue automático, puede haber pasado cien veces.

Reintentar con llave

  • La segunda llamada devuelve la misma factura, con su mismo número.
  • Tu código no tiene que distinguir «creado» de «ya estaba creado».
  • Un tiempo de espera agotado deja de ser un problema: repites y ya.
  • Vale igual para el 500 y para el proceso que se reinicia a medias.
  • Y no cuesta nada: es una cabecera.

Cómo se usa: una cabecera y nada más

En cualquier llamada que cree algo, añade Idempotency-Key con un valor tuyo:

curl -X POST https://erp.cairos.es/api/v1/facturas \
  -H "Authorization: Bearer $CAIROS_CLAVE" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-1042" \
  -d '{ "contacto_id": "con_7Qb3xK", "serie": "F27" }'

Lo que hace el servidor con eso:

  • Si es la primera vez que ve esa llave, hace el trabajo y guarda la respuesta.
  • Si ya la había visto con el mismo cuerpo, no repite nada: devuelve la respuesta guardada, con el mismo identificador y el mismo número de factura.
  • Si ya la había visto con un cuerpo distinto, devuelve 409 conflicto. Eso no es un fastidio: es la API avisándote de que estás reutilizando una llave para dos cosas diferentes, que es un fallo de tu lado.

Qué llave poner, que es lo único que hay que pensar

La regla es una sola y todo lo demás se deduce de ella: la llave tiene que ser la misma en el intento y en el reintento, y distinta para dos operaciones distintas.

Llave¿Sirve?Por qué
pedido-1042Sale del identificador del pedido en tu sistema. Al reintentar sale la misma sin tener que guardarla en ninguna parte.
emitir-pedido-1042Crear la factura y emitirla son dos operaciones: dos llaves. Con la misma para las dos, la segunda llamada daría 409.
wc-1042-2027Si tienes dos tiendas que numeran pedidos por su cuenta, mete de dónde viene: dos pedidos 1042 distintos no pueden compartir llave.
Un UUID aleatorio por intentoNoEs el error clásico. Si el reintento genera otra llave, la API no puede saber que es el mismo trabajo, y duplicas igual que sin cabecera.
La marca de tiempoNoCambia entre el intento y el reintento. Mismo caso que el anterior.
facturaNoDemasiado genérica: la segunda factura del día chocaría con la primera y saldría un 409 que no significa nada.

Si tienes que guardar la llave en algún sitio para poder reintentar, elige otra: la buena es la que se calcula a partir de algo que ya tienes.

Un UUID aleatorio sí vale, pero sólo si lo generas una vez y lo guardas junto al pedido antes de llamar, y lo reutilizas en los reintentos. Que es más trabajo que usar el identificador del pedido y no da nada a cambio.

Cuánto dura una llave

Las llaves se recuerdan durante veinticuatro horas. Pasado ese plazo, la misma llave vuelve a considerarse nueva y crearía otra factura.

Es tiempo de sobra para lo que la cabecera resuelve —un corte de red, un reintento automático, un proceso que se reinicia—, y no es un mecanismo para no duplicar nunca. Si lo que necesitas es garantizar que un pedido de hace tres semanas no se vuelva a facturar, eso no lo arregla la idempotencia: lo arregla guardar en tu sistema el identificador de la factura que creaste y mirarlo antes de llamar. Las dos cosas se complementan y hacen falta las dos.

El patrón completo, en tres líneas

Uno: antes de llamar, mira si ya guardaste el identificador de factura de ese pedido. Si lo tienes, no llames. Dos: si no lo tienes, llama con Idempotency-Key derivada del pedido. Tres: guarda el identificador que te devuelve antes de hacer nada más. Con eso, ni un corte de red ni un reproceso a mano duplican nada.

Dónde vale y dónde no hace falta

Mándala en todo lo que cree algo: POST /contactos, POST /productos, POST /facturas, POST /facturas/{id}/emitir, POST /gastos y POST /cobros. Emitir y registrar un cobro son las dos donde más duele el duplicado.

No hace falta en las lecturas. Un GET se puede repetir cien veces sin consecuencias; ésa es la definición de idempotente y por eso no lleva cabecera.

Y en un PATCH tampoco es necesaria, porque mandar dos veces el mismo cambio deja el recurso igual. Puedes mandarla si te simplifica el código, y no estorba.

La forma corta de acordarse: si la operación consume un número de serie o mueve dinero, lleva llave.

Preguntas sobre idempotencia

Un identificador que tú eliges y que le dice a la API «esta llamada y su reintento son el mismo trabajo». Si la petición se repite con la misma llave, no se vuelve a hacer nada: se devuelve la respuesta de la primera.
Técnicamente no, y en la práctica sí para cualquier integración automática. Sin ella, el primer corte de red entre tu servidor y el nuestro puede acabar en dos facturas por el mismo pedido.
Porque los números de una serie de facturación son correlativos y sin huecos. Borrar la duplicada deja un hueco, que es justo lo que no puede haber. Lo correcto es emitir una rectificativa, y con VeriFactu queda además un registro de anulación encadenado.
Una que se pueda calcular a partir de algo que ya tienes, como el identificador del pedido: pedido-1042. Lo que no vale es un aleatorio nuevo en cada intento, porque entonces el reintento parece otra operación y duplica igual.
Veinticuatro horas. Es de sobra para cubrir cortes y reintentos. Para no volver a facturar un pedido antiguo, lo que hace falta es guardar en tu sistema el identificador de la factura y comprobarlo antes de llamar.
Que esa llave ya se usó con un cuerpo distinto. Es un fallo de tu lado: estás reutilizando la misma llave para dos operaciones diferentes. Cambia la llave, no el cuerpo.

Es una cabecera. Ponla desde el primer día

Añadirla cuando la integración ya está en marcha es fácil. Explicarle a un cliente por qué tiene dos facturas del mismo pedido, no.

Idempotency-Key · En POST · Derivada del pedido

Soporte