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
Primeros pasos

De cero a una factura emitida, en seis llamadas

Sin SDK, sin instalar nada y sin leerse la referencia entera. Seis llamadas que puedes copiar, pegar y ejecutar ahora mismo: la API está desplegada y la clave te la creas tú desde tu cuenta. Empieza con una de pruebas y no ensuciarás ninguna serie de facturación.

curl, JavaScript y PHPSin dependenciasCopiar y pegar

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 info@cairos.es. Nos interesa especialmente lo que te falte del contrato: es lo que decide qué se amplía primero.

El recorrido

Lo que vas a hacer

Tres bloques. El primero se hace en tu cuenta, en un minuto; los otros dos son código.

1

Crear la clave

En Desarrolladores, dentro de tu cuenta. Empieza por una de pruebas, y apúntala: se enseña una sola vez.

2

Comprobar que llega

Una llamada a /organizacion, que no pide ningún permiso. Si responde, el resto es cuesta abajo.

3

Facturar

Contacto, factura en borrador, emisión y PDF. Cuatro llamadas más y ya está hecho.

1 · Crea la clave

En tu cuenta de Cairos, en Desarrolladores. No hay que pedirla por correo ni esperar a que nadie conteste: eliges el modo, marcas los permisos uno a uno y se genera al momento. Empieza con una de pruebas, que empieza por cai_test_.

Marca los permisos que vas a usar en esta página y ninguno más: contacts:write para el paso 3, e invoices:read e invoices:write para los pasos 4, 5 y 6. Si te falta uno, el error lo dice por su nombre y se arregla creando otra clave.

La clave se enseña una sola vez. No la guardamos: guardamos su huella, que sirve para reconocerla pero no para reconstruirla. Si la pierdes no te la podemos recuperar; hay que revocarla y crear otra. Guárdala donde guardes las contraseñas de tu servidor —una variable de entorno, un gestor de secretos— y no en el código.

Qué hace distinta a la clave de pruebas

Trabaja contra los mismos datos que la de producción: lo que crees con ella aparece en tu cuenta. Lo que no hace es registrar en VeriFactu ni enviar correos. Eso es exactamente lo que te permite desarrollar sin ensuciar una serie de facturación de verdad. No es una copia de tu empresa en un servidor aparte, y conviene tenerlo claro antes de lanzar un bucle de pruebas.

2 · La primera llamada, que confirma que todo está bien

GET /organizacion devuelve los datos fiscales de tu empresa. Es la llamada más barata que hay y no pide ningún scope: si tu clave vale, responde.

export CAIROS_CLAVE=cai_test_TU_CLAVE

curl https://erp.cairos.es/api/v1/organizacion \
  -H "Authorization: Bearer $CAIROS_CLAVE"

Cuando salga un 200 con el nombre de tu empresa dentro, habrás terminado la parte difícil.

200 OK
{
  "id": "clx3k2p9y0000qz8h1a2b3c4d",
  "nombre": "Talleres Miralles SL",
  "razon_social": "Talleres Miralles, S.L.",
  "nif": "B12345674",
  "ciudad": "Girona",
  "provincia": "Girona",
  "moneda": "EUR",
  "region_fiscal": "peninsula",
  "tipo_iva_defecto": 21,
  "recargo_equivalencia": false,
  "plan_contable": "pgc"
}

Los identificadores son así de feos a propósito: son cuid, opacos y sin prefijo. No los interpretes ni intentes deducir de qué recurso son —guárdalos tal cual y mándalos donde toque—, porque el día que dejen de tener esta forma tu código no debería enterarse.

Y si sale un 401, la clave no ha llegado bien. Los dos motivos son casi siempre el mismo: falta la palabra Bearer delante, o la variable de entorno está vacía y estás mandando la cabecera sin nada detrás.

3 · Crea un contacto

Una factura necesita a quién se la haces. Con contacts:write:

