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
Referencia

Los recursos de la API, ruta a ruta

Siete recursos y ningún secreto: qué devuelve cada uno, qué scope pide y qué le puedes mandar. Con los convenios comunes arriba, porque sabiéndolos la mitad de esta página sobra.

Especificación OpenAPIScope indicado en cada rutaEjemplos copiables

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.

Los convenios, que valen para todo

Antes de mirar recurso por recurso, seis cosas que son iguales en toda la API. Sabiéndolas, la mitad de la referencia sobra.

  • JSON en los dos sentidos. Si mandas cuerpo, va con Content-Type: application/json. Sin esa cabecera el cuerpo llega y no se lee, y el error que sale despista.
  • Identificadores con prefijo. con_ contactos, pro_ productos, fac_ facturas, gas_ gastos, cob_ cobros. Son opacos: no los interpretes, guárdalos tal cual.
  • Fechas en AAAA-MM-DD y marcas de tiempo en ISO 8601 con zona UTC. La fecha de una factura es una fecha, no un instante: no lleva hora.
  • Importes en decimales, no en céntimos, y con el punto como separador. 435.60, no 43560 ni 435,60.
  • Los campos que no conoces, déjalos pasar. Se pueden añadir campos nuevos a una respuesta sin previo aviso; eso no es un cambio que rompa nada. Lo que sí romperías es tú, si tu código explota al ver una clave que no esperaba.
  • Modificar es PATCH, no PUT. Se manda sólo lo que cambia. Lo que no mandas, se queda como estaba.

Qué es firme en esta página y qué no

Las rutas, los scopes, la paginación, el formato de error y la idempotencia son el contrato y no van a cambiar. Los nombres de campo de los ejemplos enseñan la forma de cada recurso —qué se manda y qué vuelve—, no la lista completa. El esquema campo a campo lo publica GET /api/v1/openapi.json, que es la fuente de la verdad y lo que deberías darle a tu generador de clientes.

Organización

Los datos fiscales de tu empresa: nombre, NIF, régimen y series de facturación. Es de sólo lectura y no pide ningún scope, así que es la llamada con la que se comprueba que una clave vale.

RutaScopeQué hace
GET/organizacionningunoDevuelve la organización de la clave.
200 OK
{
  "id": "org_3Kd8vR",
  "nombre": "Talleres Miralles SL",
  "nif": "B12345674",
  "regimen": "general",
  "moneda": "EUR",
  "series": ["F27", "R27"]
}

No hay ruta para cambiar estos datos. Los datos fiscales de una empresa se tocan desde la aplicación y con una persona delante: cambiar un NIF por API es de esas cosas que sólo pueden salir mal.

Contactos

Clientes y proveedores. Es la primera parada de casi cualquier integración, porque una factura necesita a quién va dirigida.

RutaScopeQué hace
GET/contactoscontacts:readLista, paginada por cursor.
GET/contactos/{id}contacts:readUn contacto.
POST/contactoscontacts:writeCrea uno. Admite Idempotency-Key.
PATCH/contactos/{id}contacts:writeCambia lo que le mandes y sólo eso.
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": "cliente",
    "direccion": {
      "via": "Carrer del Carme 14",
      "cp": "17004",
      "ciudad": "Girona",
      "pais": "ES"
    }
  }'

El NIF se valida. Un NIF con la letra mal devuelve 422 datos_invalidos con el campo señalado en error.detalles. Es deliberado: un NIF mal escrito no se ve hasta que el 347 no cuadra en febrero.

Productos

El catálogo de lo que vendes, con su precio y su tipo de IVA. No hace falta para facturar —una línea de factura puede llevar el concepto escrito a mano— pero si lo usas, los precios y los impuestos dejan de repetirse en cada línea.

RutaScopeQué hace
GET/productosproducts:readLista paginada.
GET/productos/{id}products:readUn producto.
POST/productosproducts:writeCrea uno.
PATCH/productos/{id}products:writeCambia precio, nombre o impuesto.
POST /productos
{
  "referencia": "REV-ANUAL",
  "nombre": "Revisión anual",
  "precio": 180.00,
  "iva": 21,
  "unidad": "servicio"
}

