• 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 antes de emitir. Si falta la sucursal o el certificado, falla el request entero, antes de crear ningún comprobante. Si falta el CAE de un tipo, falla por comprobante, dentro del array de respuesta. La sucursal la crea pymo en el alta de la empresa. El certificado y los CAE se cargan por API, con POST /v1/companies/{rut}/certs y POST /v1/companies/{rut}/cfesActiveNumbers/{code}/upload-xml. Cargar un CAE no cambia nada en DGI: registra en pymo una numeración que DGI ya autorizó. El CAE es por tipo, no por empresa: tener CAE de eFactura (111) no habilita emitir eTickets (101). Sirve uno con active: true y expireDate en el futuro, evaluado contra la hora de Uruguay. 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

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. Por eso un CFE puede volver SUCCESS en la emisión y quedar PROCESSED_REJECTED más tarde cuando DGI valida el sobre.
El flujo:
  1. POST sendCfes → el gateway asigna CAE, firma y guarda el CFE → responde DGI_CFES_RECEIVE_SUCCESS con los datos del comprobante.
  2. El gateway envía el sobre a DGI, de forma asíncrona.
  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. Un envelope con status: "SUCCESS" y message.code: "DGI_CFES_RECEIVE_SUCCESS" puede traer todos los comprobantes del lote fallidos. No hay conteo, ni bandera de éxito parcial, ni código distinto: hay que recorrer cfesIds y mirar cada entrada. Si el envelope no es SUCCESS, falló el request entero y no hay cfesIds.
Una entrada fallida tiene esta forma:
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 error de la emisión:
El rechazo de DGI (PROCESSED_REJECTED) no viene en la respuesta del POST: llega asíncrono. 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 sus reglas: 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.