curl -X POST https://erp.cairos.es/api/v1/contactos \
  -H "Authorization: Bearer $CAIROS_CLAVE" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: alta-cliente-1042" \
  -d '{
    "nombre": "Grupo Bonaire SL",
    "nif": "B17654321",
    "email": "admin@bonaire.example",
    "tipo": "customer"
  }'

Vuelve un 201 con el contacto creado y su id. Guárdatelo: es lo que va a pedir la factura.

Fíjate en la cabecera Idempotency-Key. Aquí todavía no duele —un contacto duplicado se borra— pero es el mismo hábito que dentro de dos pasos evita que emitas dos facturas del mismo pedido. Está contado entero en idempotencia.

4 · Crea la factura, que nace en borrador

Con invoices:write. La factura se crea sin número y en estado borrador:

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": "clx3k2p9y0000qz8h1a2b3c4d",
    "serie": "F27",
    "fecha_emision": "2027-03-02",
    "lineas": [
      {
        "descripcion": "Revision anual",
        "cantidad": 2,
        "precio": 180.00,
        "tipo_iva": 21
      }
    ]
  }'
201 Created
{
  "id": "clx3k2p9y0001qz8h5e6f7g8h",
  "serie": "F27",
  "numero": null,
  "referencia": null,
  "tipo_documento": "invoice",
  "estado": "draft",
  "fecha_emision": "2027-03-02",
  "fecha_vencimiento": null,
  "base_imponible": 360.00,
  "cuota_iva": 75.60,
  "cuota_irpf": 0.00,
  "total": 435.60,
  "cobrado": 0.00,
  "pendiente": 435.60
}

numero y referencia vienen a null, y eso es correcto: el número no existe hasta que se emite. Los totales, en cambio, ya vienen calculados, así que aquí es donde se comprueba que lo que has mandado cuadra con lo que esperabas cobrar.

Fíjate en estado: vale draft, en inglés. Los recursos y los campos de esta API están en castellano, pero unos cuantos valores de enumerado se quedaron en inglés —draft, sent, paid, overdue— y ahí se quedan, porque traducirlos rompería toda integración escrita hasta hoy.

5 · Emítela, que es el paso que no se deshace

Ahora sí. Al emitir, la factura toma el siguiente número de su serie y se genera el registro de VeriFactu encadenado con el anterior.

curl -X POST \
  https://erp.cairos.es/api/v1/facturas/clx3k2p9y0001qz8h5e6f7g8h/emitir \
  -H "Authorization: Bearer $CAIROS_CLAVE" \
  -H "Idempotency-Key: emitir-pedido-1042"
201 Created
{
  "id": "clx3k2p9y0001qz8h5e6f7g8h",
  "serie": "F27",
  "numero": 42,
  "referencia": "F27-0042",
  "estado": "sent",
  "total": 435.60,
  "pendiente": 435.60,
  "verifactu": {
    "registrada": true,
    "huella": "7f3c1a…d904",
    "registrada_en": "2027-03-02T10:41:08.517Z",
    "aeat": null
  }
}

Dos cosas de esa respuesta que conviene mirar bien. numero es un entero, no un texto: el «F27-0042» que se enseña impreso es referencia, que junta serie y número. Y el estado pasa a sent, que es el mismo en el que la deja el botón «Marcar enviada» de la pantalla.

Si vuelves a llamar a /emitir sobre una factura ya emitida, no sale un error: sale la misma factura con su mismo número, y un 200 en vez del 201. Es a propósito, y es lo que permite reintentar sin arriesgar un salto en la numeración cuando no sabes si la primera llamada llegó.

Con una clave de pruebas, «verifactu» viene con registrada: false

El bloque llega igual —no desaparece—, pero dice la verdad: una clave cai_test_ no registra en VeriFactu ni manda correos, así que huella y registrada_en vienen a null. Todo lo demás —el número de la serie, los totales, el PDF— funciona igual. Y ese campo es justo el que hay que mirar el día que pases a producción, para comprobar que ahora sí se registra.

6 · Y descárgate el PDF

GET /facturas/{id}/pdf no devuelve JSON: devuelve el fichero, con su código QR y su leyenda ya impresos.

