Documentación
La API de CopilotGestoria
La contabilidad, la facturación y los modelos fiscales de tus clientes, en tu propia web, app o programa de gestión.
Empezar en cinco minutos
- El gestor crea una clave desde su panel, en Integraciones → API y desarrolladores.
- Marca qué permisos lleva y sobre qué clientes puede operar.
- Copia la clave: se muestra una sola vez.
Tu primera llamada, que no consume cupo y te dice qué puedes hacer:
curl https://copilotgestoria.com/api/public/v1/status \
-H "Authorization: Bearer cg_live_TU_CLAVE"
Las tres cabeceras
| Cabecera | Cuándo | Para qué |
|---|---|---|
Authorization |
Siempre | Bearer cg_live_… — quién eres. |
X-CG-Client |
Casi siempre | Sobre qué cliente actúas. Los válidos salen en GET /clients. |
Idempotency-Key |
Toda escritura | Un UUID por operación. Obligatoria: ver abajo. |
Por qué la idempotencia es obligatoria
En la mayoría de las API es opcional. Aquí no, y el motivo es concreto: emitir una factura consume un número correlativo de serie y añade un eslabón a la cadena de huellas de VeriFactu. Eso no se deshace. Si tu petición agota el tiempo de espera y reintentas sin clave, no obtienes un registro repetido: obtienes una segunda factura real que solo se corrige emitiendo una rectificativa.
Con clave, el comportamiento es predecible:
- Reintento con la misma clave y los mismos datos → te devolvemos la misma respuesta, sin volver a ejecutar nada.
- Misma clave con datos distintos →
409conidempotency_key_reutilizada. Nunca te devolvemos en silencio el resultado anterior. - Guardamos el resultado durante 24 horas, también si fue un error del servidor.
curl -X POST https://copilotgestoria.com/api/public/v1/invoices \
-H "Authorization: Bearer cg_live_TU_CLAVE" \
-H "X-CG-Client: 4821" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"serie": "A",
"ejercicio": 2026,
"fecha_expedicion": "2026-09-05",
"tipo_factura": "F1",
"receptor_nif": "B12345678",
"receptor_nombre": "Cliente Ejemplo SL",
"lineas": [
{"concepto": "Servicios de consultoría", "cantidad": 1, "precio_unitario": 1000, "iva_pct": 21}
]
}'
Lo que tarda va en diferido
Dos operaciones no caben en una petición HTTP y responden 202 en vez de
201: subir un documento al OCR y generar los
modelos anuales (100, 180, 190, 193, 200, 202, 347, 349 y 390). Las dos van
de noventa a ciento veinte segundos, y devolverte un corte de conexión a mitad sería
dejarte sin saber si tu factura entró.
El 202 trae un trabajo con su consultar_en. Pregunta por él
hasta que el estado deje de ser pending o running:
| Estado | Qué significa |
|---|---|
pending | Aceptado, todavía no ha empezado. |
running | En marcha. |
succeeded | Listo. resultado trae el mismo recurso que devolvería la vía directa. |
failed | Falló. error.code dice por qué; no se reintenta solo. |
curl -X POST https://copilotgestoria.com/api/public/v1/documents \
-H "Authorization: Bearer cg_live_TU_CLAVE" \
-H "X-CG-Client: 4821" \
-H "Idempotency-Key: $(uuidgen)" \
-F file=@factura.pdf
→ 202 { "object": "job", "id": "…", "estado": "pending", "consultar_en": "…/jobs/…" }
curl https://copilotgestoria.com/api/public/v1/jobs/<id> -H "Authorization: Bearer cg_live_TU_CLAVE"
→ 200 { "object": "job", "estado": "succeeded", "resultado": { … } }
GET /jobs/{id} responde 200 también cuando el trabajo falló:
que algo saliera mal no es un fallo de tu consulta. Consultar un trabajo no exige
ningún permiso añadido — ya lo diste al pedir la operación.
Paginación
Por cursor, nunca por número de página. Si alguien inserta una factura mientras
recorres un ejercicio, con page=3 te saltarías o repetirías registros —
y en una sincronización contable eso es un descuadre que aparece semanas después.
GET https://copilotgestoria.com/api/public/v1/invoices?limit=50
→ { "object": "list", "data": [...], "has_more": true, "next_cursor": "abc123" }
GET https://copilotgestoria.com/api/public/v1/invoices?limit=50&cursor=abc123
Errores
Todos tienen la misma forma, y todos traen el enlace a la petición concreta:
{
"error": {
"type": "permission_error",
"code": "scope_insuficiente",
"message": "Esta clave no tiene el permiso «Emitir facturas nuevas...».",
"doc_url": "https://.../docs/api#errores",
"request_id": "b2c3d4...",
"request_log_url": "https://.../user/api/registro?request_id=b2c3d4..."
}
}
El code es estable: puedes programar contra él aunque cambiemos el texto.
El request_id viaja también en la cabecera Request-Id de
todas las respuestas, correctas y fallidas: guárdalo en tus trazas y
el gestor podrá encontrar esa llamada exacta en su panel.
Límites
Hay dos cosas distintas: el ritmo (peticiones por minuto) y el
cupo mensual de lecturas del plan de la gestoría. Escribir no
consume cupo; leer sí. Cuando el cupo se agota recibes un 429 con
cupo_mensual_agotado y un Retry-After que apunta al día 1
del mes siguiente — no se factura el exceso, se bloquea, para que
un bucle tuyo no le genere una factura sorpresa a la gestoría. Las escrituras
siguen funcionando.
El ritmo se cuenta por clave y por cliente final. Cada respuesta trae
RateLimit-Limit, RateLimit-Remaining y
RateLimit-Reset; el 429 trae además
Retry-After y una cabecera X-CG-Rate-Limited-Reason que te
dice cuál de los dos límites has tocado, porque bajar el ritmo y repartir mejor entre
clientes son arreglos distintos.
Permisos
| Permiso | Qué autoriza |
|---|---|
clientes:leer |
Ver la lista de clientes autorizados y sus datos fiscales básicos |
clientes:derechos |
Atender los derechos del cliente: exportar todo su histórico y borrar su rastro Escribe datos |
facturas:leer |
Consultar las facturas emitidas y recibidas |
facturas:escribir |
Emitir facturas nuevas en nombre del cliente Escribe datos |
documentos:leer |
Consultar los documentos digitalizados y sus datos extraídos |
documentos:escribir |
Enviar documentos para su digitalización Escribe datos |
gastos:leer |
Consultar los gastos y recibos sin factura |
gastos:escribir |
Registrar gastos y recibos (quedan en borrador hasta que el gestor confirme) Escribe datos |
contabilidad:leer |
Consultar asientos contables, plan contable y periodos |
bancos:leer |
Consultar cuentas y movimientos bancarios |
nominas:leer |
Consultar las nóminas generadas |
modelos:leer |
Consultar los modelos fiscales y descargar sus ficheros |
modelos:escribir |
Generar modelos fiscales nuevos Escribe datos |
modelos:presentar |
Presentar modelos ante la AEAT (irreversible) Escribe datos |
webhooks:gestionar |
Configurar avisos automáticos hacia un servidor propio Escribe datos |
Avisos automáticos (webhooks)
En lugar de preguntar cada pocos minutos, te avisamos. La firma va en la cabecera
CG-Signature con el formato t=<marca>,v1=<hmac>,
calculada sobre marca + "." + cuerpo crudo.
Cuatro cosas que tu receptor tiene que hacer bien:
- Comparar la firma en tiempo constante, nunca con
===. - Rechazar lo que llegue con más de 5 minutos: si no, cualquiera que capture una entrega puede reproducirla para siempre.
- Responder
401a una firma inválida. Lo comprobamos: antes de activar tu destino te enviamos una firma buena y otra mala a propósito, y si aceptas la mala no lo activamos. - Descartar duplicados por el
iddel evento: puede llegar el mismo más de una vez y no garantizamos el orden.
| Evento | Cuándo |
|---|---|
invoice.emitted | Se ha emitido una factura |
invoice.verifactu_sent | La factura se ha enviado a la AEAT |
invoice.verifactu_accepted | La AEAT ha aceptado la factura |
invoice.verifactu_accepted_with_errors | La AEAT la ha registrado CON INCIDENCIAS (no es un éxito) |
invoice.verifactu_rejected | La AEAT ha rechazado la factura |
invoice.verifactu_needs_review | Estado desconocido tras el envío: lo revisa el gestor |
invoice.annulled | Se ha anulado una factura |
invoice.payment_recorded | Se ha registrado un cobro |
document.processed | Se ha digitalizado un documento |
document.rejected | Un documento se ha descartado (duplicado o no es factura) |
document.alert_detected | Un documento tiene incidencias que revisar |
tax_model.generated | Se ha generado un modelo fiscal |
consent.revoked | Un cliente ha retirado su autorización |
Claves: rotación y caducidad
Una clave se rota, no se sustituye a lo bruto: el gestor pulsa Rotar y nace una nueva con los mismos permisos y los mismos clientes, mientras la anterior sigue funcionando siete días. Eso te da margen para desplegar sin que el cliente se quede sin servicio.
Y una clave que lleva noventa días sin usarse se marca para caducar
con treinta de aviso: recibirás un evento consent.revoked por cada
cliente afectado, con la fecha de corte. Una integración abandonada no puede dejar
un secreto vivo sobre la cartera de una gestoría para siempre.
Los tres derechos del cliente, por API
Si integras datos fiscales de terceros, antes o después alguien te preguntará qué haces con los suyos. Los tres derechos están resueltos para que puedas responder sin pedírselo a nadie:
| Derecho | Endpoint | Qué devuelve |
|---|---|---|
| Acceso (art. 15) | GET /clients/{id}/activity |
Las últimas llamadas de tu integración sobre ese cliente, con el permiso usado y el resultado, más la fecha del consentimiento. |
| Portabilidad (art. 20) | GET /clients/{id}/export |
Sus datos, documentos, asientos y modelos en un solo fichero, siempre con la misma forma. |
| Supresión (art. 17) | DELETE /clients/{id}/data |
Revoca el consentimiento, te desautoriza sobre ese cliente y retira la IP y el navegador de tu rastro de llamadas. |
Los dos últimos van bajo su propio permiso, clientes:derechos.
No entran en clientes:leer: la portabilidad saca de una vez el histórico
que en cualquier otra ruta exige tres permisos distintos, y la supresión borra. El
gestor los marca aparte, con aviso, y el cliente tiene que haberlos autorizado.
La supresión borra lo tuyo, no lo del gestor. No toca facturas, asientos ni modelos: son documentación contable con plazos de conservación propios —cuatro años de prescripción tributaria, seis del Código de Comercio— y esa decisión es del gestor desde su panel, no de una integración. Del registro de llamadas se retiran la IP y el navegador, pero las filas se conservan: son la prueba de qué leíste y cuándo, que el art. 17.3.e) deja fuera del derecho de supresión.
Cuántas a la vez
Además del ritmo por minuto hay un tope de 8 peticiones abiertas a la vez
por clave. Si lo superas recibes un 429 con
demasiadas_simultaneas y X-CG-Rate-Limited-Reason: concurrency:
eso no se arregla esperando, se arregla bajando los hilos. Sincronizar con cincuenta
conexiones en paralelo no va más rápido; tumba la base de datos de la gestoría.
Lo que esta API no hace, y por qué
- Anular o rectificar facturas. Una anulación es un registro más en la cadena ante la AEAT y no se deshace; una rectificativa equivocada solo se corrige con otra. Se hacen desde el panel, donde hay una persona.
-
Presentar modelos ante la AEAT. Y no por prudencia comercial: la
presentación exige la contraseña del certificado del cliente, tecleada en el
momento y que no guardamos en ninguna parte. Exponerla por API significaría que
un integrador manejase esa contraseña, y eso no lo vamos a hacer. Presentar
ocurre en el panel, con una persona delante.
Lo que sí puedes: descargar el justificante de lo ya presentado —con su CSV— enGET /tax-models/{id}/receipt, que va bajo el permiso propiomodelos:presentarporque acredita el cumplimiento de un cliente ante terceros. - Escribir asientos contables. Es donde un error se propaga a los modelos fiscales y de ahí a una declaración.