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.

Versión del contrato 2026-09-05 · Especificación OpenAPI

Empezar en cinco minutos

  1. El gestor crea una clave desde su panel, en Integraciones → API y desarrolladores.
  2. Marca qué permisos lleva y sobre qué clientes puede operar.
  3. 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

CabeceraCuándoPara 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 distintos409 con idempotency_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:

EstadoQué significa
pendingAceptado, todavía no ha empezado.
runningEn marcha.
succeededListo. resultado trae el mismo recurso que devolvería la vía directa.
failedFalló. 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

PermisoQué 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 401 a 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 id del evento: puede llegar el mismo más de una vez y no garantizamos el orden.
EventoCuándo
invoice.emittedSe ha emitido una factura
invoice.verifactu_sentLa factura se ha enviado a la AEAT
invoice.verifactu_acceptedLa AEAT ha aceptado la factura
invoice.verifactu_accepted_with_errorsLa AEAT la ha registrado CON INCIDENCIAS (no es un éxito)
invoice.verifactu_rejectedLa AEAT ha rechazado la factura
invoice.verifactu_needs_reviewEstado desconocido tras el envío: lo revisa el gestor
invoice.annulledSe ha anulado una factura
invoice.payment_recordedSe ha registrado un cobro
document.processedSe ha digitalizado un documento
document.rejectedUn documento se ha descartado (duplicado o no es factura)
document.alert_detectedUn documento tiene incidencias que revisar
tax_model.generatedSe ha generado un modelo fiscal
consent.revokedUn 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:

DerechoEndpointQué 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— en GET /tax-models/{id}/receipt, que va bajo el permiso propio modelos:presentar porque 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.