• rut: RUT emisor (12 dígitos)
  • branchOffice: número de sucursal DGI
Un solo endpoint cubre todos los tipos de CFE (eTicket, eFactura, notas de crédito/débito, exportación, contingencia): el tipo va como clave en el body.

Antes de poder emitir

Tres cosas tienen que existir del lado de pymo, y ninguna la crea el integrador por API. Si falta alguna, la emisión no falla al principio del request: falla por comprobante, dentro del array de respuesta. El CAE es por tipo, no por empresa: tener CAE de eFactura (111) no habilita emitir eTickets (101). El gateway busca uno con active: true y expireDate en el futuro, evaluado contra la hora local uruguaya. Cada emisión consume un número del rango (range.first → range.last) de forma atómica. Cuando el rango se agota el CAE se desactiva solo, y a partir de ahí ese tipo responde DGI_MISSING_CAE hasta que se cargue uno nuevo. Hay un aviso previo configurable por porcentaje de rango restante, así que no debería llegarse al final por sorpresa. DGI_MISSING_CAE es de los pocos errores recuperables sin cambiar el request: cargado el CAE, reenviar el mismo clientEmissionId emite (ver Idempotencia, sección del CAE faltante).

Qué se manda

No hay una clave cfes. Cada clave del body es el código de tipo de CFE y su valor es la lista de comprobantes de ese tipo, así que un mismo request puede llevar eFacturas y eTickets a la vez. Las únicas claves que no son un tipo son emailsToNotify y phonesToNotify; cualquier otra se ignora en silencio.
Obligatorios en cada comprobante: clientEmissionId, IdDoc, Receptor, Totales e Items con al menos una línea. La estructura completa, campo por campo, está en la Referencia de la API bajo el esquema SolicitudEmision. Los nombres de los campos son los de DGI, no los del gateway, y las reglas de cada uno son las del documento Formato CFE de DGI. Acá se documenta la estructura y lo que el gateway hace con ella; lo que no está acá se resuelve contra ese documento, no adivinando. Dos que conviene tener claros:
  • IdDoc.Serie y IdDoc.Nro sólo se mandan para los tipos que admiten serie y número propios. En el resto los asigna el gateway desde el CAE, y mandarlos no hace nada.
  • Receptor.DocRecep con dígito verificador inválido no falla en la emisión. El gateway firma y responde SUCCESS; DGI rechaza el sobre después, de forma asíncrona. Es el caso más fácil de confundir con un éxito.

Cómo responde (verificado en código)

La respuesta del POST no significa “aceptado por DGI”. El gateway crea y firma el CFE con su CAE y responde de inmediato; el envío a DGI ocurre después, de forma asíncrona (un proceso toma los CFEs y arma los sobres). Por eso un CFE puede volver SUCCESS en la emisión y quedar PROCESSED_REJECTED más tarde cuando DGI valida el sobre.
Flujo real:
  1. POST sendCfes → el gateway asigna CAE, firma y persiste el CFE → responde DGI_CFES_RECEIVE_SUCCESS con los datos del comprobante.
  2. Un proceso asíncrono envía el sobre a DGI.
  3. El estado final (PROCESSED_ACCEPTED / PROCESSED_REJECTED) se consulta por GET .../sentCfes o llega por el webhook CFE_STATUS_CHANGE.

Respuesta de éxito

message.code = DGI_CFES_RECEIVE_SUCCESS. El payload trae cfesIds, un array con un objeto por CFE creado. Campos exactos que devuelve el servicio:

Campos a persistir

Todos los de arriba, y en particular:

Lotes: éxito parcial

Un request puede llevar varios comprobantes y cada uno se resuelve por separado. El array cfesIds es heterogéneo: los que salieron traen los datos del comprobante, los que fallaron traen un error, en el mismo array y sin nada que los separe.
El estado de arriba no refleja lo de abajo. El envelope responde siempre status: "SUCCESS" y message.code: "DGI_CFES_RECEIVE_SUCCESS", aunque todos los comprobantes del lote hayan fallado. No hay conteo, ni bandera de éxito parcial, ni código distinto: hay que recorrer cfesIds y mirar cada entrada.
Una entrada fallida tiene esta forma (capturada de la suite de tests del gateway):
Cómo distinguirlas en código:
  • Una entrada con status: "FAIL" es un error. Una con serie y nro es un comprobante emitido.
  • No te fíes del orden. Para saber a cuál de tus comprobantes corresponde, usá clientEmissionId: está en receivedDataWithError.clientEmissionId en el error y en el nivel superior en el éxito.
  • receivedDataWithError.cfe trae el comprobante como lo interpretó el gateway, con los totales ya calculados. Sirve para ver qué entendió de lo que mandaste.

Errores

Códigos de emisión, del código del gateway:
El rechazo de DGI (PROCESSED_REJECTED) no viene en la respuesta del POST: llega asíncrono. Ejemplo real capturado en GET .../sentCfes, el motivo viaja en cfeHistory[].data.digestAck:

Estados del CFE

El estado vive en actualCfeStatus, y el historial completo con sus fechas en cfeHistory[]. Se consulta con GET .../sentCfes o llega por el webhook CFE_STATUS_CHANGE. Son diecisiete valores, no cuatro. La distinción que importa al escribir el polling es si el estado puede cambiar solo: si es transitorio hay que seguir consultando, si es final no.

Transitorios: seguí consultando

Finales: el comprobante ya no se mueve

FAKE_CFES_HOMOLOGATION existe para el proceso de homologación y no aparece en operación normal.
PROCESSED_REJECTED es el mismo valor para dos cosas distintas: un CFE rechazado y un CFC observado. Si trabajás con comprobantes de contingencia, el estado solo no alcanza para distinguirlos.

Corregir o anular un comprobante ya emitido

No se puede borrar ni editar un CFE emitido. Se emite otro comprobante que lo referencia: una nota de crédito para anular o descontar, una nota de débito para agregar. Ambas son comprobantes fiscales por derecho propio, consumen su propia numeración y también se informan a DGI.
Cómo se referencia el original, y las reglas que impone el gateway: La sucursal no se manda: se infiere del comprobante referenciado. El tipo de la nota tampoco: sale del tipo del original (una NC de una eFactura 111 es 112), y si ese tipo no admite notas el gateway responde que no las admite en lugar de inventar una. Cuando no se mandan items, el gateway construye la línea a partir de la primera del original, heredando IndFact, UniMed y NomItem. Si no puede inferirlos, falla en lugar de adivinar.
Verificado leyendo el código, no ejecutado. Emitir una nota de crédito requiere un CFE original aceptado por DGI, y el stack local no llega hasta ahí: el único comprobante que se envió de verdad fue rechazado. Las reglas de arriba salen del código del gateway; los mensajes de error exactos y el resultado de un caso real no están reproducidos.