Si tu tienda ya lleva su propio catálogo, no lo dupliques aquí: manda las líneas con su concepto y su precio y deja el catálogo donde está. Sincronizar dos catálogos es trabajo permanente y casi nunca hace falta.

Facturas

El recurso central, y el único con dos pasos. Una factura se crea en borrador y se emite aparte.

RutaScopeQué hace
GET/facturasinvoices:readLista paginada. Se filtra por estado, contacto y fechas.
GET/facturas/{id}invoices:readUna factura con sus líneas y sus totales.
POST/facturasinvoices:writeCrea un borrador. Admite Idempotency-Key.
PATCH/facturas/{id}invoices:writeModifica un borrador. Una emitida ya no se toca.
POST/facturas/{id}/emitirinvoices:writeAsigna número y genera el registro de VeriFactu.
GET/facturas/{id}/pdfinvoices:readDevuelve el PDF, no JSON.

Crear el borrador

POST /facturas
{
  "contacto_id": "con_7Qb3xK",
  "serie": "F27",
  "fecha": "2027-03-02",
  "vencimiento": "2027-04-01",
  "lineas": [
    {
      "concepto": "Revisión anual",
      "cantidad": 2,
      "precio": 180.00,
      "iva": 21
    },
    {
      "producto_id": "pro_2Xf9tL",
      "cantidad": 1
    }
  ]
}

Las dos formas de línea conviven: una con el concepto y el precio escritos, otra apuntando a un producto del catálogo, que aporta los suyos. Y la respuesta trae ya base, cuota_iva y total calculados, que es lo que hay que comparar con lo que esperabas cobrar antes de emitir.

Emitir, que es lo que no se deshace

Al emitir pasan tres cosas a la vez: la factura toma el siguiente número de su serie, se genera el registro encadenado de VeriFactu y el documento deja de ser modificable.

200 OK · POST /facturas/{id}/emitir
{
  "id": "fac_5Np2wD",
  "numero": "F27/0042",
  "estado": "emitida",
  "fecha": "2027-03-02",
  "base": 360.00,
  "cuota_iva": 75.60,
  "total": 435.60,
  "pendiente": 435.60,
  "verifactu": {
    "estado": "remitida",
    "huella": "7f3c1a…d904"
  }
}

No hay DELETE de una factura emitida, y no lo va a haber

Una factura emitida no se borra: se rectifica, con una rectificativa que deja constancia. Lo exige el reglamento de facturación, y la cadena de huellas de VeriFactu está construida justo para que borrar el pasado se note. Si tu integración necesita «deshacer», lo que necesita en realidad es no haber emitido todavía: comprueba el borrador y emite después.

El PDF

GET /facturas/{id}/pdf devuelve el fichero con el código QR y la leyenda ya impresos. Si pides el PDF de un borrador vuelve un 409 conflicto: un borrador no tiene número, y un PDF de factura sin número no es una factura.

Gastos

Lo que te facturan a ti. Es el otro lado del IVA y lo que hace que el modelo 303 salga bien: sin gastos sólo tienes la mitad de la cuenta.

RutaScopeQué hace
GET/gastosexpenses:readLista paginada, filtrable por fechas y proveedor.
GET/gastos/{id}expenses:readUn gasto.
POST/gastosexpenses:writeCrea uno.
PATCH/gastos/{id}expenses:writeModifica lo que le mandes.
POST /gastos
{
  "contacto_id": "con_9Wr4hM",
  "numero_proveedor": "A-2027/118",
  "fecha": "2027-02-28",
  "base": 240.00,
  "iva": 21,
  "categoria": "suministros"
}

Un gasto lleva el número que le puso el proveedor, no uno tuyo: la numeración correlativa es cosa de quien emite. Por eso numero_proveedor es un texto libre y no se valida contra ninguna serie.

Cobros

