Ir al contenido

Emisión de e-CFEndpoints y semántica

Endpoints y semántica

Ver como Markdown
POST /api/v1/ecf emitir
POST /api/v1/ecf/validate validar sin emitir
GET /api/v1/ecf/{id} estado y detalle
GET /api/v1/ecf/{id}/xml XML firmado (?rfce=true para el RFCE)
GET /api/v1/ecf/{id}/trackids trackIds registrados en la DGII
GET /api/v1/ecf/{id}/attempts historial de envíos a la DGII
GET /api/v1/ecf?... listado y búsqueda paginados
POST /api/v1/ecf/{id}/retry reencolar un envío failed, review o rejected (si la DGII no consumió el e-NCF)

Todos se autentican con el header X-API-Key (ver Autenticación rápida). El detalle de /validate está en Validar sin emitir, y el de los GET en Consultar un comprobante. Esta página cubre la semántica de POST /api/v1/ecf. El reenvío y el historial de envíos se explican en Rechazos y reintentos.

Desde Configuración puedes poner dos frenos a tu propia emisión (tu organización también puede fijarlos para todos sus contribuyentes):

Situación Respuesta
La emisión está pausada 409 Ecf.EmissionPaused, con el motivo que escribiste.
El tipo de comprobante no está en tu lista 403 Ecf.TypeNotAllowed.

En ambos casos no se consume ningún e-NCF ni queda una clave de idempotencia pendiente, así que puedes reintentar con la misma clave cuando levantes el freno. La pausa solo frena emisiones nuevas: lo que ya firmaste se sigue enviando a la DGII, y la validación y las consultas siguen funcionando.

Los planes gratuitos (Developer y Developer + Soporte) solo permiten el ambiente de pruebas. Con uno de ellos, POST /ecf en certificación o en producción responde 409 Billing.EnvironmentNotIncluded, y tampoco puedes crear una API key de esos ambientes. No se consume ningún e-NCF ni queda una clave pendiente. Para usar certificación o producción sube a un plan de pago. Lo que emitas en pruebas y en certificación no cuenta contra el cupo de tu plan: solo cuenta lo aceptado en producción.

Si tu organización fijó un tope de sobreconsumo, POST /ecf responde 409 Billing.SpendCapReached cuando el excedente del período (lo que pasa del cupo incluido en el plan) llega a ese monto. No se consume ningún e-NCF ni queda una clave pendiente. La emisión se reanuda en el período siguiente o cuando la organización sube el tope.

Desde Configuración puedes limitar tu propia emisión (tu organización también puede fijar los topes para todos sus contribuyentes):

Situación Respuesta
Alcanzaste el tope mensual de Producción 409 Ecf.MonthlyLimitReached, hasta el mes siguiente o hasta subir el tope.
Superaste tu máximo de emisiones por minuto 429 Ecf.RateLimited, con el header Retry-After en segundos.

El tope mensual solo cuenta los comprobantes de Producción; los de pruebas no cuentan. En los dos casos no se consume ningún e-NCF ni queda una clave de idempotencia pendiente, así que puedes reintentar con la misma clave. El límite del plan de tu organización sigue rigiendo por encima de tu máximo por minuto.

NovaFE atiende un número limitado de emisiones a la vez. Si envías una ráfaga (por ejemplo, 40 comprobantes de golpe), las que sobran esperan su turno y se atienden en orden, sin que tengas que hacer nada: solo tardan un poco más. Si la ráfaga es tan grande que también se llena la cola de espera, la API responde 503 con el header Retry-After (en segundos). Es un aviso de saturación, no un error de tu comprobante: no consume ningún e-NCF ni deja una clave pendiente, así que reintenta con la misma Idempotency-Key pasado ese tiempo.

Situación Respuesta
Hay demasiadas emisiones en curso 503, con Retry-After en segundos. Reintenta.

El header Idempotency-Key es obligatorio en cada POST /ecf: genera un valor único por comprobante (cualquier string, por ejemplo un UUID). Si falta, la API responde 400 Ecf.IdempotencyKeyRequired; si lo mandas en el cuerpo JSON en vez del header, responde 400 Ecf.IdempotencyKeyInBody. La razón es que un e-CF no se puede borrar: si una llamada se corta por timeout y reintentas sin clave, se emitirían dos comprobantes. Con la clave, repetir el mismo request es seguro:

