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://www.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 |
POST, PUT y PATCH | Un UUID por operación. Obligatoria, salvo en las tres escrituras de webhooks, donde es opcional. Los DELETE no la usan. 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 (
500). - No guardamos lo que no dejó nada hecho y se arregla reintentando: los errores de validación, los
503y los conflictos transitorios comooperacion_en_cursooemision_no_posible. En esos casos, reintenta con la misma clave.
curl -X POST https://www.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
Hay operaciones que no caben en una petición HTTP y responden 202 en vez
de 201: subir un documento al OCR, generar los
modelos anuales (100, 180, 190, 193, 200, 202, 347, 349 y 390),
importar un extracto bancario y importar un CSV de
clientes. 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://www.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://www.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.
limit: de 1 a 100, 25 por defecto; fuera de
rango, 400 limite_no_valido. Usa el next_cursor tal cual; uno
alterado da 400 cursor_no_valido. Orden: del más reciente al más antiguo.
Facturas y documentos admiten modificado_desde para sincronizar solo los cambios.
GET https://www.copilotgestoria.com/api/public/v1/invoices?limit=50
→ { "object": "list", "data": [...], "has_more": true, "next_cursor": "abc123" }
GET https://www.copilotgestoria.com/api/public/v1/invoices?limit=50&cursor=abc123
Cada listado admite además sus propios filtros (por NIF, por fechas, por estado…). Los que acepta cada uno, con lo que significa cada parámetro, están en la especificación OpenAPI, que se genera desde las rutas reales del servidor.
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.
400: petición mal formada (JSON ilegible
—json_invalido—, cabecera ausente, parámetro con formato imposible).
422: valor de campo no válido, con el detalle en
error.errors y el nombre de campo que enviaste.
Las fechas van siempre en AAAA-MM-DD (con hora ISO opcional):
05/09/2026 se rechaza, porque según el país significa el 5 de septiembre o
el 9 de mayo, y en una factura eso no se adivina. El 401 trae
WWW-Authenticate: Bearer. Un 503, o un 500 que pide
reintentar con la misma Idempotency-Key, no ha escrito nada: repítelo con esa
clave. Cualquier otro 500 se devuelve igual durante 24 horas: comprueba el
estado antes de reintentar con otra clave.
Las advertencias que no bloquean (retención poco habitual, serie ajustada…) llegan en la
respuesta correcta, en avisos, con su codigo y su
mensaje.
Tres códigos que conviene distinguir, porque se arreglan de formas distintas:
| Código | Qué pasa | Quién lo arregla |
|---|---|---|
scope_insuficiente |
La clave no lleva ese permiso. | El gestor, marcándolo en la clave desde su panel. |
permiso_fuera_de_plan |
La clave sí lo lleva, pero el plan de la gestoría no lo incluye. El error trae scope y plan_minimo. |
El gestor, cambiando de plan. Marcarlo otra vez no sirve de nada. |
sin_consentimiento |
Ese cliente final no ha autorizado el acceso de integraciones. | El gestor, autorizando a ese cliente en la clave. |
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 (segundos). Todo 429 añade
Retry-After y X-CG-Rate-Limited-Reason con el límite alcanzado:
X-CG-Rate-Limited-Reason | Qué límite es | Cómo se arregla |
|---|---|---|
credential-rate |
El ritmo por minuto de la clave, según el plan de la gestoría. | Bajar el ritmo o espaciar las llamadas. |
client-rate |
El ritmo por minuto sobre un mismo cliente final. | Repartir las llamadas entre clientes. |
concurrency |
Más de 8 peticiones abiertas a la vez con la misma clave (demasiadas_simultaneas). |
Reducir los hilos o encadenar las llamadas. |
client-creation-rate |
Más de 120 altas de clientes por hora con la misma clave. | Para volcar una cartera, POST /clients/import. |
platform-rate |
Techo de la plataforma por clave (1.000 por minuto), para frenar bucles. | Revisar la integración. |
ip-rate |
Peticiones sin una clave bien formada desde una misma dirección (120 por minuto). | Mandar la cabecera Authorization bien formada. |
Más de 60 claves no válidas en un minuto desde una misma dirección dan 429
demasiados_intentos_fallidos; una clave válida desde esa dirección sigue funcionando.
Qué incluye cada plan
La API no es la misma en todos los planes, y esto no lo decide la ruta: lo decide el
plan de la gestoría, y se comprueba en cada petición. Si una gestoría
baja de plan, sus claves siguen existiendo pero dejan de poder hacer lo que ese plan
ya no incluye — recibirás permiso_fuera_de_plan, no un error de
autenticación.
Gratuito
Sin API.
Starter
Incluye:
- Ver los clientes autorizados, sus datos fiscales y sus actividades
- Consultar las facturas emitidas y recibidas
- Consultar los documentos digitalizados y sus datos extraídos
- Consultar los gastos y recibos sin factura
Una clave.
Professional
Todo lo de Starter, y además:
- Atender los derechos del cliente: exportar todo su histórico y borrar su rastro
- Emitir facturas nuevas en nombre del cliente
- Enviar documentos a digitalizar, corregirlos y retirarlos
- Registrar gastos y recibos (quedan en borrador hasta que el gestor confirme)
- Consultar asientos contables, plan contable y periodos
- Consultar cuentas, movimientos, importaciones de extractos y propuestas de conciliación
- Consultar las nóminas generadas
- Consultar los modelos fiscales y descargar sus ficheros
- Generar modelos fiscales nuevos
- Descargar el justificante de los modelos ya presentados ante la AEAT
- Configurar avisos automáticos hacia un servidor propio
- Registrar cobros de facturas emitidas y marcar vencimientos como cobrados
- Consultar los destinatarios y productos que el cliente usa al facturar
- Dar de alta y modificar destinatarios y productos de facturación
- Consultar las facturas recurrentes y sus próximas emisiones
- Crear, modificar, pausar, reanudar y cancelar facturas recurrentes
- Confirmar gastos en borrador (desde ese momento cuentan en el Modelo 130)
- Consultar los pagos con retención de IRPF
- Registrar pagos con retención de IRPF (quedan en borrador)
- Confirmar pagos con retención de IRPF (desde ese momento cuentan en el Modelo 111)
- Dar de alta cuentas en el plan contable del cliente
- Importar extractos bancarios
- Proponer qué factura corresponde a un movimiento (la conciliación la confirma el gestor)
- Consultar el calendario de obligaciones fiscales del cliente
- Consultar las peticiones de documentos que hace el gestor
- Consultar los webhooks y su historial de entregas
- Consultar mandatos y remesas SEPA y descargar sus ficheros
Hasta 5 claves.
Enterprise
Todo lo de Professional, y además:
- Dar de alta clientes, importarlos y corregir sus datos de contacto
- Dar de alta, modificar y eliminar las actividades económicas (epígrafes IAE) del cliente
- Registrar y revocar mandatos SEPA y descargar las remesas del cliente
- Consultar y dar de alta trabajadores y sus contratos (no genera nóminas)
- Consultar los trabajadores y sus contratos
Hasta 20 claves. Configuración avanzada de la clave: lista de IP autorizadas, caducidad elegida al crearla y avisos de cupo y de caducidad.
Cada operación de la especificación
OpenAPI lleva su x-scope (el permiso que exige) y su
x-plan (el plan más barato que lo incluye). Los dos se calculan desde el
código y desde la tabla de planes, así que no pueden contradecir a lo que hace el
servidor.
Permisos
| Permiso | Qué autoriza |
|---|---|
clientes:leer |
Ver los clientes autorizados, sus datos fiscales y sus actividades |
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 a digitalizar, corregirlos y retirarlos 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, movimientos, importaciones de extractos y propuestas de conciliación |
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 |
Descargar el justificante de los modelos ya presentados ante la AEAT Escribe datos |
webhooks:gestionar |
Configurar avisos automáticos hacia un servidor propio Escribe datos |
facturas:cobros |
Registrar cobros de facturas emitidas y marcar vencimientos como cobrados Escribe datos |
directorio:leer |
Consultar los destinatarios y productos que el cliente usa al facturar |
directorio:escribir |
Dar de alta y modificar destinatarios y productos de facturación Escribe datos |
recurrentes:leer |
Consultar las facturas recurrentes y sus próximas emisiones |
recurrentes:escribir |
Crear, modificar, pausar, reanudar y cancelar facturas recurrentes Escribe datos |
gastos:confirmar |
Confirmar gastos en borrador (desde ese momento cuentan en el Modelo 130) Escribe datos |
irpf:leer |
Consultar los pagos con retención de IRPF |
irpf:escribir |
Registrar pagos con retención de IRPF (quedan en borrador) Escribe datos |
irpf:confirmar |
Confirmar pagos con retención de IRPF (desde ese momento cuentan en el Modelo 111) Escribe datos |
contabilidad:escribir |
Dar de alta cuentas en el plan contable del cliente Escribe datos |
bancos:escribir |
Importar extractos bancarios Escribe datos |
bancos:conciliar |
Proponer qué factura corresponde a un movimiento (la conciliación la confirma el gestor) Escribe datos |
calendario:leer |
Consultar el calendario de obligaciones fiscales del cliente |
comunicacion:leer |
Consultar las peticiones de documentos que hace el gestor |
clientes:escribir |
Dar de alta clientes, importarlos y corregir sus datos de contacto Escribe datos |
actividades:escribir |
Dar de alta, modificar y eliminar las actividades económicas (epígrafes IAE) del cliente Escribe datos |
sepa:gestionar |
Registrar y revocar mandatos SEPA y descargar las remesas del cliente Escribe datos |
nominas:empleados |
Consultar y dar de alta trabajadores y sus contratos (no genera nóminas) Escribe datos |
webhooks:leer |
Consultar los webhooks y su historial de entregas |
sepa:leer |
Consultar mandatos y remesas SEPA y descargar sus ficheros |
trabajadores:leer |
Consultar los trabajadores y sus contratos |
En x-scope, a|b significa que basta con uno; una
lista exige todos. Webhooks, SEPA y trabajadores se leen con su permiso
de lectura (webhooks:leer, sepa:leer,
trabajadores:leer) o con el de gestión.
Los marcados como Escribe datos no escriben con una clave de
pruebas (cg_test_). Esa clave lee datos reales del cliente
autorizado, pero no escribe sobre ellos; sí puedes gestionar tus destinos de
webhook. Emitir una factura consume numeración y firma la cadena de VeriFactu, y
eso no se deshace ni siquiera «en pruebas». La única lectura que tampoco admite es la
exportación completa de un cliente (GET /clients/{id}/export):
saca todo su histórico, y eso no se hace con una clave de pruebas.
Para probar escrituras sin tocar datos reales existe el sandbox.
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 exactamente
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 a la mala respondes cualquier otra cosa —un2xx, pero también un400o un403— no lo activamos, y el mensaje te dice qué código recibimos. - Descartar duplicados por el
iddel evento: puede llegar el mismo más de una vez y no garantizamos el orden.
Qué se te entrega, y qué no:
-
Solo lo de los clientes que el gestor ha autorizado en esa clave y
que además han consentido el permiso de lectura del evento. Un aviso de factura
exige
facturas:leer; uno de documento,documentos:leer. Si el cliente no está, el evento existe pero a ti no te llega. - Una clave revocada o caducada deja de recibir en el acto, y lo que tuviera pendiente se da por agotado.
- Un destino que agota sus reintentos se apaga solo y se avisa al gestor. Para volver a recibir hay que verificarlo otra vez desde el panel: así una caída larga tuya no deja el destino reintentando para siempre.
-
Las entregas quedan registradas:
GET /webhooks/{id}/entregasdice qué se intentó y con qué resultado, y se puede reenviar lo que se perdió mientras tu servidor estuvo caído.
| 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 no se ha incorporado (duplicado, no es una factura o no se ha podido leer) |
document.alert_detected | Un documento tiene incidencias que revisar |
tax_model.generated | Se ha generado un modelo fiscal |
consent.revoked | Se ha retirado la autorización de un cliente para esta integración |
expense.confirmed | Se ha confirmado un gasto (ya cuenta en el Modelo 130) |
irpf_payment.confirmed | Se ha confirmado un pago con retención de IRPF (ya cuenta en el Modelo 111) |
recurring_invoice.emitted | Una factura recurrente ha emitido su factura del periodo |
bank_transaction.matched | Un movimiento bancario ha quedado conciliado con una factura |
credential.expiring | La clave de la integración se va a apagar por falta de uso |
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.
- Los webhooks pasan a la clave nueva en el mismo momento: no hay que darlos de alta otra vez ni se pierden avisos cuando caduque la anterior.
- La clave nueva hereda los permisos que el plan de la gestoría incluye hoy. Si la anterior llevaba alguno que el plan ya no incluye, no se pasa, y el gestor recibe el aviso.
- La anterior nunca dura más de lo que ya le quedaba: si tenía una fecha de caducidad más cercana que esos siete días, se respeta.
- Una clave ya caducada no se rota: hay que crear una nueva. Rotar no puede devolverle la vida a una clave muerta.
Y una clave que lleva noventa días sin usarse se marca para caducar
con treinta de aviso: recibirás un evento credential.expiring con la
fecha de corte. Ningún cliente ha retirado su autorización en ese camino, y por eso
no se emite consent.revoked. Una integración abandonada no puede dejar
un secreto vivo sobre la cartera de una gestoría para siempre.
En el plan Enterprise el gestor puede además ponerle fecha de caducidad a una
clave al crearla y limitar desde qué direcciones IP funciona.
Si tu integración llega desde fuera de esa lista recibirás un 403 con
ip_no_permitida: dile al gestor desde qué IP sales antes de desplegar.
Sandbox: entorno de pruebas
El sandbox es un entorno aparte, con su propia dirección y su propia base de datos, con clientes y facturas ficticios. Se puede emitir, cobrar, dar de alta clientes y recibir webhooks sin afectar a datos reales. Está disponible en los planes Starter, Professional y Enterprise, con todos los permisos de la API. El gestor lo crea desde API y desarrolladores › Sandbox.
Tres tipos de clave
| Clave | Dónde vale | Qué hace |
|---|---|---|
cg_live_ | Producción | Lee y escribe datos reales. |
cg_test_ | Producción | Lee datos reales; no escribe. |
cg_sbx_ | Solo el sandbox | Lee y escribe datos ficticios aislados. |
Una clave del sandbox en producción, o una de producción en el sandbox, responde 401
entorno_incorrecto antes de consultar nada. Toda respuesta del sandbox lleva la
cabecera CG-Entorno: sandbox y GET /status incluye un bloque
sandbox con la hora que el sandbox cree que es.
Primera llamada en menos de treinta minutos
- El gestor crea el sandbox y copia la clave
cg_sbx_(se muestra una sola vez). - Llama a
GET /statusconAuthorization: Bearer <clave>. - Lista los clientes con
GET /clientsy emite una factura conPOST /invoices(cabeceraX-CG-ClienteIdempotency-Key). - Da de alta un destino de webhook con
POST /webhooksy dispara un evento conPOST /sandbox/webhooks/fire.
Para no escribir las peticiones a mano, importa la especificación
(openapi.json) o la colección de Postman
(postman.json) y rellena
baseUrl, apiKey y clienteId.
Control del sandbox
GET /sandbox: ficha (reloj, reseteos que quedan hoy, topes).POST /sandbox/reset: vuelve a sembrar los datos. Conserva las claves y los destinos de webhook con su secreto. Conclienteresetea solo ese cliente. Conclientesdeclaras clientes a medida (nombre, tipo, régimen de IVA, territorio, caso…) que se añaden a los de ejemplo y se vuelven a crear en cada reseteo. Hay un máximo de reseteos al día.POST /sandbox/clock/advance(dias,horasosegundos): avanza el reloj de ESTE sandbox. Solo avanza; para volver al presente se resetea. Al avanzar se ejecutan las facturas recurrentes que tocan, los recordatorios de cobro, los vencimientos y la remisión simulada.POST /sandbox/webhooks/fire: dispara un evento con carga realista.escenariopermite provocar fallos del receptor:receptor_5xx,receptor_lentoofirma_invalida.GET /sandbox/emails: los correos que el sistema habría enviado. En el sandbox no sale ninguno.POST /sandbox/bank-accounts/{id}/movementsy/consent-expiry: movimientos bancarios a medida y caducidad del consentimiento.POST /sandbox/sepa-remittances/{id}/simulate: mueve el estado de una remesa (presentada,confirmada,rechazada_parcial,anulada). Los estados finales no se revierten.GET /sandbox/diferencias,GET /sandbox/scenarios,GET /sandbox/preparacionyGET /sandbox/configuracion.
Valores de prueba
La cabecera X-CG-Sandbox-Scenario produce siempre el mismo resultado. Un valor
desconocido responde 422 escenario_desconocido con la lista de los válidos.
- Remisión a la AEAT (al emitir):
aceptada,aceptada_con_errores(no cuenta como éxito),rechazada(código 4104),estado_desconocido(nunca se reintenta solo),reintentable,duplicadayespera. También se fijan con el NIF del receptor:00000001R(aceptada_con_errores),00000002W(rechazada),00000003A(estado_desconocido),00000004G(reintentable),00000005M(duplicada),00000006Y(espera). - Cobros (en
POST /invoices/{id}/payments):cobro_correcto,cobro_parcial,cobro_fuera_de_plazo,cobro_devueltoycobro_devuelto_con_retraso. - Plataforma:
limite_de_peticiones(429 conRetry-After),servicio_no_disponible(503) y, en escrituras,operacion_en_curso(409 transitorio, no se guarda),error_interno(500 que la idempotencia guarda) ysin_efecto(500 que no se guarda: reintentar vuelve a ejecutar). - Consentimiento: el sandbox incluye un cliente autorizado sin consentimiento (
sin_consentimiento) y otro cuyo consentimiento solo cubre la lectura de clientes (consentimiento_no_cubre).
Qué no es igual que en producción
- Remisión a la AEAT. Simulada. No se envía nada a la AEAT, ni a producción ni a su entorno de pruebas.
- Validación de NIF contra el censo. Solo dígito de control.
- Huella y código QR. Cadena propia del sandbox. El QR lleva a una página de verificación del sandbox.
- Documentos. Marca «Entorno de pruebas, sin validez fiscal» y sin mención VERI*FACTU.
- Series y numeración. Serie SBX independiente. Se reinicia con el reseteo.
- Correo. Capturado en una bandeja. No sale ningún mensaje.
- Cobros y pasarelas. Sin pasarela. El cobro se registra con importes mágicos.
- Banca. Banco de pruebas con movimientos generados. Sin conexión PSD2.
- Digitalización (OCR), modelos fiscales y extractos bancarios. No disponible: POST /documents, POST /tax-models, POST /bank-transactions/import y las propuestas de conciliación responden 501 funcion_no_disponible_en_sandbox. Las lecturas funcionan, y cada cliente con facturas trae un Modelo 303 del trimestre anterior ya generado.
- Cobros devueltos. Los escenarios cobro_devuelto y cobro_devuelto_con_retraso devuelven la factura a pendiente sin generar el asiento contrario.
- Remesas SEPA. El estado de la remesa lo mueve el integrador con POST /sandbox/sepa-remittances/{id}/simulate, sin asientos de cobro ni de devolución.
- Tiempo. Reloj simulable hacia delante. Las tareas dependientes del tiempo se ejecutan al avanzarlo.
- Cupos y límites. Contadores propios. No consumen los del plan.
- Certificados. No se custodian certificados y la subida está bloqueada. Los registros no llevan firma XAdES.
- Webhooks. Mismos eventos, firma y reintentos. Secreto propio y campo entorno.
- Conservación. Archivado tras 90 días sin actividad, con aviso a los 60 y 30 días de gracia.
Recibir webhooks en local
Los eventos del sandbox van a las URL que registres, que deben ser públicas y con HTTPS. Para
desarrollar en local, expón tu receptor con un túnel (ngrok o similar) y registra esa dirección.
No uses un túnel sin autenticar en producción. Verifica siempre la firma
CG-Signature, descarta los duplicados por id y tolera el desorden:
el sandbox usa los mismos reintentos que producción. Los eventos llevan el campo
environment: sandbox.
Registro de cambios del sandbox
- 14/10/2026. Primera versión: claves cg_sbx_, datos de ejemplo, remisión a la AEAT simulada, reloj simulable, webhooks de prueba y correo capturado.
Paso a producción
Integración (en el sandbox)
- La integración envía una versión de API fija y la registra.
- Todas las escrituras envían Idempotency-Key y los reintentos reutilizan la misma clave tras un 503 o un error de red.
- Los errores 400, 401, 403 (por permiso y por plan), 404, 409, 422, 429 con Retry-After y 503 están probados con los valores de prueba.
- Se han probado datos incompletos, no válidos y duplicados.
- El receptor de webhooks compara la firma en tiempo constante, rechaza marcas de tiempo de más de 5 minutos, responde 401 a una firma no válida, descarta duplicados por identificador y tolera el desorden.
- Los flujos de consentimiento y revocación del cliente están probados, incluido el error sin_consentimiento.
- La integración tolera la rotación de claves, con 7 días de convivencia de la anterior.
- El registro propio de llamadas no guarda datos personales innecesarios.
- Ningún identificador del sandbox se reutiliza en producción.
- Se han probado los estados de remisión simulados (aceptada, aceptada con errores, rechazada, pendiente de revisión) y su reflejo en la interfaz propia.
Solicitud
- Están documentados los datos tratados, su finalidad y el encargo de tratamiento (art. 28 RGPD).
- Constan el dominio, el contacto técnico, la política de privacidad y el contacto de incidencias.
- Los permisos solicitados están justificados uno a uno y limitados a lo que se usa.
- Las claves se guardan en un gestor de secretos, con lista de IP, y no están en el código.
- Se dispone del informe de preparación del sandbox como evidencia.
- Las claves con escritura sobre facturas, clientes o exportación de datos llevan su declaración de uso.
Activación (con datos reales)
- El gestor crea la clave de producción desde su panel y marca los clientes autorizados.
- Cada cliente final registra su consentimiento para los permisos solicitados.
- Los destinos de webhook de producción están dados de alta y verificados con la firma real.
- Las claves de producción sustituyen a las del sandbox en el almacén de secretos del servidor de la integración.
- La primera escritura se hace con un cliente piloto y con revisión humana.
- A las 24 horas se revisan las entregas y los errores, y se rota cualquier clave usada durante el desarrollo.
Del sandbox se puede exportar la configuración (destinos de webhook sin secreto y plantilla de
permisos) con GET /sandbox/configuracion. No se migran datos, claves, secretos ni
consentimientos. Un sandbox sin actividad se avisa a los 60 días, se archiva a los 90 y se
elimina tras 30 días de gracia.
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. Lo que sí puedes es leer el libro diario y dar de alta subcuentas en el plan contable.
-
Confirmar una conciliación bancaria. Puedes
proponerla (
POST /bank-transactions/{id}/match-proposals) y el gestor la confirma desde su panel. Dar una factura por cobrada mueve el IVA del trimestre y genera un asiento: esa firma es suya. - Cambiar el NIF ni el correo de un cliente. Del NIF cuelga la pertenencia de cada factura —en los documentos no hay cliente, hay NIF—, y el correo es el acceso del cliente a su portal. Los dos se corrigen desde el panel.
-
Borrar de verdad.
DELETE /documents/{id}es una baja lógica, y se rechaza si el documento tiene asiento contabilizado, IRPF declarado, cobros o conciliación. La contabilidad no se destruye desde un programa externo. - Generar nóminas ni remesas SEPA. Puedes dar de alta trabajadores, contratos y mandatos; calcular una nómina y presentar adeudos al banco lo decide el gestor.
- Autorizarse a sí misma sobre un cliente. Aunque des de alta un cliente por API, nace sin autorizar en ninguna clave: que una integración pueda concederse acceso a un titular de datos sería justo lo que el consentimiento existe para impedir.