Cuándo y cómo te han pagado. Registrar el cobro es lo que baja el pendiente de una factura y lo que hace que la previsión de tesorería signifique algo.

RutaScopeQué hace
GET/cobrospayments:readLista paginada.
POST/cobrospayments:writeRegistra un cobro contra una factura.
POST /cobros
{
  "factura_id": "fac_5Np2wD",
  "fecha": "2027-03-05",
  "importe": 435.60,
  "medio": "transferencia",
  "referencia": "Pedido 1042 · Stripe"
}

Los cobros parciales son normales y la API no los estorba: manda el importe que sea y la factura queda parcialmente cobrada. Lo que no acepta es cobrar más de lo que queda pendiente, que devuelve 409 conflicto.

Si vendes por internet, el cobro suele existir antes que la factura: el pedido ya está pagado por la pasarela cuando llegas aquí. El orden entonces es crear la factura, emitirla y registrar el cobro seguido, que es lo que hacen las guías de WooCommerce y Shopify.

Webhooks

Las suscripciones a sucesos se administran también por API, con el scope webhooks:manage.

RutaScopeQué hace
GET/webhookswebhooks:manageLas suscripciones que tienes.
POST/webhookswebhooks:manageCrea una. Devuelve el secreto de firma una sola vez.
DELETE/webhooks/{id}webhooks:manageLa quita. Deja de recibir.

Cómo se comprueba la firma, qué trae cada suceso y qué hacer con un reenvío está entero en la página de webhooks, que es donde vive lo que de verdad hay que entender de esto.

La especificación, que es la fuente de la verdad

Toda la API está descrita en OpenAPI y se sirve en la propia base:

curl
curl https://erp.cairos.es/api/v1/openapi.json -o cairos.json

Con ese fichero, un generador de clientes te saca el tuyo en el lenguaje que uses, y una herramienta como Postman o Insomnia te monta la colección entera sin teclear una ruta.

Y sirve para algo más importante: cuando esta página y la especificación no coincidan, gana la especificación. Es lo que genera el propio servidor; esto es texto escrito por personas. Si encuentras una diferencia, dínosla a hola@cairos.es y se corrige la página.

La API está en español, y la aplicación también

Los recursos, los parámetros, los nombres de campo y los códigos de error están en castellano: /facturas y no /invoices, limite y no limit, no_encontrado y no not_found. Cairos es un ERP español y no mantiene un segundo juego de nombres en inglés. Lo decimos arriba porque quien integra puede no hablar español, y enterarse a mitad del trabajo es peor que saberlo antes de empezar.

Preguntas sobre la referencia

En GET /api/v1/openapi.json. Esta página enseña la forma de cada recurso con ejemplos; la especificación tiene el esquema campo a campo, los formatos y las validaciones, y es lo que genera el servidor.
Porque con PATCH mandas sólo lo que cambia y lo demás se queda como está. Con PUT tendrías que mandar el recurso entero cada vez, y el día que olvidaras un campo lo estarías borrando sin querer.
Un borrador sí. Una factura emitida no, ni por API ni desde la aplicación: se rectifica. Es lo que exige el reglamento de facturación y lo que hace posible la cadena de huellas de VeriFactu.
No, en decimales con punto: 435.60. Es lo que devuelve la API y lo que espera recibir.
Hoy no hay campos personalizados por API. Lo que sí puedes es guardar tu identificador externo en un campo de referencia y usarlo para reconciliar; es lo que hacen las guías de tienda para no facturar dos veces el mismo pedido.
Puede pasar, y no cuenta como un cambio que rompe nada. Tu código tiene que ignorar lo que no conoce. Lo que no haremos es quitar campos ni cambiarles el significado sin avisar.

Bájate la especificación y monta tu cliente

Un fichero OpenAPI y el generador que ya uses. Y si algo de esta página no coincide con lo que devuelve el servidor, gana el servidor: dínoslo y se corrige.

GET /api/v1/openapi.json · JSON sobre HTTPS

Soporte