Situación Qué pasa
Misma clave, mismo cuerpo 200 OK con la respuesta del comprobante original. No se emite dos veces.
Misma clave, cuerpo distinto 409 Ecf.IdempotencyKeyConflict.
Misma clave, la petición anterior todavía se está procesando 409 Ecf.RequestInProgress.

Si tu integración reintenta antes de que termine el RequestInProgress, espera unos segundos y reintenta con la misma clave.

Si la emisión falla sin crear el comprobante (por ejemplo, no hay rango de secuencia disponible), la clave se libera y puedes reintentar de inmediato, con la misma clave y el cuerpo corregido.

Si mandas internalNumber (tu propio número de factura) y ya existe un comprobante con ese mismo número para tu tenant, NovaFE no emite uno nuevo: te devuelve 200 con el comprobante existente. Es un mecanismo aparte del Idempotency-Key, pensado para cuando tu sistema reintenta una operación sin recordar qué clave de idempotencia usó la primera vez.

Una tercera capa, independiente de las dos anteriores, para el caso en que tu integración arma dos comprobantes distintos (con distinto internalNumber y distinto Idempotency-Key) que en la práctica son el mismo. Se activa por configuración de tu tenant (ecf.duplicate_detection_mode, apagada por defecto):

  • off (por defecto): no corre ningún chequeo.
  • observar: no bloquea nada. Si detecta una posible duplicación, emite el comprobante de todas formas y además dispara el webhook ecf.duplicate_suspected, para que decidas tú qué hacer.
  • bloquear: si detecta una posible duplicación, responde 409 Ecf.DuplicateSuspected en vez de emitir.

La huella compara comprador, tipo de comprobante, monto total, fecha de emisión y ambiente, contra los comprobantes emitidos en una ventana reciente (5 minutos por defecto, configurable entre 30 segundos y una hora).

POST /ecf resuelve tu emisor y ambiente desde la API key, revisa idempotencia y duplicados, asigna la secuencia, arma y calcula el comprobante, lo firma con tu certificado, y persiste y encola todo en una sola transacción antes de tocar la DGII. Desde ahí hay dos modos, según la configuración de tu cuenta:

Modo Qué hace POST /ecf Cómo conoces el resultado
Con espera (el modo por defecto) Intenta el envío dentro del propio request, durante unos 8 segundos. Si la DGII responde a tiempo, la respuesta ya trae el estado final y el bloque dgii. Si tarda más, responde con submitted o signed y un proceso de fondo termina el seguimiento. En la respuesta, o después por webhook o consultando el comprobante.
Sin espera (asíncrono) Responde de inmediato con el comprobante firmado: status: "signed" y dgii: null. El envío a la DGII ocurre en segundo plano. Siempre por webhook (ecf.submitted, ecf.accepted…) o consultando el comprobante.

El modo sin espera es útil para sistemas intermedios con muchas cajas emitiendo en paralelo, que prefieren no mantener peticiones abiertas. Se activa con el ajuste submission.wait_for_result, que puede fijar tu organización o tu contribuyente, y que NovaFE puede desactivar de forma global ante un incidente. Ver Ajustes del contribuyente.

Todo error de la API (de negocio, de validación, o inesperado) responde application/problem+json, con esta forma base:

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
"title": "El e-NCF solicitado no existe.",
"status": 404,
"traceId": "b1f6c3e2-8a2d-4e10-9c7a-4f2e6d1a9b30"
}

Con un solo error, la descripción va en title y no hay ningún otro campo con el motivo. Con más de uno a la vez, se agrega errors: una lista de { code, description } para errores de negocio, o un diccionario { campo: [mensajes] } cuando es un error de validación de formulario.

traceId identifica la petición de punta a punta y es el dato a dar si necesitas soporte. Por defecto lo genera NovaFE, pero puedes fijar el tuyo mandando el header X-Trace-Id: la respuesta lo devuelve tal cual, así correlacionas tus propios logs sin depender del valor que generó la API.