La misma capa que la app
Cada request corre sobre la misma capa de aplicación que la interfaz: mismas validaciones DGI, misma reserva de CAE, mismo pipeline de emisión. Lo que creás por API aparece en la app.
Una API REST para crear y emitir CFE, descargar el PDF y el XML firmado, y administrar contactos, productos, inventario y cobros. Vos mandás el JSON; la firma, el CAE y el envío a DGI corren de nuestro lado.
{
"documentTypeCode": 111,
"receiverCode": "CLI-001",
"paymentType": "CREDIT",
"lines": [{
"description": "Servicio mensual",
"quantity": "1",
"unitPrice": "10000,50",
"billingIndicator": 3
}],
"autoIssue": true
}
→ 201 { "issue": { "enqueued": true } } ●Cada request corre sobre la misma capa de aplicación que la interfaz: mismas validaciones DGI, misma reserva de CAE, mismo pipeline de emisión. Lo que creás por API aparece en la app.
Clave de idempotencia en la creación y emisión idempotente por diseño: un retry devuelve lo ya creado. Diseñado para que un retry no duplique ni re-encole.
Cada CFE expone su representación imprimible y el XML firmado — el artefacto fiscal autoritativo — listos para descargar por API.
Envelope estable con códigos máquina-legibles que no cambian entre versiones, detalle de campo cuando aplica y rate limits en headers estándar.
Registrás tu empresa, generás una key y emitís sobre la misma capa que usa la app.
01
Registrás tu empresa y configurás la emisión (certificado, CAE, numeración) desde la app.
02
En /app/configuracion/api-keys (rol OWNER o ADMIN). El secreto se muestra una sola vez: guardalo en un gestor de secretos.
03
Header Authorization: Bearer afk_live_… en cada request. La key resuelve la empresa: el tenant nunca viaja en la URL ni en el body.
04
POST /documents con autoIssue: true crea el comprobante y encola la emisión. Sondeá GET /documents/{id} hasta ACCEPTED.
curl -s "https://facturar.uy/api/v1/me" \ -H "Authorization: Bearer afk_live_…" → 200 { "company": { "legalName": "Estudio Méndez SRL" }, "apiKey": { "prefix": "afk_live_a1b2c3" } }
Las keys se crean en /app/configuracion/api-keys (roles OWNER o ADMIN); el secreto se muestra una única vez y solo se persiste su hash SHA-256. La key resuelve la empresa: los ids de otra empresa devuelven 404, y una key revocada o vencida devuelve 401 con el mismo mensaje que una inexistente.
Cada operación de la referencia indica su scope. Operar sin el scope requerido devuelve 403 FORBIDDEN. Una key sin scopes (legada) tiene acceso total; GET /me no requiere ninguno.
https://facturar.uy/api/v1Verificación de la API key y su empresa.
Devuelve la empresa (id, nombre, razón social, RUC) y los metadatos de la key (nombre, prefijo). Úsalo como health check de la integración antes de operar. No requiere ningún scope: cualquier key válida puede llamarlo.
Comprobantes fiscales electrónicos (CFE): listado, creación, emisión a DGI, NC/ND y artefactos (PDF / XML firmado).
Listado de comprobantes de la empresa (emitidos y recibidos), más reciente primero. Nunca incluye plantillas de facturas recurrentes. Scope requerido: documents:read.
Crea un comprobante en una llamada, con el mismo flujo que el sistema: la API compone los pasos del wizard (borrador → datos generales → líneas → datos adicionales) sobre la misma capa de aplicación que la UI. Nunca va directo a DGI: el resultado es un DRAFT normal, visible y editable en la app. Con autoIssue: true corre además el gate de validación DGI y encola el pipeline de emisión; si el borrador está incompleto responde 422 con el requisito exacto y details.documentId (el draft persiste y puede terminarse en la app). Idempotente vía header Idempotency-Key (o idempotencyKey en el body; el header gana): un retry devuelve el documento ya creado (200, reused: true). series/number se ignoran (la numeración la gestiona la reserva de CAE). Scope requerido: documents:write (más documents:issue cuando autoIssue: true).
Detalle completo: identidad fiscal (tipo, serie, número), totales (strings decimales, nunca floats), líneas, CAE (número, serie, rango, vencimiento), tracking DGI (trackingId, submittedAt, ackSobreCode), receptor y sucursal. Tras emitir, sondear este endpoint hasta ACCEPTED / REJECTED / OBSERVED. Scope requerido: documents:read.
Dispara la emisión a DGI. READY_TO_ISSUE → encola el pipeline (202, enqueued: true). DRAFT → corre primero la misma validación de completitud que la UI (campos DGI, snapshots fiscales, reserva de numeración CAE, tipo de cambio); si el borrador está incompleto responde 422 con el requisito exacto. Si la empresa tiene activado el ajuste por redondeo automático, al validar un DRAFT se agrega/recalcula la línea «Ajuste por redondeo» (indicador de facturación 6/7) que lleva el Monto a pagar al múltiplo configurado. Ya emitido o en curso → idempotente: 200 con enqueued: false y el estado actual (el jobId de BullMQ deriva del documento, sin doble-encolado). FAILED → reintento: resetea a READY_TO_ISSUE (mismo use case que el botón «Reintentar» de la app, auditado) y re-encola (202). CANCELLED → 409. El resultado es asíncrono: sondear GET /documents/{id}. Scope requerido: documents:issue.
Crea una nota de crédito o débito a partir de un comprobante ACCEPTED/OBSERVED, pre-llenada desde el origen (cabecera, líneas, pagos, retenciones) y con la referencia Zona F al documento fuente puesta automáticamente (monto acotado al total a pagar del origen). La NC resta o anula el comprobante original (devoluciones, descuentos, corrección de errores — es la forma fiscalmente correcta de anular un CFE en Uruguay: no existe el "delete"); la ND suma o aumenta su valor (gastos extra, diferencias de precio, intereses). correctionTypeCode debe ser la NC o ND que corresponde al tipo del documento fuente (p.ej. 112 NC de e-Factura). Scope requerido: documents:write (más documents:issue cuando autoIssue: true).
Crea un recibo de cobranza a partir de una e-Factura (111) o e-Ticket (101) en estado ACCEPTED u OBSERVED, según la FAQ 4.10 de DGI (Res. Nº 303/2019): un CFE del mismo tipo con indicador de cobranza propia (A-C20 = 1), una línea «COBRANZA DE …» por cada ítem del original como monto no facturable (B-C4 = 6, IVA incluido en el importe) y la referencia en Zona F al comprobante cobrado. El borrador puede ajustarse (p.ej. cobranza parcial) antes de emitir, o emitirse directo con autoIssue. Scope requerido: documents:write (+ documents:issue si autoIssue).
Representación imprimible del comprobante, renderizada on-demand (nunca se persiste; el artefacto fiscal autoritativo es el XML firmado). Scope requerido: documents:read.
El XML firmado del comprobante — el artefacto fiscal autoritativo. Devuelve 409 CONFLICT mientras el documento exista pero aún no haya sido firmado por el pipeline. Scope requerido: documents:read.
Clientes y proveedores (receptores de comprobantes).
Listado de contactos (clientes y proveedores) de la empresa. Scope requerido: contacts:read.
Crea un contacto con el mismo schema y validaciones que el formulario de la app: dígito verificador de RUC (módulo 11), unicidad por empresa de documento y código interno, reglas de receptor. code y legalName son requeridos. Scope requerido: contacts:write.
Detalle completo del contacto, incluyendo dirección, datos de receptor y metadatos. Scope requerido: contacts:read.
Actualización COMPLETA (semántica PUT sobre el verbo PATCH): el body debe incluir el objeto entero — type, code y legalName son obligatorios y un campo omitido se limpia. Recomendado: GET del contacto, modificar los campos deseados y reenviar todo. Acepta además isActive para activar/desactivar. Scope requerido: contacts:write.
Catálogo de productos y servicios.
Listado de productos y servicios del catálogo de la empresa. Scope requerido: products:read.
Crea un producto o servicio. sku, name y price son requeridos; los flags de stock solo aplican a type: "PRODUCT". Mismas validaciones que la app (SKU único por empresa). Scope requerido: products:write.
Detalle completo del producto, incluyendo defaults DGI y configuración de stock. Scope requerido: products:read.
Actualización COMPLETA (semántica PUT sobre el verbo PATCH): el body debe incluir el objeto entero — type, sku, name y price son obligatorios y un campo omitido se limpia. Recomendado: GET del producto, modificar y reenviar todo. Acepta además isActive. Scope requerido: products:write.
Sucursales (casa central y locales) de la empresa — datos de referencia para emitir documentos.
Listado de sucursales (casa central y locales) de la empresa, la de por defecto primero. Es la forma de descubrir los branchCode que se usan al crear documentos. Scope requerido: documents:read.
Stock por sucursal, kardex y movimientos manuales. Requiere el módulo inventory en el plan.
Stock actual por producto y sucursal con estado derivado (normal, low, out, negative). Requiere el módulo inventory en el plan de la empresa (si no, 403). Scope requerido: inventory:read.
Historial de movimientos de stock (kardex): manuales, ajustes y automáticos por venta/NC. Requiere el módulo inventory en el plan. Scope requerido: inventory:read.
Crea un movimiento manual auditado. MANUAL_IN/MANUAL_OUT llevan la cantidad movida (quantity); ADJUSTMENT es un recuento físico: lleva countedQuantity (el delta se calcula server-side) y reason obligatorio. Mismas garantías que la app: SELECT FOR UPDATE sobre el balance, sin stock negativo salvo que el producto lo permita. Requiere el módulo inventory en el plan. Scope requerido: inventory:write.
Cobros de clientes, cuentas por cobrar con aging y estado de cobro de comprobantes. Metadata comercial que nunca modifica el registro fiscal. Requiere el módulo «payments» habilitado en el plan de la empresa — sin él, todos estos endpoints responden 403. Scopes: payments:read / payments:write.
Registra un cobro de cliente (total, parcial o a cuenta), opcionalmente imputado a comprobantes. contactId: null = consumidor final (exige al menos una imputación). Reglas: los documentos imputados deben ser emitidos ACCEPTED/OBSERVED, del mismo receptor y moneda, y ninguna imputación puede superar el saldo pendiente. Idempotente vía Idempotency-Key (header) o idempotencyKey (body): un reintento con la misma clave devuelve el cobro ya creado. El registro NUNCA modifica el comprobante fiscal. Scope requerido: payments:write.
Imputa el crédito disponible de un cobro a cuenta existente (monto − imputaciones activas) a comprobantes del mismo receptor y moneda. No crea un cobro nuevo ni modifica el registro fiscal. Scope requerido: payments:write.
Anulación lógica: el cobro se conserva con AuditLog y deja de contar en todos los saldos. Un cobro inexistente o ya anulado devuelve 404. Scope requerido: payments:write.
Saldos pendientes de cobro de comprobantes emitidos ACCEPTED/OBSERVED, agrupados por moneda y con antigüedad (aging): a vencer / vencido 1–30 / 31–60 / +60 días / sin vencimiento. Las notas de crédito quedan excluidas. Scope requerido: payments:read.
Estado de cobro derivado de un comprobante: PENDING/PARTIAL/PAID, total, imputado, saldo y los cobros aplicados. Scope requerido: payments:read.
POST /api/v1/documents → 201 { "reused": false } POST /api/v1/documents # retry, misma clave → 200 { "reused": true } POST /api/v1/documents/{id}/issue → 202 { "enqueued": true } POST /api/v1/documents/{id}/issue # retry → 200 { "enqueued": false } ●
El header Idempotency-Key (o idempotencyKey en el body; el header gana) hace que un retry devuelva el documento ya creado. La numeración la gestiona la reserva de CAE — series/number se ignoran al crear — y el jobId de la cola deriva del documento: sin duplicados ni doble-encolado.
{ "error": { "code": "VALIDATION_ERROR", "message": "Parámetros inválidos", "details": { … } } }Límites: 120 req/min por API key y 240 req/min por IP. Toda respuesta incluye X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset; los 429 agregan Retry-After.
Creá tu cuenta, generá tu API key y emití sobre la misma capa que usa la app. La referencia completa vive en esta página, en el spec OpenAPI y en la colección Postman.