Facturación electrónica AFIP/ARCA
Emití comprobantes electrónicos AFIP/ARCA —facturas, notas de crédito y notas de débito— de forma programática desde tus propios sistemas.
Introducción
Este recurso permite emitir comprobantes electrónicos AFIP/ARCA (facturas, notas de crédito y notas de débito) de forma programática. Para usarlo, la cuenta debe tener la configuración fiscal y el certificado AFIP cargados desde el panel.
El tipo de comprobante (A, B o C) se determina según la condición fiscal del emisor y del receptor. Podés indicar el tipo de forma explícita con el campo tipo_comprobante o dejar que la API lo resuelva automáticamente.
Todos los endpoints están bajo la URL base https://api.yo-facturo.com/api/v1/bill/receipts. El scope necesario es invoices: invoices:read para consultar e invoices:write para emitir o modificar comprobantes.
Endpoints
Todos los endpoints son relativos a https://api.yo-facturo.com/api/v1/bill/receipts.
| Método | Endpoint | Descripción | Scope |
|---|---|---|---|
POST | /invoice/ | Emitir una factura | invoices:write |
POST | /invoice/customer/ | Emitir una factura a un cliente registrado | invoices:write |
POST | /credit-note/ | Emitir una nota de crédito | invoices:write |
POST | /debit-note/ | Emitir una nota de débito | invoices:write |
POST | /authorize/{id}/ | Autorizar un comprobante ante AFIP (obtener CAE) | invoices:write |
GET | /{id}/ | Obtener un comprobante por su ID | invoices:read |
GET | /list/ | Listar comprobantes (con filtros y paginación) | invoices:read |
PUT | /{id}/ | Actualizar el estado de un comprobante | invoices:write |
PATCH | /{id}/ | Editar un comprobante en borrador | invoices:write |
POST | /cancel/{id}/ | Anular un comprobante (genera una nota de crédito por el total) | invoices:write |
DELETE | /{id}/ | Eliminar un comprobante en borrador | invoices:write |
GET | /types/ | Tipos de comprobante soportados | invoices:read |
GET | /supported-types/ | Tipos de comprobante soportados (detallado) | invoices:read |
POST | /preview-type/ | Previsualizar qué tipo de comprobante se generaría | invoices:write |
POST | /validate-type/{tipo}/ | Validar los datos para un tipo de comprobante | invoices:write |
GET | /notes/reasons/{tipo_nota}/ | Motivos disponibles para notas de crédito/débito | invoices:read |
POST | /notes/calculate-amounts/ | Calcular automáticamente los importes de una nota | invoices:write |
POST | /notes/validate-amounts/ | Validar los importes de una nota contra el comprobante original | invoices:write |
POST | /test-environment/ | Probar la conexión con AFIP | invoices:write |
POST | /doctor/ | Diagnostica y repara la numeración de comprobantes contra AFIP | invoices:write |
GET | /{id}/pdf/ | Descargar el PDF del comprobante (formato A4 o ticket térmico 80mm) | invoices:read |
GET | /{id}/print/ | Obtener el HTML embebible del comprobante (CSS inline, QR como data URI) | invoices:read |
POST | /{id}/public-link/ | Generar URLs públicas firmadas (HMAC) para compartir el PDF/HTML sin auth | invoices:read |
DELETE | /{id}/pdf/cache/ | Invalidar el PDF cacheado del comprobante (forzar regeneración) | invoices:write |
Emitir una factura
Para emitir una factura usá POST /api/v1/bill/receipts/invoice/. El body es un objeto JSON con los siguientes campos:
| Campo | Tipo | Descripción |
|---|---|---|
tipo_comprobante | entero (opcional) | Código del tipo de comprobante AFIP. Si se omite, se determina automáticamente según la condición fiscal. La lista de códigos válidos se obtiene con GET /types/. |
punto_venta | entero | Punto de venta habilitado en AFIP. Obligatorio. |
concepto | entero | Obligatorio. 1 = productos, 2 = servicios, 3 = productos y servicios. |
tipo_documento | entero | Tipo de documento del receptor (por ejemplo CUIT, DNI, Consumidor Final). |
numero_documento | string | Número de documento del receptor. |
razon_social | string | Razón social del receptor. Por defecto "CONSUMIDOR FINAL". |
domicilio_comercial | string (opcional) | Domicilio del receptor. |
condicion_iva_receptor_id | entero (opcional) | Condición frente al IVA del receptor, código ARCA: 1 = Responsable Inscripto, 4 = Exento, 5 = Consumidor Final, 6 = Monotributo. Es la que define la letra del comprobante y lo que se informa a ARCA. También se acepta como receptor_condicion_iva o condicion_iva con el nombre (responsable_inscripto, monotributo, exento, consumidor_final): los tres campos valen lo mismo. Si no mandás ninguno y el receptor tiene CUIT, la API consulta el padrón de ARCA; si no puede determinarla, emite Factura B a Consumidor Final. |
cotizacion | número (opcional) | Cotización de la moneda. Por defecto 1.0. |
items | array | Líneas del comprobante. Cada item con descripcion, cantidad y precio_unitario: el precio es final, con IVA incluido (ver «Precios: finales, IVA incluido»). Opcionales por línea: iva_id (alícuota, 5 = 21% por defecto, 4 = 10,5%, 3 = 0%) y bonificacion (descuento de la línea, en importe). |
iva_items | array | Opcional: el backend arma el desglose de alícuotas desde los items. Si lo mandás, cada elemento es un objeto con Id (código de alícuota AFIP), BaseImp (base imponible) e Importe (IVA) y tiene que coincidir con los items. |
importe_total | número | Importe total del comprobante. Opcional: el backend lo calcula; si lo mandás, tiene que coincidir con los items. |
importe_neto_gravado | número | Importe neto gravado. Opcional: el backend lo calcula; si lo mandás, tiene que coincidir con los items. |
importe_neto_no_gravado | número | Importe neto no gravado. Opcional: el backend lo calcula; si lo mandás, tiene que coincidir con los items. |
importe_exento | número | Importe exento. Opcional: el backend lo calcula; si lo mandás, tiene que coincidir con los items. |
importe_iva | número | Importe de IVA. Opcional: el backend lo calcula; si lo mandás, tiene que coincidir con los items. |
importe_tributos | número | Importe de otros tributos. Opcional: el backend lo calcula; si lo mandás, tiene que coincidir con los items. |
tributos | array (opcional) | Otros tributos. |
precios_con_iva | booleano (opcional) | Con API token el default es true: los precio_unitario son finales, IVA incluido. Mandá false si tus precios son netos de IVA. |
autorizar | booleano | Si es true, el comprobante se autoriza ante AFIP automáticamente tras crearse. Si es false, queda como borrador para autorizar después. |
Precios: finales, IVA incluido
Los precio_unitario de items son precios finales, con IVA incluido — igual que en el POS y en el catálogo. El backend calcula el neto, el IVA por alícuota y el total, y la API responde esos importes: no hace falta (ni conviene) mandar importe_* ni iva_items. Una línea de 1.210 con iva_id: 5 (21%) se emite como neto 1.000 + IVA 210 = total 1.210.
Cambio del 5 de octubre de 2026. Antes, esta ruta leía los precios como netos y le sumaba el IVA encima: la misma línea de 1.210 salía en 1.464,10. Si tu integración manda precios netos de IVA, agregá "precios_con_iva": false al body y todo sigue como antes. Si tus precios ya son finales, el total del comprobante ahora coincide con lo que mandás.
Una línea puede llevar bonificacion (descuento en importe, sobre cantidad por precio) también en facturas A y B. La condición de IVA del receptor se resuelve con una sola regla, la misma que el POS: se lee de condicion_iva_receptor_id, receptor_condicion_iva o condicion_iva (cualquiera de los tres) y define tanto la letra como el dato que se informa a ARCA. Con un CUIT sin condición, la API consulta el padrón de ARCA; si el padrón no puede determinarla, emite Factura B a Consumidor Final.
Ejemplo de body
Factura C de ejemplo (emisor Monotributo), emitida a consumidor final y autorizada en el mismo paso:
{
"tipo_comprobante": 11,
"punto_venta": 1,
"concepto": 1,
"tipo_documento": 99,
"numero_documento": "0",
"razon_social": "CONSUMIDOR FINAL",
"condicion_iva_receptor_id": 5,
"items": [
{
"descripcion": "Servicio de consultoría",
"cantidad": 1,
"precio_unitario": 15000.00
}
],
"autorizar": true
}Ejemplo de respuesta
La respuesta confirma que el comprobante se creó y reservó su número: trae el id (el UUID que vas a usar en todas las rutas), el tipo_comprobante resuelto y los importes. Todavía no trae el CAE: con autorizar: true la autorización corre en segundo plano, así que el comprobante sale en pendiente y la respuesta lo avisa con authorization_pending:
{
"success": true,
"message": "Factura creada exitosamente",
"data": {
"id": "d2fefcbd-07b0-4fd4-a8ff-f8544d68601d",
"tipo_comprobante": 11,
"punto_venta": 1,
"numero_comprobante": 87,
"razon_social": "CONSUMIDOR FINAL",
"importe_total": 15000.00,
"importe_neto_gravado": 15000.00,
"importe_iva": 0.00,
"estado": "pendiente",
"cae": "",
"cae_vencimiento": null,
"authorization_pending": true
}
}Factura A: receptor Responsable Inscripto
En una factura A o B el IVA se discrimina en el comprobante, pero lo calcula el backend: mandás el precio final de cada línea con su iva_id (5 = 21%) y la respuesta trae el neto gravado, el IVA y el total. Acá, 121.000 final equivale a neto 100.000 + IVA 21.000. El receptor es Responsable Inscripto (condicion_iva_receptor_id: 1), así que la letra es A:
{
"tipo_comprobante": 1,
"punto_venta": 1,
"concepto": 1,
"tipo_documento": 80,
"numero_documento": "30710000014",
"razon_social": "Comercial Andina S.A.",
"condicion_iva_receptor_id": 1,
"items": [
{
"descripcion": "Servicio de consultoría",
"cantidad": 1,
"precio_unitario": 121000.00,
"iva_id": 5
}
],
"autorizar": true
}Flujo de emisión
Hay dos modos de emitir un comprobante, según el valor del campo autorizar:
1. Emisión directa
Enviá "autorizar": true en el body. El comprobante se crea y la autorización ante AFIP se dispara sola, sin que tengas que hacer una segunda llamada. Pero la respuesta del POST no espera a AFIP: vuelve enseguida con authorization_pending: true y el comprobante en pendiente. Para obtener el CAE volvé a leer el comprobante con GET /{id}/ hasta que el campo cae venga cargado — tarda unos segundos. Cuando AFIP lo aprueba el estado pasa a aprobado; si lo rechaza, a error o rechazado. Ojo: un 200 OK acá significa «el comprobante se creó», no «AFIP lo aprobó».
2. Borrador + autorización
Creá el comprobante con "autorizar": false. Queda en estado de borrador, sin CAE. Cuando esté listo para emitirse, llamá a POST /authorize/{id}/ para autorizarlo ante AFIP y obtener el CAE. Este modo es útil para revisar el comprobante antes de emitirlo, y es el más fácil de diagnosticar: la autorización es sincrónica y, si AFIP rechaza, te devuelve el motivo en el acto. Importante: el número de comprobante se reserva al crearlo, no al autorizarlo. Si dejás un borrador sin autorizar y seguís emitiendo, la numeración local queda adelantada respecto de AFIP y todas las emisiones siguientes fallan con el error 10016 — hasta que corras el Doctor. Lo mismo pasa con un comprobante que AFIP rechaza. Si usás este modo, autorizá o eliminá cada borrador antes de emitir el siguiente; y si ya te pasó, POST /doctor/ con apply: true resincroniza la secuencia.
El body de POST /authorize/{id}/ va vacío ({}): el entorno —producción u homologación— es configuración de la cuenta, no un parámetro del request.
{}A diferencia del POST de emisión, esta llamada sí es sincrónica: espera a AFIP y la respuesta ya trae el comprobante autorizado, con su cae y su cae_vencimiento. Si AFIP lo rechaza, devuelve 400 con el motivo — por eso este modo es el más fácil de diagnosticar:
{
"success": true,
"message": "Comprobante autorizado con CAE: 75987654321098",
"data": {
"id": "9d9fc2b8-3947-41e8-9549-6996cc4f2b43",
"tipo_comprobante": 1,
"punto_venta": 1,
"numero_comprobante": 142,
"importe_total": 121000.00,
"estado": "aprobado",
"cae": "75987654321098",
"cae_vencimiento": "2026-05-31T00:00:00+00:00"
}
}Notas de crédito y débito
Los endpoints POST /credit-note/ y POST /debit-note/ reciben el campo comprobante_asociado_id: el id (UUID) del comprobante original. Es obligatorio — sin él la API responde 400 Must provide associated receipt ID. No se referencia el original por tipo/punto de venta/número: eso es lo que la API devuelve después, en comprobantes_asociados, que ella misma completa. El comprobante original tiene que estar aprobado; si está en error o pendiente, la nota se rechaza.
Nota de crédito: cuánto acreditar. La API arma las líneas de la nota a partir del comprobante original, así que no se mandan items. Indicá uno de estos campos: lineas (devolución por renglón: un array de { "linea": 1, "cantidad": 1 }, donde linea es el índice —desde 0— del renglón del comprobante original y cantidad las unidades devueltas), importe_a_acreditar (un monto con IVA incluido, para una bonificación o un ajuste de precio) o porcentaje_aplicar (un porcentaje del comprobante, de 0 a 100). Sin ninguno, la nota anula el 100% del comprobante. La API calcula neto e IVA por alícuota de lo acreditado. Un items que no coincida con el total de la nota (por ejemplo, el importe de una devolución sin importe_total) se rechaza con 400.
Para calcular los importes de la nota podés usar POST /notes/calculate-amounts/, que los determina automáticamente, y validarlos contra el comprobante original con POST /notes/validate-amounts/. Los motivos válidos para cada tipo de nota se consultan con GET /notes/reasons/{tipo_nota}/.
Ejemplo de body para POST /credit-note/: devolución de una unidad del segundo renglón del comprobante original.
{
"comprobante_asociado_id": "9d9fc2b8-3947-41e8-9549-6996cc4f2b43",
"lineas": [
{ "linea": 1, "cantidad": 1 }
],
"motivo": "Devolución parcial de mercadería",
"autorizar": true
}Nota de débito. Lleva comprobante_asociado_id, items y el importe_total de la nota (o, en su lugar, porcentaje_aplicar sobre el comprobante original). A diferencia de la factura, los precio_unitario de una nota de débito son netos de IVA: el importe_total es el neto más el IVA (100 + 21% = 121). Sin importe_total ni porcentaje_aplicar la API responde 400 («Falta el importe de la nota»).
{
"comprobante_asociado_id": "9d9fc2b8-3947-41e8-9549-6996cc4f2b43",
"items": [
{ "descripcion": "Interés por mora", "cantidad": 1, "precio_unitario": 100.00, "iva_id": 5 }
],
"importe_total": 121.00,
"motivo": "Interés por mora",
"autorizar": true
}La respuesta es la nota generada, con su propio id y con comprobantes_asociados ya resuelto a partir del comprobante_asociado_id que mandaste. Igual que en la emisión de facturas, con autorizar: true el CAE no viene en esta respuesta: hay que releer la nota:
{
"success": true,
"data": {
"id": "c1d2dae6-5da4-437f-93a4-74dbab025e58",
"tipo_comprobante": 3,
"punto_venta": 1,
"numero_comprobante": 17,
"importe_total": 121000.00,
"estado": "pendiente",
"cae": "",
"cae_vencimiento": null,
"authorization_pending": true
}
}Anular un comprobante
POST /cancel/{id}/ anula un comprobante ya emitido: genera automáticamente una nota de crédito por el importe total del comprobante original. Es la forma correcta de revertir un comprobante que ya fue autorizado ante AFIP.
DELETE /{id}/, en cambio, solo elimina comprobantes que están en estado de borrador (no autorizados). Un comprobante autorizado no puede eliminarse, únicamente anularse.
El body de POST /cancel/{id}/ puede ir vacío ({}) o incluir un motivo de la anulación:
{
"motivo": "Comprobante emitido por error"
}Esta llamada sí espera a AFIP: la respuesta trae el id del comprobante anulado y, anidada en nota_credito, la nota ya autorizada con su cae. El comprobante original conserva su estado aprobado y su propio CAE — la reversión es la nota, no un cambio de estado:
{
"success": true,
"message": "Comprobante anulado correctamente",
"data": {
"comprobante_anulado_id": "9d9fc2b8-3947-41e8-9549-6996cc4f2b43",
"nota_credito": {
"id": "ba41a1bb-3f20-4922-b3d6-4ed5a0389037",
"tipo_comprobante": 3,
"punto_venta": 1,
"numero_comprobante": 18,
"importe_total": 121000.00,
"estado": "aprobado",
"cae": "75223344556677",
"cae_vencimiento": "2026-05-31T00:00:00+00:00"
}
}
}Consultar comprobantes
GET /list/ devuelve los comprobantes de la cuenta con soporte de filtros y paginación, mientras que GET /{id}/ obtiene un comprobante puntual por su ID. Ejemplo de listado:
curl "https://api.yo-facturo.com/api/v1/bill/receipts/list/?page=1&limit=20" \ -H "X-API-Key: TU_TOKEN"
Representación impresa — PDF, HTML y links públicos
Una vez que el comprobante tiene CAE (estado aprobado) podés descargarlo como PDF, embeberlo como HTML, o generar un link público firmado para compartirlo por email/WhatsApp sin exponer tu API token. Todos los PDFs incluyen el QR AFIP per RG 4291/2018 (escaneable desde cualquier validador). Los PDFs aprobados se cachean en el servidor; el primer hit lo genera, los siguientes vienen del cache.
PDF — dos formatos
?format=a4 (default) es la representación A4 con los totales + QR + CAE anclados al pie. ?format=ticket es la versión 80mm para impresora térmica (POS); la altura del PDF se ajusta al contenido. ?download=1 fuerza Content-Disposition: attachment.
# PDF A4 (default) — abre en el browser curl "https://api.yo-facturo.com/api/v1/bill/receipts/<COMP_ID>/pdf/" \ -H "X-API-Key: TU_TOKEN" \ --output factura.pdf # Forzar descarga en lugar de inline curl "https://api.yo-facturo.com/api/v1/bill/receipts/<COMP_ID>/pdf/?download=1" \ -H "X-API-Key: TU_TOKEN" \ --output factura.pdf # Ticket térmico (80mm × alto dinámico) para impresora POS curl "https://api.yo-facturo.com/api/v1/bill/receipts/<COMP_ID>/pdf/?format=ticket" \ -H "X-API-Key: TU_TOKEN" \ --output factura_ticket.pdf
HTML embebible
Devuelve el comprobante como página HTML autocontenida (CSS inline, QR como data URI, sin recursos externos). Útil para iframe en tu propio sistema, previa antes de imprimir, o vista en el browser sin descargar el PDF. Header X-Frame-Options: SAMEORIGIN — embed solo desde el mismo origen.
# HTML embebible (CSS inline, QR como data URI, X-Frame-Options: SAMEORIGIN) curl "https://api.yo-facturo.com/api/v1/bill/receipts/<COMP_ID>/print/" \ -H "X-API-Key: TU_TOKEN" \ > factura.html
Link público compartible
Devuelve un token firmado (HMAC-SHA256, stateless — no se persiste) y dos URLs listas para compartir: pdf_url y view_url. Quien las recibe abre el comprobante sin necesitar el API token. La firma garantiza que nadie pueda fabricar URLs para comprobantes ajenos. expires_in_seconds es opcional (mínimo 60); sin ese campo, el link no expira.
POST /api/v1/bill/receipts/<COMP_ID>/public-link/
{
"expires_in_seconds": 86400 // opcional; sin esto el link no expira
}Response:
{
"success": true,
"data": {
"token": "eyJ0Ij...._sig",
"pdf_url": "https://api.yo-facturo.com/api/v1/public/comprobantes/<TOKEN>/pdf",
"view_url": "https://api.yo-facturo.com/api/v1/public/comprobantes/<TOKEN>/view",
"expires_at": 1748390400
}
}Las URLs públicas vienen con Cache-Control: public, max-age=31536000, immutable — Cloudflare las cachea por un año por cada variante (?format=a4 y ?format=ticket se cachean como entradas separadas). El comprobante con CAE es inmutable, así que es seguro.
Invalidar el PDF cacheado
Rara vez necesario (los comprobantes con CAE son inmutables), útil si actualizamos el template del PDF y querés forzar el re-render. DELETE /{id}/pdf/cache/ sin query params borra todas las variantes; con ?format=ticket borra sólo esa. Requiere scope invoices:write.
Diagnóstico y recuperación de comprobantes
Cada comprobante reserva su número en una secuencia local al crearse el borrador. Si ese borrador nunca llega a autorizarse —por un corte de red, una validación fallida o un rechazo de AFIP— la secuencia local queda adelantada respecto del último número realmente autorizado en AFIP. A partir de ahí, toda emisión posterior falla con el error AFIP 10016.
POST /doctor/ es el endpoint que cubre ambos casos: (a) diagnostica esa desincronización de numeración —compara, por cada tipo, el último número autorizado en AFIP contra la secuencia local— y (b) recupera comprobantes que quedaron procesando o en error: consulta AFIP por cada uno, adopta el CAE si ya fue autorizado, reintenta los que fallaron y deja para revisión manual los procesando que AFIP no confirma (no se reintentan a ciegas porque podrían ser autorizaciones en curso y duplicar el comprobante).
Tiene dos modos según el campo apply: con "apply": false (el valor por defecto) solo diagnostica y no modifica nada; con "apply": true además repara las secuencias desincronizadas y recupera los comprobantes atascados que se puedan resolver automáticamente.
Todos los campos del body son opcionales:
| Campo | Tipo | Descripción |
|---|---|---|
environment | string (opcional) | "testing" o "production". Por defecto se usa el entorno configurado en la cuenta. Alias deprecado: "ambiente". |
apply | booleano (opcional) | Si es false (por defecto), solo diagnostica sin modificar nada. Si es true, además repara la numeración y recupera comprobantes atascados. Debe ser un booleano JSON (true/false) — strings como "true" son rechazadas con 400. |
tipos | array de enteros (opcional) | Códigos de tipo de comprobante a revisar. Si se omite, se revisan los habituales (Factura A, B y C). Una lista vacía [] significa "ningún tipo" (no hace fallback al default). Strings se rechazan con 400. |
Ejemplo de petición en modo solo-diagnóstico, revisando las facturas A, B y C:
POST /api/v1/bill/receipts/doctor/
{
"environment": "testing",
"apply": false,
"tipos": [1, 6, 11]
}El campo data es el reporte completo. Incluye el bloque de numeración (detail: una entrada por tipo de comprobante con el último número autorizado en AFIP, la secuencia local y si están en sincronía) y el bloque stuck_receipts con los comprobantes en procesando/error que necesitan resolución. La action de cada atascado describe qué pasó: adopted_from_afip, reauthorized, processing_unconfirmed, number_reused, not_verifiable, etc.
{
"success": true,
"data": {
"environment": "production",
"cuit": "30709876543",
"punto_venta": 1,
"mode": "diagnosis",
"types_reviewed": 3,
"out_of_sync": 1,
"repaired": 0,
"detail": [
{
"tipo_comprobante": 1,
"afip_last_authorized": 142,
"local_sequence": 142,
"in_sync": true
},
{
"tipo_comprobante": 6,
"afip_last_authorized": 318,
"local_sequence": 320,
"in_sync": false,
"suggested_action": "resync local sequence to 318"
},
{
"tipo_comprobante": 11,
"afip_last_authorized": 87,
"local_sequence": 87,
"in_sync": true
}
],
"stuck_receipts": {
"found": 2,
"recovered": 0,
"retried_ok": 0,
"still_failing": 0,
"detail": [
{
"id": "8b12ed81-99b8-4f6e-b25c-0d7e94bcbcc8",
"tipo_comprobante": 6,
"numero_comprobante": 318,
"previous_state": "procesando",
"action": "pending — send apply=true to recover it"
},
{
"id": "2cfee208-e24f-4713-8d0e-201aabfa007e",
"tipo_comprobante": 6,
"numero_comprobante": 319,
"previous_state": "error",
"action": "pending — send apply=true to recover it"
}
]
}
},
"message": "Detected 1 out-of-sync sequence(s). Send apply=true to repair them. | 2 receipt(s) left unlegalized — send apply=true to recover them."
}Entorno: producción y homologación
El entorno —producción u homologación (testing)— es configuración de la cuenta, no un parámetro de cada request. Todos los comprobantes que emitís se autorizan contra el entorno configurado en tu cuenta. Una cuenta en homologación emite contra el ambiente de pruebas de AFIP: los comprobantes obtienen un CAE de homologación y no tienen validez fiscal.
Para cambiar el entorno de tu cuenta usá POST /api/v1/bill/tax_info/afip-environment/ (scope accounting:write) con el body {"environment": "testing"} — o production. Mientras integrás y probás, mantené la cuenta en testing; pasala a production recién cuando vayas a emitir comprobantes con validez fiscal.
Para probar el flujo en homologación no necesitás cargar un CUIT ni un certificado propio: YoFacturo usa de manera transparente el certificado y CUIT de homologación de la plataforma. Vos seguís viendo tu propio CUIT en la UI y en los payloads; el intercambio para llegar a los WS de AFIP es interno. Esto vale solo en testing — en production los comprobantes salen con tu propio CUIT como emisor y los firma el certificado de la plataforma: no cargás certificado, delegás el servicio «Facturación Electrónica» al CUIT 20-44894138-9 (guía).
Para consultar en qué entorno está tu cuenta usá GET /api/v1/bill/tax_info/taxpayer/ (scope accounting:read): el campo afip_environment del taxpayer activo indica testing o production.
Las cuentas de prueba pueden estar bloqueadas en homologación: en ese caso el endpoint rechaza el cambio a producción y hay que solicitarlo al administrador. Es una protección para no emitir comprobantes reales por accidente desde una cuenta de pruebas.
Para verificar que la conexión con AFIP funciona correctamente usá POST /test-environment/, que prueba la comunicación con los servicios de AFIP del entorno configurado.
Facturación offline con CAEA
El CAEA (Código de Autorización Electrónico Anticipado) permite emitir comprobantes cuando no hay conexión en tiempo real con AFIP: en vez de pedir el CAE comprobante por comprobante, se solicita anticipadamente un código que cubre una quincena completa (días 1 a 15 —orden: 1— o del 16 al último día del mes —orden: 2—) y, una vez recuperada la conectividad, se informan a AFIP los comprobantes emitidos bajo ese código.
Para usar CAEA la cuenta necesita un punto de venta dedicado a facturación offline (distinto del punto de venta de CAE), activado desde Configuración. Sin ese punto de venta configurado, todos estos endpoints —salvo GET /pending-count/— responden 400 con el mensaje «CAEA no configurado. Active la facturación offline en Configuración». El ciclo de solicitud e informe corre de forma automática en segundo plano; estos endpoints existen para consultar el estado desde tu integración y, si hace falta, disparar manualmente una solicitud o un informe.
Todos los endpoints son relativos a https://api.yo-facturo.com/api/v1/bill/caea y usan el mismo scope invoices del resto de esta guía. Además del límite de frecuencia por cuenta, este recurso tiene su propio límite de 20 peticiones cada 60 segundos.
| Método | Endpoint | Descripción | Scope |
|---|---|---|---|
GET | /vigente/ | Obtener el CAEA vigente para el punto de venta configurado | invoices:read |
GET | / | Listar los CAEA de la cuenta (paginado) | invoices:read |
POST | /solicitar/ | Solicitar manualmente un CAEA (normalmente se pide en forma automática) | invoices:write |
POST | /{caea_id}/informar/ | Informar a AFIP los comprobantes emitidos bajo un CAEA | invoices:write |
GET | /status/ | Estado del sistema CAEA de la cuenta | invoices:read |
GET | /pending-count/ | Cantidad de CAEA pendientes de informar (para avisar antes de desactivar la facturación offline) | invoices:read |
Consultar el CAEA vigente
GET /vigente/ devuelve el CAEA activo para el punto de venta offline configurado (un CAEA cubre todos los puntos de venta del CUIT). Si no hay ninguno vigente, responde con "data": null y el mensaje «No hay CAEA vigente para este punto de venta», en vez de un error. El campo last_numbers trae, por tipo de comprobante, el último número emitido en ese punto de venta: la app offline lo usa para arrancar la numeración local sin chocar con comprobantes ya emitidos online.
curl "https://api.yo-facturo.com/api/v1/bill/caea/vigente/" \ -H "X-API-Key: TU_TOKEN"
Respuesta:
{
"success": true,
"message": "CAEA vigente encontrado",
"data": {
"id": "8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"cuit": "30709876543",
"punto_venta": 5,
"periodo": "202607",
"orden": 1,
"caea": "26075012345678",
"vigencia_desde": "20260701",
"vigencia_hasta": "20260715",
"tope_informe": "20260725",
"fecha_proceso": "20260630",
"estado": "vigente",
"comprobantes_emitidos": 12,
"last_numbers": {
"1": 142,
"6": 318,
"11": 87
}
}
}Listar los CAEA de la cuenta
GET / lista, paginado, el historial completo de CAEA solicitados (vigentes, vencidos e informados). Acepta page (default 1) y per_page (default 20, tope 50).
curl "https://api.yo-facturo.com/api/v1/bill/caea/?page=1&per_page=20" \ -H "X-API-Key: TU_TOKEN"
Respuesta:
{
"success": true,
"data": {
"items": [
{
"id": "8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"periodo": "202607",
"orden": 1,
"caea": "26075012345678",
"estado": "vigente",
"comprobantes_emitidos": 12
}
],
"total": 6,
"page": 1,
"per_page": 20
}
}Solicitar un CAEA manualmente
POST /solicitar/ es idempotente: si ya existe un CAEA para el período y orden pedidos, devuelve el existente en vez de duplicarlo. El body es opcional —periodo (formato YYYYMM) y orden (1 o 2) deben enviarse juntos; si se omiten, la API calcula automáticamente la próxima quincena. El campo ambiente que acepta el endpoint se ignora en la práctica: el entorno (producción u homologación) siempre se resuelve de la configuración de la cuenta, igual que el resto de la facturación AFIP.
{
"periodo": "202607",
"orden": 2
}Respuesta:
{
"success": true,
"message": "CAEA solicitado exitosamente para 202607-Q2",
"data": {
"id": "3a4b5c6d-7e8f-4a9b-8c1d-2e3f4a5b6c7d",
"cuit": "30709876543",
"punto_venta": 5,
"periodo": "202607",
"orden": 2,
"caea": "26075098765432",
"vigencia_desde": "20260716",
"vigencia_hasta": "20260731",
"tope_informe": "20260810",
"estado": "solicitado",
"comprobantes_emitidos": 0
}
}Informar los comprobantes emitidos
POST /{caea_id}/informar/ envía a AFIP los comprobantes emitidos bajo ese CAEA (en lotes de hasta 200 por request). El body puede ir vacío: al igual que en /solicitar/, el campo ambiente se acepta pero no tiene efecto. Si no hay comprobantes para informar, la API lo informa como «sin movimiento» ante AFIP en vez de fallar.
curl -X POST "https://api.yo-facturo.com/api/v1/bill/caea/<CAEA_ID>/informar/" \
-H "X-API-Key: TU_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'Respuesta:
{
"success": true,
"message": "12 comprobantes informados",
"data": {
"comprobantes_informados": 12
}
}Estado del sistema CAEA
GET /status/ resume de un vistazo si la cuenta puede facturar offline en este momento (puede_facturar_offline), el CAEA vigente, la quincena actual y la próxima (con un flag que indica si ya tiene CAEA solicitado), y cuántos CAEA quedan pendientes de informar. Si el punto de venta offline no está configurado, devuelve { "enabled": false, "message": "CAEA no configurado" } en vez del resumen completo.
curl "https://api.yo-facturo.com/api/v1/bill/caea/status/" \ -H "X-API-Key: TU_TOKEN"
Respuesta:
{
"success": true,
"data": {
"enabled": true,
"caea_vigente": {
"id": "8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"periodo": "202607",
"orden": 1,
"caea": "26075012345678",
"estado": "vigente"
},
"quincena_actual": { "periodo": "202607", "orden": 1 },
"proxima_quincena": { "periodo": "202607", "orden": 2, "caea_solicitado": false },
"pendientes_informe": 1,
"puede_facturar_offline": true
}
}Cantidad de CAEA pendientes de informar
GET /pending-count/ cuenta cuántos CAEA quedan en un estado que todavía requiere informarse (solicitado, vigente o vencido). El campo can_deactivate es true solo cuando ese contador es cero — es la señal que usa el panel antes de dejar desactivar la facturación offline, para no perder comprobantes sin informar.
curl "https://api.yo-facturo.com/api/v1/bill/caea/pending-count/" \ -H "X-API-Key: TU_TOKEN"
Respuesta:
{
"success": true,
"data": {
"pending_count": 1,
"can_deactivate": false
}
}Recetas con curl — flujo de prueba end-to-end
Guía paso a paso para probar todo el sistema desde cero. Reemplazá TU_TOKEN por tu API token y COMP_ID por el _id que devuelva la emisión. No necesitás CUIT ni certificado de homologación propios: la plataforma usa los suyos de manera transparente cuando la cuenta está en testing.
Paso 1 — Activar el entorno de homologación
El entorno es configuración de la cuenta, no parámetro del request. Pasala a testing antes de probar para no emitir comprobantes con validez fiscal.
# 1) Pasar la cuenta a homologación (no requiere CUIT/cert propio)
curl -X POST "https://api.yo-facturo.com/api/v1/bill/tax_info/afip-environment/" \
-H "X-API-Key: TU_TOKEN" \
-H "Content-Type: application/json" \
-d '{"environment": "testing"}'Paso 2 — Confirmar el entorno actual
Verificá que el cambio se aplicó correctamente leyendo el taxpayer activo.
# 2) Verificar en qué entorno está la cuenta curl "https://api.yo-facturo.com/api/v1/bill/tax_info/taxpayer/" \ -H "X-API-Key: TU_TOKEN" # → el campo "afip_environment" del taxpayer activo es "testing" o "production"
Paso 3 — Probar la conexión con AFIP
Antes de emitir, asegurate de que los WS de AFIP del entorno configurado respondan.
# 3) Verificar conexión con AFIP del entorno configurado
curl -X POST "https://api.yo-facturo.com/api/v1/bill/receipts/test-environment/" \
-H "X-API-Key: TU_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'Paso 4 — Emitir una Factura B a Consumidor Final
Con autorizar: true, la API obtiene el CAE en la misma llamada. Guardá el _id del response: lo vamos a usar como COMP_ID en los pasos siguientes.
# 4) Emitir Factura B a Consumidor Final (el precio es final, IVA incluido)
curl -X POST "https://api.yo-facturo.com/api/v1/bill/receipts/invoice/" \
-H "X-API-Key: TU_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"tipo_comprobante": 6,
"punto_venta": 1,
"concepto": 1,
"tipo_documento": 99,
"numero_documento": "0",
"razon_social": "CONSUMIDOR FINAL",
"condicion_iva_receptor_id": 5,
"items": [
{"descripcion": "Producto demo", "cantidad": 1, "precio_unitario": 1210.00, "iva_id": 5}
],
"autorizar": true
}'
# Guardá el "id" devuelto — lo vamos a usar como <COMP_ID> en los pasos siguientes.Paso 5 — Listar los comprobantes emitidos
# 5) Listar comprobantes emitidos curl "https://api.yo-facturo.com/api/v1/bill/receipts/list/?page=1&limit=20" \ -H "X-API-Key: TU_TOKEN"
Paso 6 — Obtener un comprobante por su ID
# 6) Obtener un comprobante puntual curl "https://api.yo-facturo.com/api/v1/bill/receipts/<COMP_ID>/" \ -H "X-API-Key: TU_TOKEN"
Paso 7 — Descargar el PDF
El PDF viene en dos formatos: a4 (default, hoja A4 con QR AFIP) y ticket (80mm para impresora térmica, alto ajustado al contenido).
# 7) Descargar el PDF (A4 o ticket térmico 80mm) curl "https://api.yo-facturo.com/api/v1/bill/receipts/<COMP_ID>/pdf/" \ -H "X-API-Key: TU_TOKEN" \ --output factura.pdf # Versión ticket térmico 80mm: curl "https://api.yo-facturo.com/api/v1/bill/receipts/<COMP_ID>/pdf/?format=ticket" \ -H "X-API-Key: TU_TOKEN" \ --output factura_ticket.pdf
Paso 8 — Generar link público compartible
Devuelve dos URLs firmadas con HMAC: pdf_url (descarga directa) y view_url (HTML embebible). Quien las abre no necesita el API token; sirven para mandar la factura por WhatsApp o email al cliente final.
# 8) Generar link público compartible (firmado HMAC, no expone el API token)
curl -X POST "https://api.yo-facturo.com/api/v1/bill/receipts/<COMP_ID>/public-link/" \
-H "X-API-Key: TU_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
# → devuelve pdf_url + view_url que se pueden mandar por WhatsApp / email.
# Para que el link expire, agregar "expires_in_seconds": 86400 (mínimo 60).Paso 9 — Anular el comprobante con una Nota de Crédito
cancel arma sola la NC por el total y la referencia al comprobante original. Solo funciona si el comprobante ya tiene CAE; si está en procesando, primero hay que resolverlo con el Doctor (paso 10).
# 9) Anular el comprobante emitiendo una Nota de Crédito por el total
curl -X POST "https://api.yo-facturo.com/api/v1/bill/receipts/cancel/<COMP_ID>/" \
-H "X-API-Key: TU_TOKEN" \
-H "Content-Type: application/json" \
-d '{"motivo": "Comprobante emitido por error"}'
# La API arma sola la NC referenciando el comprobante original.Paso 10 — Doctor: arreglar comprobantes atascados o numeración
Si quedan facturas en procesando (autorización a medias) o error (rechazo de AFIP), o si la numeración local se desincronizó (error AFIP 10016), el Doctor consulta AFIP por cada caso y adopta el CAE existente, reintenta los rechazados, y resincroniza la numeración.
# 10) Si quedan facturas en estado "procesando" o "error", correr el Doctor
# Primero en modo solo-diagnóstico (no aplica cambios):
curl -X POST "https://api.yo-facturo.com/api/v1/bill/receipts/doctor/" \
-H "X-API-Key: TU_TOKEN" \
-H "Content-Type: application/json" \
-d '{"environment": "testing", "apply": false}'
# Si el diagnóstico muestra problemas, aplicarlos:
curl -X POST "https://api.yo-facturo.com/api/v1/bill/receipts/doctor/" \
-H "X-API-Key: TU_TOKEN" \
-H "Content-Type: application/json" \
-d '{"environment": "testing", "apply": true}'
# Nota: apply DEBE ser boolean JSON (true/false), no string "true".Paso 11 — Pasar a producción cuando termines de probar
A partir de acá los comprobantes tienen validez fiscal real y la cuenta usa tu propio CUIT y certificado. Si la cuenta está bloqueada en testing, el cambio se rechaza y hay que pedirlo al administrador.
# 11) Cuando termines de probar, volver a producción (validez fiscal real)
curl -X POST "https://api.yo-facturo.com/api/v1/bill/tax_info/afip-environment/" \
-H "X-API-Key: TU_TOKEN" \
-H "Content-Type: application/json" \
-d '{"environment": "production"}'Cuentas API partner
Para plataformas que emiten facturas por cuenta de muchos CUIT. Solo está disponible para cuentas con contrato partner: cualquier otra cuenta recibe 403 con error_code: API_PARTNER_REQUIRED. Los scopes taxpayers:read y taxpayers:write son exclusivos de las cuentas partner y solo aparecen en GET /api-tokens/scopes de una cuenta partner. Las rutas viven bajo https://api.yo-facturo.com/api/v1/partner/.
Paso 1 — Registrar el CUIT del usuario final
Devuelve 201 con el id del contribuyente: es el taxpayer_id para facturar. Se aceptan guiones en el CUIT, que debe tener 11 dígitos y dígito verificador válido.
curl -X POST "https://api.yo-facturo.com/api/v1/partner/taxpayers/" \
-H "X-API-Key: TU_TOKEN" \
-H "Content-Type: application/json" \
-d '{"tax_id": "20111111112", "tax_condition": "monotributo", "punto_venta": "2", "business_name": "Cliente SA"}'Paso 2 — El usuario final delega el servicio WSFE
Desde el Administrador de Relaciones de Clave Fiscal de ARCA (grupo Web Services), el usuario final delega Facturación Electrónica (WSFE) al CUIT 20-44894138-9 (YoFacturo) y acepta la delegación.
Paso 3 — Verificar la delegación
delegation_verified responde true, false o null (ARCA no contestó: reintentá, el estado guardado no cambia). Máximo 60 por minuto.
curl -X POST "https://api.yo-facturo.com/api/v1/partner/taxpayers/TAXPAYER_ID/verify-delegation/" \ -H "X-API-Key: TU_TOKEN"
Paso 4 — Emitir con taxpayer_id
Se usa el endpoint de siempre, POST /bill/receipts/invoice/ (invoices:write), agregando taxpayer_id. La letra, el punto de venta y el CUIT emisor salen del contribuyente registrado (monotributista = Factura C). Las notas de crédito y débito no reciben taxpayer_id: toman el emisor del comprobante original.
curl -X POST "https://api.yo-facturo.com/api/v1/bill/receipts/invoice/" \
-H "X-API-Key: TU_TOKEN" \
-H "Content-Type: application/json" \
-d '{"taxpayer_id": "TAXPAYER_ID", "punto_venta": 2, "concepto": 1, "tipo_documento": 99, "numero_documento": "0", "items": [...], "autorizar": true}'Endpoints
| Método | Endpoint | Descripción | Scope |
|---|---|---|---|
POST | /partner/taxpayers/ | Registrar el CUIT de un usuario final (máx. 500 altas por hora) | taxpayers:write |
GET | /partner/taxpayers/ | Listar con cursor: limit (máx. 500), cursor, q. Seguí con next_cursor hasta que sea null | taxpayers:read |
GET | /partner/taxpayers/{id}/ | Obtener un contribuyente | taxpayers:read |
DELETE | /partner/taxpayers/{id}/ | Baja lógica: no se emiten comprobantes nuevos, los emitidos conservan su historial | taxpayers:write |
POST | /partner/taxpayers/{id}/verify-delegation/ | Verificar la delegación WSFE (máx. 60 por minuto) | taxpayers:write |
GET | /partner/usage/ | CUIT activos del período (con al menos un comprobante autorizado con CAE real), CUIT extra y estimate_ars calculado por el backend | taxpayers:read |
Errores
Los errores de cuentas partner traen error_code además del code habitual.
| HTTP | error_code | Cuándo |
|---|---|---|
| 403 | API_PARTNER_REQUIRED | La cuenta no es una cuenta API partner |
| 404 | API_PARTNER_TAXPAYER_NOT_FOUND | El taxpayer_id no existe |
| 400 | API_PARTNER_TAXPAYER_CUIT_INVALID | CUIT mal formado o con dígito verificador inválido |
| 409 | API_PARTNER_TAXPAYER_DUPLICATE | El CUIT ya está registrado y activo |
| 409 | API_PARTNER_TAXPAYER_IS_PRIMARY | Es el CUIT principal de la cuenta |
| 409 | API_PARTNER_TAXPAYER_CUIT_CLAIMED | Ese CUIT ya lo registró otra cuenta |
| 409 | API_PARTNER_NOTE_ISSUER_CLAIMED_ELSEWHERE | Nota sobre un comprobante de un CUIT dado de baja que otra cuenta registró después |
| 409 | API_PARTNER_NOTE_ISSUER_DELEGATION_MISSING | Nota sobre un CUIT dado de baja cuya delegación ARCA ya no lista: la nota queda pendiente |
Límites de uso
Las cuentas partner tienen un perfil de límites propio, compartido por todos sus tokens: 100 por segundo, 3.000 por minuto, 120.000 por hora y 2.000.000 por día. La emisión admite hasta 600 facturas y 300 notas cada 5 minutos.