curl
curl https://erp.cairos.es/api/v1/facturas/clx3k2p9y0001qz8h5e6f7g8h/pdf \
  -H "Authorization: Bearer $CAIROS_CLAVE" \
  -o factura.pdf

Y ya está: seis llamadas, y al final una factura emitida, numerada y en PDF. El resto de la documentación es detalle sobre esto.

Lo que falla en los dos primeros minutos

Casi siempre es una de estas cinco cosas, y ninguna es interesante. Por eso vale la pena tenerlas juntas:

Lo que vesLo que pasa de verdad
401 no_autenticadoFalta la palabra Bearer delante de la clave, o la variable de entorno está vacía y mandas la cabecera con nada dentro. Comprueba lo segundo antes de dar por perdida la clave.
403 sin_permisoLa clave es válida pero le falta el scope de esa llamada. Y ojo con la trampa: tener invoices:write no te deja leer facturas. Son dos permisos distintos.
422 datos_invalidosEl JSON llegó, pero algo dentro no vale. error.detalles te dice qué campo y por qué; míralo antes de tocar nada.
409 conflictoEstás intentando algo que el estado del documento no permite: emitir una factura ya emitida, o reutilizar una Idempotency-Key con un cuerpo distinto.
El servidor no responde JSONSuele ser el Content-Type. Si mandas cuerpo, tiene que ir application/json: sin él, el cuerpo llega y no se lee.

Los siete códigos de error, con lo que hay que hacer con cada uno, están en errores y límites.

Y ahora, ¿por dónde sigo?

Depende de lo que estés montando, y sinceramente sólo hay tres caminos:

  • Vas a crear facturas desde otro sistema. Lee idempotencia antes que nada. Es la página más aburrida de esta sección y la que evita el único error de esta API que no se arregla borrando.
  • Necesitas enterarte de lo que pasa en Cairos. Entonces lo tuyo son los webhooks, no consultar la API cada cinco minutos.
  • Vas a leer datos y poco más. Mira la paginación por cursor, porque una lista de facturas de un año no cabe en una respuesta.

Y si lo que tienes delante es una tienda o una herramienta de automatización, hay página propia: WooCommerce, Shopify, Make y n8n.

Dudas del primer día

No. La clave se emite contra una organización, así que hace falta una cuenta —el plan gratuito sirve—. La clave te la creas tú desde Desarrolladores; no hay que pedirla ni esperar a que nadie conteste.
Porque emitir es irreversible: asigna el número correlativo de la serie y genera el registro de VeriFactu. Separarlo en dos llamadas te deja comprobar los totales antes de que ocurra lo que ya no se puede deshacer. Si quieres las dos cosas seguidas, encadena las dos llamadas; lo que no hacemos es quitarte la oportunidad de mirar.
Una factura emitida no se borra: se rectifica, con una factura rectificativa que deja constancia. Es lo que exige el reglamento de facturación, y por eso no hay un DELETE que lo arregle. Está explicado en VeriFactu.
Por defecto no: precio es el precio unitario sin impuestos y tipo_iva es el tipo en porcentaje. La respuesta trae base_imponible, cuota_iva y total ya calculados, que es lo que conviene comparar con lo que esperabas. Si vienes de una tienda al público, donde el precio que existe es el de la etiqueta, manda precios_con_iva: true y Cairos desglosa la base hacia atrás en céntimos enteros, con el mismo motor que usa el conector de tiendas.
No lo hagas. Poner una clave de API en el JavaScript de una página es publicarla: cualquiera que abra las herramientas del navegador la ve. Las llamadas se hacen desde tu servidor, y el navegador habla con tu servidor, no con Cairos.

Seis llamadas, quince minutos con calma

Crea la clave de pruebas, pega el primer curl y sigue hasta el PDF. Y si montas algo con esto, cuéntanoslo: lo que le falta a la gente del contrato es lo que decide qué se amplía primero.

Clave de pruebas · Mismos datos, sin VeriFactu ni correos

Soporte