Valida un CFE SIN emitirlo
POST /v1/cfe/validar-cfe
Corre las MISMAS validaciones que POST /v1/cfe/emitir —ítems, receptor, cuadre de IVA y totales, CAE vigente, referencias de Notas, XSD de DGI— y devuelve la lista de problemas, sin emitir nada.
Sin certificado
Funciona aunque la empresa todavía no tenga certificado de facturación electrónica: el CFE se firma en memoria con una clave de prueba (el XSD de DGI exige la firma pero sólo revisa su estructura) y la respuesta trae data.advertencias[] avisando que para emitir falta el certificado. POST /v1/cfe/emitir sí lo exige y responde 409 CERTIFICADO_NO_VIGENTE.
Por qué importa
Un CFE quema su número aunque después se anule: no hay forma de deshacer una emisión. Validar antes es la única manera de que un cuerpo mal armado no cueste un número de la serie, y es lo que conviene cablear en el formulario antes del botón de emitir.
No consume numeración, no viaja a DGI y no persiste nada.
El cuerpo es idéntico al de POST /v1/cfe/emitir. La respuesta siempre es 200: data.valido dice si pasa, y data.errores[] trae los motivos cuando no. Un 200 con valido: false no es un error de la API — es el resultado de la validación. data.advertencias[] (opcional) lista lo que no invalida el comprobante pero impediría emitirlo hoy.
Alcance requerido:
cfe:emitirocfe:leer. Si la credencial no lo tiene, la respuesta es403 API_ALCANCE_INSUFICIENTEcon la lista enerror.requerido. Las credenciales creadas antes del modelo de alcances (alcances: null) conservan acceso total.
Autorizaciones
Sección titulada «Autorizaciones »Parámetros
Sección titulada « Parámetros »Parámetros de header
Sección titulada «Parámetros de header »Ejemplo
0199d1b0-0000-7000-8000-000000000000Sólo para credenciales de cuenta proveedora (revendedor). UUID de la subcuenta de la cartera sobre la que se quiere operar: la cuenta efectiva del request pasa a ser esa.
Una credencial que no es de proveedor, o una subcuenta que no está en su cartera, recibe 403 API_CUENTA_FUERA_DE_CARTERA. Sin la cabecera, la cuenta es siempre la de la credencial.
Sucursal contra la que operar, cuando la credencial es global. Si la credencial está fijada a una sucursal y esta cabecera indica otra, la respuesta es 403 API_ACCESS_BRANCH_MISMATCH. Sin la cabecera se asume la casa central.
Punto de emisión dentro de la sucursal resuelta. Obligatorio cuando la sucursal tiene 2 o más puntos activos (400 PUNTO_EMISION_REQUERIDO si falta); con uno solo se selecciona automáticamente.
Cuerpo de la solicitud required
Sección titulada «Cuerpo de la solicitud required »El mismo cuerpo que POST /v1/cfe/emitir
object
object
A-C12 Fecha de vencimiento del pago (AAAA-MM-DD). Pensada para ventas a crédito (formaPago=2).
A-C5.1 FchValor (AAAA-MM-DD) — fecha valor, EXCLUSIVA del e-Resguardo (182): la usan los organismos del Estado cuando la fecha en que se hace efectiva la retención difiere de la fecha contable de emisión. Debe caer en el MISMO MES que fchEmis (DGI exige que coincida el AAAAMM) y su año no puede pasar de 2050. En cualquier otro tipo devuelve 422.
Emite un CFE de Cobranza (e-Recibo, A-C20 IndCobPropia=1): solo e-Ticket (101) o e-Factura (111), con todas las líneas no facturables (billingIndex 6 o 7) y la leyenda “Cobranza” en la representación impresa. Opcionalmente, referenciaId apunta a la factura a crédito cobrada (Referencia Zona F).
object
object
object
CompraID — estandar DGI para el nº de orden de compra / ID de pago del RECEPTOR. Viaja en el bloque Receptor del XML. Hasta 50 caracteres. No lo admite el e-Resguardo (182).
Venta por cuenta ajena (131/132/133 y 141/142/143): datos del mandante, es decir, por cuenta de quién se vende. Van en la zona K (Complemento Fiscal) del CFE; el RUC emisor de esa zona es siempre el de tu cuenta. Obligatorio en esos tipos, notas incluidas, y prohibido en el resto (422).
object
K-C2 TipoDocMdte — tipo de documento del mandante: 1=NIE, 2=RUC, 3=CI, 4=Otros, 5=Pasaporte, 6=DNI (Argentina, Brasil, Chile o Paraguay), 7=NIFE.
K-C3 Pais — código ISO 3166-1 alfa-2 del país emisor del documento (ej. UY).
K-C4 DocMdte — número de documento del mandante (hasta 20 caracteres). Si es RUC o CI uruguayos se valida el dígito verificador.
K-C5 NombreMdte — nombre o razón social del mandante (hasta 150 caracteres).
Metadatos personalizados de la cuenta (campos NO fiscales definidos desde el panel web, Configuración → Campos personalizados). Las claves deben coincidir con las definidas para la cuenta; valores fuera de tipo/formato son rechazados con 422 METADATA_INVALIDA. Cada valor es un texto, un número, un booleano, una lista de textos o null (que borra el valor del campo). No soportado en POST /v1/cfe/emitir-xml.
object
object
B-C20 CodRet — código de la tabla de Retención/Percepción de DGI, formato FFFF-LLL (ej. “2183-010”). Consultable en GET /cfe/retenciones. Los códigos del formulario 2181 son CRÉDITOS FISCALES (A-C127): su importe suma a mntTotCredFisc (A-C125.1), NO a mntTotRetenido (A-C125).
B-C21 Tasa (%) aplicada. Opcional.
B-C22 MntSujetoaRet — base imponible sobre la que se retiene. Debe ser mayor a 0.
B-C23 ValRetPerc — importe retenido/percibido.
B-C22.1 InfoAdicionalRet — leyenda normativa de la línea (hasta 150 caracteres). Cuando se envía, la representación impresa del e-Resguardo la imprime debajo del concepto: DGI lo exige (Formato CFE v25).
object
Marca la línea como AJUSTE (B-C4 IndFact=9): sus importes RESTAN del agregado por código (A-C128) y por lo tanto de mntTotRetenido/mntTotCredFisc. Exige referenciaId apuntando al e-Resguardo que se ajusta.
Tabla de Retención/Percepción de esta línea de detalle (Item_Resg admite hasta 5).
object
B-C20 CodRet — código de la tabla de Retención/Percepción de DGI, formato FFFF-LLL (ej. “2183-010”). Consultable en GET /cfe/retenciones. Los códigos del formulario 2181 son CRÉDITOS FISCALES (A-C127): su importe suma a mntTotCredFisc (A-C125.1), NO a mntTotRetenido (A-C125).
B-C21 Tasa (%) aplicada. Opcional.
B-C22 MntSujetoaRet — base imponible sobre la que se retiene. Debe ser mayor a 0.
B-C23 ValRetPerc — importe retenido/percibido.
B-C22.1 InfoAdicionalRet — leyenda normativa de la línea (hasta 150 caracteres). Cuando se envía, la representación impresa del e-Resguardo la imprime debajo del concepto: DGI lo exige (Formato CFE v25).
A-C125 MntTotRetenido — total RETENIDO del e-Resguardo. Solo los códigos que NO son del formulario 2181: los 2181xxx son créditos fiscales y van a A-C125.1 mntTotCredFisc. Si se omite, se deriva de la suma (con signo: las líneas de ajuste restan) de los importes no-2181; si se envía, debe coincidir con esa suma. Puede ser 0 (resguardo que solo documenta créditos fiscales) o negativo (resguardo de ajuste).
Ejemplos
e-Ticket a consumidor final (sin datos de receptor)
Caso típico de POS: venta de bajo monto a consumidor final, sin RUT ni datos del cliente. Para tipo 101 (e-Ticket), si el monto neto en UI no supera el tope (~10.000 UI), el receptor es opcional.
{ "tipo": 101, "moneda": "UYU", "formaPago": 1, "items": [ { "name": "Café americano", "quantity": 2, "price": 90, "billingIndex": 3 }, { "name": "Medialuna", "quantity": 1, "price": 60, "billingIndex": 3 } ]}e-Factura a empresa (RUT)
Emisión a una empresa identificada por RUT (tipoDocumento=2). Mezcla un ítem de catálogo (solo con id y quantity) con un ítem ad-hoc. compraId (opcional, máx. 50) es el estándar DGI para el nº de orden de compra / ID de pago del receptor: viaja en el bloque Receptor del XML del CFE.
{ "tipo": 111, "moneda": "UYU", "formaPago": 2, "cliente": { "tipoDocumento": 2, "documento": "219999830019", "razonSocial": "EMPRESA EJEMPLO S.A.", "direccion": "18 DE JULIO 1234", "ciudad": "MONTEVIDEO", "estado": "MONTEVIDEO", "pais": "UY", "email": "facturacion@empresa-ejemplo.com.uy" }, "items": [ { "id": "3f1a5c80-7b22-4e8d-9f1c-2b3a4c5d6e7f", "quantity": 3 }, { "name": "Servicio de consultoría — Septiembre", "quantity": 1, "price": 12500, "billingIndex": 3, "description": "Horas: 40 — Hora: $312.50" } ], "compraId": "OC-2026-00845", "adenda": "Forma de pago: transferencia BROU 001-1234567-89"}Respuestas
Sección titulada « Respuestas »Resultado de la validación. Mirá data.valido: el 200 significa que la validación CORRIÓ, no que el CFE sea válido.
object
Ejemplos
{ "data": { "valido": true, "errores": [] }}No es emitible
{ "data": { "valido": false, "errores": [ "No hay CAE vigente para el tipo 111 serie A", "El ítem 3 no tiene billingIndex" ] }}Válido, pero la empresa todavía no tiene certificado
{ "data": { "valido": true, "errores": [], "advertencias": [ "La empresa no tiene un certificado de facturación electrónica vigente: se validó con una firma de prueba. Para emitir hay que cargar el certificado." ] }}El cuerpo no parsea contra el esquema. Es un error de FORMA, distinto de un CFE bien formado pero no emitible, que sale 200 con valido: false.
object
object
Identificador estable del error en SCREAMING_SNAKE_CASE
Mensaje legible en español. No parsearlo: puede cambiar
object
object
Sólo en 403 API_ALCANCE_INSUFICIENTE: alcances que habilitan la operación
UUID de correlación; mismo valor que el header X-Request-ID
Ejemplos
Cédula uruguaya con dígito verificador inválido
{ "error": { "code": "CLIENT_CI_INVALID", "message": "La cédula uruguaya del cliente no es válida (dígito verificador incorrecto)" }, "requestId": "5b2c7c8a-1f6e-4d29-9a0b-7c3a8d1e2f4c"}No autenticado. La API_KEY no fue enviada, no es válida o la credencial está desactivada.
Códigos posibles: API_AUTH_HEADER_MISSING, API_AUTH_HEADER_INVALID, API_ACCESS_INVALID.
object
object
Identificador estable del error en SCREAMING_SNAKE_CASE
Mensaje legible en español. No parsearlo: puede cambiar
object
object
Sólo en 403 API_ALCANCE_INSUFICIENTE: alcances que habilitan la operación
UUID de correlación; mismo valor que el header X-Request-ID
Ejemplos
Falta header Authorization
{ "error": { "code": "API_AUTH_HEADER_MISSING", "message": "Se esperaba la cabecera Authorization con esquema Bearer" }, "requestId": "5b2c7c8a-1f6e-4d29-9a0b-7c3a8d1e2f4c"}Secret key inválida o revocada
{ "error": { "code": "API_ACCESS_INVALID", "message": "Acceso no autorizado" }, "requestId": "5b2c7c8a-1f6e-4d29-9a0b-7c3a8d1e2f4c"}Acceso prohibido. La credencial es válida pero algo del contexto lo impide. Las cinco causas se corrigen de forma distinta y ninguna se arregla reintentando:
| Code | Qué pasó | Cómo se corrige |
|---|---|---|
API_FEATURE_DISABLED | La cuenta no tiene la API habilitada en su plan | Pedir la habilitación a soporte o al proveedor |
API_ALCANCE_INSUFICIENTE | La credencial no tiene el alcance de la operación (va en error.requerido) | Editar la credencial en Integraciones → API |
API_ALCANCE_NO_DISPONIBLE | La credencial tiene el alcance pero la cuenta no lo admite: sin cobros habilitados (pagos:*), sin facturación (cfe:*) o no es cuenta proveedora (webhooks:gestionar). Va en error.details | Pedir la habilitación a soporte o al proveedor |
API_ACCESS_BRANCH_MISMATCH | x-branch-id distinto de la sucursal fijada en la credencial | Quitar la cabecera o usar otra credencial |
API_CUENTA_FUERA_DE_CARTERA | x-cuenta-id apunta a una cuenta que no es de la cartera | Revisar el UUID de la subcuenta |
object
object
Identificador estable del error en SCREAMING_SNAKE_CASE
Mensaje legible en español. No parsearlo: puede cambiar
object
object
Sólo en 403 API_ALCANCE_INSUFICIENTE: alcances que habilitan la operación
UUID de correlación; mismo valor que el header X-Request-ID
Ejemplos
Falta el alcance
{ "error": { "code": "API_ALCANCE_INSUFICIENTE", "message": "La credencial de API no tiene ninguno de los alcances necesarios para esta operación (pagos:cobrar)", "requerido": [ "pagos:cobrar" ] }, "requestId": "3f6b1c2e-9a4d-4f80-bc11-7e2d5a8f0c31"}La cuenta no admite el alcance
{ "error": { "code": "API_ALCANCE_NO_DISPONIBLE", "message": "Disponible sólo para cuentas proveedoras. La credencial tiene el permiso, pero la cuenta no puede usarlo.", "details": { "alcances": [ "webhooks:gestionar" ], "motivo": "SOLO_REVENDEDOR" } }, "requestId": "3f6b1c2e-9a4d-4f80-bc11-7e2d5a8f0c31"}La cuenta no tiene la API habilitada
{ "error": { "code": "API_FEATURE_DISABLED", "message": "El plan de esta cuenta no tiene habilitado el acceso a la API" }, "requestId": "5b2c7c8a-1f6e-4d29-9a0b-7c3a8d1e2f4c"}Header x-branch-id distinto al fijo del ApiAccess
{ "error": { "code": "API_ACCESS_BRANCH_MISMATCH", "message": "El ApiAccess está fijado a otra sucursal y no coincide con x-branch-id" }, "requestId": "5b2c7c8a-1f6e-4d29-9a0b-7c3a8d1e2f4c"}x-cuenta-id fuera de la cartera
{ "error": { "code": "API_CUENTA_FUERA_DE_CARTERA", "message": "La cuenta indicada en 'x-cuenta-id' no pertenece a la cartera de esta credencial" }, "requestId": "3f6b1c2e-9a4d-4f80-bc11-7e2d5a8f0c31"}Rate limit excedido. Esperá los segundos del header Retry-After. La cuota va por credencial: 60 req/min en cuentas estándar, 600 en cuentas proveedor.
object
object
Identificador estable del error en SCREAMING_SNAKE_CASE
Mensaje legible en español. No parsearlo: puede cambiar
object
object
Sólo en 403 API_ALCANCE_INSUFICIENTE: alcances que habilitan la operación
UUID de correlación; mismo valor que el header X-Request-ID
Ejemplos
Excediste el rate limit
{ "error": { "code": "API_RATE_LIMITED", "message": "Se superó el límite de requests para esta API key" }, "requestId": "5b2c7c8a-1f6e-4d29-9a0b-7c3a8d1e2f4c"}Headers
Sección titulada «Headers »Ejemplo
42Segundos hasta que se libera la ventana de rate limit