Los recursos de la API, ruta a ruta
Los siete que toca casi toda integración, con los nombres de campo que el servidor acepta de verdad: qué devuelve cada uno, qué scope pide y qué le puedes mandar. Y al final, el resto de áreas, que ya no caben en una página.
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.
Los convenios, que valen para todo
Antes de mirar recurso por recurso, siete 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 opacos y sin prefijo. Son
cuid:clx3k2p9y0000qz8h1a2b3c4d. No llevan delante nifac_nicon_ni nada que diga de qué recurso son, así que no intentes deducirlo ni validarlos con una expresión regular: guárdalos tal cual y devuélvelos donde toque. - Nombres de campo en castellano.
descripciony no «concepto»,tipo_ivay no «iva»,fecha_emisiony no «fecha»,skuy no «referencia». Un campo que no existe no se ignora: devuelve422. - Fechas en
AAAA-MM-DDy 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, no43560ni435,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, noPUT. Se manda sólo lo que cambia y lo que no mandas se queda como estaba. Ojo: no todos los recursos se pueden modificar; una factura, por ejemplo, no.
Y una forma común para las listas: datos con los elementos, total con cuántos hay, limite con cuántos caben por página y siguiente con la URL entera de la página que sigue —o null cuando se acabó—. Un recurso suelto, en cambio, sale sin envoltorio: {"id": …} y no {"datos": {"id": …}}.
Qué enseña esta página y qué manda de verdad
Los nombres de campo de los ejemplos son los que acepta el servidor: se copian y se ejecutan. Lo que no son es la lista completa —un contacto admite muchos más campos de los que caben en un ejemplo legible—. El esquema campo a campo, con sus formatos, sus topes y sus valores permitidos, lo publica GET /api/v1/openapi.json, que se genera del propio código: es la fuente de la verdad y es lo que deberías darle a tu generador de clientes. Si algo de aquí no coincide con la especificación, gana la especificación, y avísanos.
Organización
Los datos fiscales de tu empresa: nombre, NIF, domicilio y el régimen con el que se calcula todo lo demás. 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.
| Ruta | Scope | Qué hace |
|---|---|---|
GET/organizacion | ninguno | Devuelve la organización de la clave. |
{
"id": "clx3k2p9y0000qz8h1a2b3c4d",
"nombre": "Talleres Miralles SL",
"razon_social": "Talleres Miralles, S.L.",
"nif": "B12345674",
"ciudad": "Girona",
"provincia": "Girona",
"pais": "ES",
"moneda": "EUR",
"region_fiscal": "peninsula",
"tipo_iva_defecto": 21,
"regimen_igic": "general",
"recargo_equivalencia": false,
"sin_animo_lucro": false,
"plan_contable": "pgc"
}Los tres campos que de verdad hay que leer antes de facturar son region_fiscal, tipo_iva_defecto y recargo_equivalencia. El primero decide si lo que se repercute es IVA o IGIC —peninsula, canarias, ceuta o melilla— y por tanto qué tipos tienen sentido; el tercero, si a los clientes minoristas hay que añadirles recargo. Una integración que da por hecho el 21 % peninsular le saca a un cliente canario una factura mal calculada.
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.
| Ruta | Scope | Qué hace |
|---|---|---|
GET/contactos | contacts:read | Lista, paginada por cursor. nif, email y referencia buscan exacto; buscar es parcial y mira varios campos a la vez. |
GET/contactos/{id} | contacts:read | Un contacto. |
POST/contactos | contacts:write | Crea uno. Admite Idempotency-Key. |
POST/contactos/upsert | contacts:write | Crea o actualiza buscando por NIF, correo o referencia. Es el que quieres. |
PATCH/contactos/{id} | contacts:write | Cambia lo que le mandes y sólo eso. |
DELETE/contactos/{id} | contacts:write | A la papelera, recuperable 30 días. Si ya tiene facturas o gastos, 409. |
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",
"direccion": "Carrer del Carme 14",
"cp": "17004",
"ciudad": "Girona",
"provincia": "Girona",
"pais": "ES",
"dias_pago": 30
}'const r = await fetch(BASE + "/contactos?limite=100", {
headers: { Authorization: "Bearer " + CLAVE },
});
const { datos, total, siguiente } = await r.json();<?php
// Buscar un contacto por NIF antes de crearlo es la forma
// sencilla de no llenar la agenda de duplicados.
$url = $base . "/contactos?nif=B17654321";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer " . $clave,
]);
$lista = json_decode(curl_exec($ch), true);
curl_close($ch);
$existe = $lista["datos"][0] ?? null;tipo vale customer o supplier, y ahí no hay traducción: es uno de los pocos valores de enumerado que se quedaron en inglés porque cambiarlos rompería lo ya escrito. Mandar "cliente" devuelve 422.
Y la dirección va en campos sueltos —direccion, cp, ciudad, provincia, pais—, no dentro de un objeto anidado. Es lo que se imprime en la factura, y lo que se filtra después por provincia.
El NIF se valida. Un NIF con la letra mal devuelve 422 datos_invalidos con el campo señalado en error.detalles.campos. Es deliberado: un NIF mal escrito no se ve hasta que el 347 no cuadra en febrero.
Y si vas a crear contactos desde otro sistema, usa upsert. Hace de una vez lo que si no son tres pasos —buscar, mirar si hay resultados, crear si no los hay—, que es la bifurcación donde se equivoca todo el mundo. Se le manda el mismo cuerpo más buscar_por con "nif", "email" o "referencia"; si no se dice, se usa el NIF cuando venga, si no el correo, si no la referencia. Vuelve 201 con creado: true o 200 con creado: false, y trae además coincidencias: si es mayor que uno, ya tenías duplicados de antes y se ha usado el más antiguo, que suele ser el que arrastra el histórico.
Para buscar antes de crear no uses ?buscar=. Es una búsqueda parcial que mira el nombre, el NIF, el correo, el teléfono y la referencia, así que ?buscar=B12345678 devuelve también a cualquiera que lleve ese texto en otro campo; un proceso que dé por bueno el primer resultado de esa lista acaba facturando a quien no era. Para comprobar si alguien existe, nif, email o referencia, que son exactos.
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 su descripcion y su precio escritos a mano— pero si lo usas, los precios y los impuestos dejan de repetirse en cada línea, y las ventas descuentan existencias.
| Ruta | Scope | Qué hace |
|---|---|---|
GET/productos | products:read | Lista paginada. Con ?con_existencias=1 añade dónde está el stock. |
GET/productos/{id} | products:read | Un producto. |
POST/productos | products:write | Crea uno. |
POST/productos/upsert | products:write | Crea o actualiza, aquí buscando por SKU o por código de barras. |
PATCH/productos/{id} | products:write | Cambia precio, nombre o impuesto. |
DELETE/productos/{id} | products:write | A la papelera, recuperable 30 días. Si aparece en alguna factura, 409. |
{
"nombre": "Revisión anual",
"sku": "REV-ANUAL",
"precio": 180.00,
"tipo_iva": 21,
"unidad": "h",
"clase": "service",
"stock_minimo": 0
}Tres nombres que se equivocan siempre: la referencia interna es sku —referencia es otra cosa, y en el producto no existe—, el tipo impositivo es tipo_iva y no «iva», y clase vale goods o service, que es lo que decide la clave del modelo 349 cuando el cliente es intracomunitario.
Y aquí upsert resuelve además un fallo concreto: el código de barras es único dentro de una empresa, así que un POST /productos con uno repetido devuelve 409 y para en seco la sincronización de un catálogo. Con upsert, ese caso —«ya existe»— es el normal: se actualiza y el proceso sigue.
Si tu tienda ya lleva su propio catálogo, no lo dupliques aquí: manda las líneas de la factura con su descripcion 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.
| Ruta | Scope | Qué hace |
|---|---|---|
GET/facturas | invoices:read | Lista paginada y sin las líneas: para verlas, ?con_lineas=1. Filtra por estado, fechas, cliente, serie y comercial. |
GET/facturas/{id} | invoices:read | Una factura con sus líneas y sus totales. |
POST/facturas | invoices:write | Crea un borrador —o la emite ya, con emitir—. Admite Idempotency-Key. |
POST/facturas/{id}/emitir | invoices:write | Asigna número y genera el registro de VeriFactu. |
GET/facturas/{id}/pdf | invoices:read | Devuelve el PDF, no JSON. |
Tres filtros de la lista que contestan a las tres preguntas que se hacen de verdad, y que se escriben mal cuando no se sabe que existen: «¿qué me deben?» es ?pendiente_desde=0.01; «¿qué está vencido?», ?vencidas=1&orden=vencimiento; y «¿qué ha cambiado desde ayer?», ?actualizado_desde=. Presupuestos, albaranes y proformas siguen saliendo aquí con ?tipo_documento=, pero tienen ruta propia y es la que conviene usar, porque además deja crearlos y convertirlos.
No hay PATCH de una factura, ni siquiera de un borrador, y tampoco DELETE. Un documento que va a acabar en una serie correlativa se corrige donde se ve entero, que es la pantalla; desde fuera, si el borrador está mal, se crea otro. Es menos cómodo y es a propósito.
Crear el borrador
{
"contacto_id": "clx3k2p9y0000qz8h1a2b3c4d",
"serie": "F27",
"fecha_emision": "2027-03-02",
"fecha_vencimiento": "2027-04-01",
"forma_pago": "transfer",
"lineas": [
{
"descripcion": "Revisión anual",
"cantidad": 2,
"precio": 180.00,
"tipo_iva": 21
},
{
"producto_id": "clx3k2p9y0002qz8h9i0j1k2l",
"descripcion": "Filtro de aceite",
"cantidad": 1,
"precio": 12.40,
"tipo_iva": 21
}
]
}Los tres campos que todo el mundo escribe mal la primera vez: la fecha es fecha_emision y el vencimiento fecha_vencimiento; dentro de la línea, lo que se factura es descripcion y el tipo es tipo_iva. «concepto», «iva», «fecha» y «vencimiento» no existen y devuelven 422.
Una línea con producto_id no se ahorra el resto. descripcion, cantidad y precio son obligatorios siempre: el producto sirve para enlazar la línea con el catálogo —y para que descuente existencias— pero lo que se imprime en la factura es lo que tú mandas, porque el precio de una factura es el pactado en su día y no el que tenga hoy la ficha.
La respuesta trae ya base_imponible, cuota_iva y total calculados, que es lo que hay que comparar con lo que esperabas cobrar antes de emitir.
Dos campos más que ahorran trabajo: emitir: true crea y emite en una sola llamada —el valor por defecto es el prudente, un borrador sin número—, y precios_con_iva: true dice que los precio de las líneas llevan el impuesto dentro, como en el escaparate de una tienda, y deja que Cairos desglose la base hacia atrás en vez de que lo hagas tú.
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.
{
"id": "clx3k2p9y0001qz8h5e6f7g8h",
"serie": "F27",
"numero": 42,
"referencia": "F27-0042",
"tipo_documento": "invoice",
"estado": "sent",
"fecha_emision": "2027-03-02",
"fecha_vencimiento": "2027-04-01",
"base_imponible": 360.00,
"cuota_iva": 75.60,
"cuota_irpf": 0.00,
"total": 435.60,
"cobrado": 0.00,
"pendiente": 435.60,
"verifactu": {
"registrada": true,
"huella": "7f3c1a…d904",
"registrada_en": "2027-03-02T10:41:08.517Z",
"aeat": null
}
}numero es un entero y referencia es el texto que sale impreso, serie y número juntos. En un borrador los dos vienen a null: un borrador no ocupa número, se le reserva al emitirlo. Y estado recorre draft → sent → paid, con overdue cuando pasa el vencimiento sin cobrar.
Emitir dos veces no es un error: la segunda llamada devuelve la misma factura con su mismo número y un 200 en lugar del 201. Es la única respuesta que no arriesga un salto en la numeración cuando quien reintenta no sabe si la primera llegó.
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, no JSON, con el código QR y la leyenda ya impresos. Es el mismo PDF que sale del botón de descargar de la pantalla —la misma plantilla, el mismo logo—, así que una tienda puede adjuntarlo a su propio correo sin rehacer el diseño. Con ?descargar=1 llega como adjunto en vez de para verlo en el navegador.
Un borrador también tiene PDF, y sale con la palabra «borrador» en el nombre del fichero en lugar de un número que todavía no es suyo. Sirve para enseñárselo a alguien antes de emitir; lo que no es todavía es una factura.
El único fallo propio de esta ruta es un 503: el documento se imprime de verdad, y cuando la cola de impresión está ocupada no sale. Es pasajero y se reintenta en unos segundos.
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.
| Ruta | Scope | Qué hace |
|---|---|---|
GET/gastos | expenses:read | Lista paginada, filtrable por proveedor_id, categoría, fechas y si está pagado. |
GET/gastos/{id} | expenses:read | Un gasto. |
POST/gastos | expenses:write | Crea uno. |
{
"proveedor_id": "clx3k2p9y0003qz8hm3n4o5p6",
"referencia": "A-2027/118",
"fecha": "2027-02-28",
"base": 240.00,
"tipo_iva": 21,
"irpf": 0,
"categoria": "suministros",
"clase": "goods",
"pagado": false
}Aquí el proveedor es proveedor_id y no «contacto_id», y tiene que apuntar a un contacto de tipo supplier. El número de la factura del proveedor es referencia: es su numeración, no la tuya, así que es texto libre y no se valida contra ninguna serie. Y el tipo impositivo es tipo_iva, igual que en todas partes.
La cuota y el total no se mandan: se calculan. Dejar que quien llama mande el total es dejar que mande uno que no cuadre con la base y el tipo, y a partir de ahí el 303 y el libro de IVA salen mal sin que nada avise. La respuesta trae cuota_iva y total ya hechos, con el mismo redondeo que la pantalla.
Y clase —goods o service— no es un adorno: es la clave con la que ese gasto entra en el modelo 349 si el proveedor es intracomunitario.
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.
| Ruta | Scope | Qué hace |
|---|---|---|
GET/cobros | payments:read | Lista paginada. |
POST/cobros | payments:write | Registra un cobro contra una factura. |
{
"factura_id": "clx3k2p9y0001qz8h5e6f7g8h",
"fecha": "2027-03-05",
"importe": 435.60,
"metodo": "transfer",
"notas": "Pedido 1042 · Stripe"
}La forma de pago es metodo —no «medio»— y sus valores están en inglés: transfer, cash, card, direct_debit u other. Un "transferencia" devuelve 422. Y para dejar escrito de dónde viene el dinero, notas.
Los cobros parciales son normales y la API no los estorba: manda el importe que sea y la factura queda parcialmente cobrada, con su pendiente al día. Lo que no acepta es cobrar más de lo que queda pendiente, y eso sale como 422 datos_invalidos diciendo cuánto quedaba —no como un conflicto—: es un dato que no cuadra, no un estado que lo impida.
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.
| Ruta | Scope | Qué hace |
|---|---|---|
GET/webhooks | webhooks:manage | Las suscripciones que tienes. |
POST/webhooks | webhooks:manage | Crea una. Devuelve el secreto de firma una sola vez. |
GET/webhooks/{id} | webhooks:manage | Una suscripción, con sus contadores de fallos. |
PATCH/webhooks/{id} | webhooks:manage | Cambia sucesos, descripción o modo de aviso. La URL no. |
DELETE/webhooks/{id} | webhooks:manage | La quita. Deja de recibir. |
POST/webhooks/{id}/probar | webhooks:manage | Dispara un aviso de mentira contra tu servidor. |
GET/webhooks/{id}/envios | webhooks:manage | El historial de ese webhook: qué se mandó, qué contestaste y cuándo se reintentará. |
GET/envios | webhooks:manage | Lo mismo, pero de todos los webhooks a la vez. Con ?estado=abandoned, la lista de lo que se perdió. |
POST/webhooks/{id}/envios/{envioId}/reintentar | webhooks:manage | Vuelve a mandar uno que falló. Un solo intento. |
GET /envios es la ruta que se busca cuando algo ha ido mal. El día que tu servidor se cae, lo que hace falta no es entrar webhook por webhook: es la lista completa de lo que no entró, de golpe, para poder reintentarlo. Cada fila trae el webhook_id, la URL a la que iba y el cuerpo entero, así que también sirve para reproducir el aviso en tu máquina. Pide webhooks:manage y no un permiso más blando porque ese cuerpo lleva la factura dentro: esta lista enseña tanto como /facturas.
La URL no se puede cambiar con un PATCH, y no es un descuido. El secreto de un webhook está pactado con un destino concreto; dejar mover la URL manteniendo el secreto convertiría ese PATCH en la forma de redirigir a otro sitio los avisos de una empresa con su firma buena puesta. Para cambiar de destino se crea otro webhook, que estrena secreto. Lo que sí se puede cambiar es todo lo demás, y por eso existe el método: añadir un suceso ya no obliga a borrar y recrear, que es lo que perdía el secreto.
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.
Y todo lo demás, que ya no cabe en una página
Los recursos de arriba son los que casi toda integración toca, y por eso llevan ejemplo. No son todos. La API cubre además estas áreas, cada una con su permiso y sus rutas propias:
| Área | Qué hay | Permiso |
|---|---|---|
| Presupuestos, albaranes y proformas | Rutas propias, y las acciones que faltaban: aceptar y rechazar un presupuesto, y convertir cualquiera de los tres en factura. | invoices:* |
| Suscripciones | Las facturas recurrentes, con pausar, reanudar y emitir ahora. | invoices:* |
| Pedidos de compra | Lo que le pides a un proveedor, su estado y la recepción de mercancía. | expenses:* |
| CRM | Oportunidades con sus etapas y motivos de pérdida, y tareas de seguimiento. | crm:read · crm:write |
| Inventario | Almacenes, existencias por almacén, movimientos, ajustes, traspasos y lotes. | inventory:* |
| Tarifas y precios | Tarifas con escalas, precios de proveedor y qué precio le toca a un cliente. | pricing:* |
| Proyectos | Proyectos con su coste y su desviación. | projects:* |
| Comisiones | Reglas, devengos y liquidaciones. Sólo lectura. | commissions:read |
| Socios, cuotas y donativos | El libro de socios de una entidad, con sus cuotas y sus donativos. Y los carnets: emitirlos y comprobar uno por su código, que es lo que hace falta para dejar pasar a alguien en la puerta. | members:* |
| Control horario | Personas, jornadas y resumen de horas. Sólo lectura. | timetracking:read |
| Inmovilizado | Bienes y su cuadro de amortización. Sólo lectura. | assets:read |
| Tiendas conectadas | Estado de la sincronización y cómo lanzar una. | stores:read · stores:write |
| Catálogos, sucesos e informes | Las listas que rellenan un desplegable, la ficha de cada suceso y los totales de un periodo. | Ninguno o invoices:read |
Aquí no ponemos cuántas rutas tiene cada área, porque cambia solo. La cuenta de hoy sale de openapi.json, y el mapa por áreas está en la portada.
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 https://erp.cairos.es/api/v1/openapi.json -o cairos.jsonCon 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.
Sale en OpenAPI 3.1, y hay una variante que ahorra una tarde: ?version=3.0 devuelve el mismo documento traducido a 3.0.3. Varios importadores —Make entre ellos— todavía se atascan con un type: ["string","null"], que es como 3.1 dice que un campo puede venir vacío, y o fallan al importar o te crean la operación con los campos anulables mal tipados. No son dos contratos distintos: es el mismo, traducido.
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 info@cairos.es y se corrige la página.
La API está en español
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. La aplicación sí está traducida al inglés, al catalán, al gallego y al euskera; lo que no se traduce son los nombres de los recursos de la API, porque cambiarlos rompería toda integración escrita hasta hoy.
Preguntas sobre la referencia
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.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./facturas/{id} sólo admite GET. Un documento que va a acabar en una serie correlativa se corrige donde se ve entero, que es la pantalla. Y una factura ya emitida no se borra en ninguna parte: se rectifica, que es lo que exige el reglamento de facturación y lo que hace posible la cadena de huellas de VeriFactu.435.60. Es lo que devuelve la API y lo que espera recibir.referencia en un contacto o en un gasto, etiquetas o notas_internas en una factura. Y usarlo para reconciliar, que es lo que hacen las guías de tienda para no facturar dos veces el mismo pedido.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