API

Documentación para desarrolladores

Scope: invoices

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étodoEndpointDescripciónScope
POST/invoice/Emitir una facturainvoices:write
POST/invoice/customer/Emitir una factura a un cliente registradoinvoices:write
POST/credit-note/Emitir una nota de créditoinvoices:write
POST/debit-note/Emitir una nota de débitoinvoices:write
POST/authorize/{id}/Autorizar un comprobante ante AFIP (obtener CAE)invoices:write
GET/{id}/Obtener un comprobante por su IDinvoices:read
GET/list/Listar comprobantes (con filtros y paginación)invoices:read
PUT/{id}/Actualizar el estado de un comprobanteinvoices:write
PATCH/{id}/Editar un comprobante en borradorinvoices:write
POST/cancel/{id}/Anular un comprobante (genera una nota de crédito por el total)invoices:write
DELETE/{id}/Eliminar un comprobante en borradorinvoices:write
GET/types/Tipos de comprobante soportadosinvoices:read
GET/supported-types/Tipos de comprobante soportados (detallado)invoices:read
POST/preview-type/Previsualizar qué tipo de comprobante se generaríainvoices:write
POST/validate-type/{tipo}/Validar los datos para un tipo de comprobanteinvoices:write
GET/notes/reasons/{tipo_nota}/Motivos disponibles para notas de crédito/débitoinvoices:read
POST/notes/calculate-amounts/Calcular automáticamente los importes de una notainvoices:write
POST/notes/validate-amounts/Validar los importes de una nota contra el comprobante originalinvoices:write
POST/test-environment/Probar la conexión con AFIPinvoices:write
POST/doctor/Diagnostica y repara la numeración de comprobantes contra AFIPinvoices: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 authinvoices: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:

CampoTipoDescripción
tipo_comprobanteentero (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_ventaenteroPunto de venta habilitado en AFIP.
tipo_documentoenteroTipo de documento del receptor (por ejemplo CUIT, DNI, Consumidor Final).
numero_documentostringNúmero de documento del receptor.
razon_socialstringRazón social del receptor. Por defecto "CONSUMIDOR FINAL".
domicilio_comercialstring (opcional)Domicilio del receptor.
condicion_ivastringCondición frente al IVA del receptor.
cotizacionnúmero (opcional)Cotización de la moneda. Por defecto 1.0.
itemsarrayLíneas del comprobante. Cada item con descripción, cantidad y precio unitario.
iva_itemsarrayDesglose de alícuotas de IVA. Cada elemento es un objeto con Id (entero, código de alícuota AFIP), BaseImp (número, base imponible) e Importe (número, IVA resultante). Obligatorio en facturas A y B con IVA discriminado; vacío en facturas C.
importe_totalnúmeroImporte total del comprobante.
importe_neto_gravadonúmeroImporte neto gravado.
importe_neto_no_gravadonúmeroImporte neto no gravado.
importe_exentonúmeroImporte exento.
importe_ivanúmeroImporte de IVA.
importe_tributosnúmeroImporte de otros tributos.
tributosarray (opcional)Otros tributos.
autorizarbooleanoSi es true, el comprobante se autoriza ante AFIP automáticamente tras crearse. Si es false, queda como borrador para autorizar después.

Ejemplo de body

Factura C de ejemplo, emitida a consumidor final y autorizada en el mismo paso:

{
  "punto_venta": 1,
  "tipo_documento": 99,
  "numero_documento": "0",
  "razon_social": "CONSUMIDOR FINAL",
  "condicion_iva": "Consumidor Final",
  "cotizacion": 1.0,
  "items": [
    {
      "descripcion": "Servicio de consultoría",
      "cantidad": 1,
      "precio_unitario": 15000.00
    }
  ],
  "iva_items": [],
  "importe_total": 15000.00,
  "importe_neto_gravado": 0.00,
  "importe_neto_no_gravado": 0.00,
  "importe_exento": 0.00,
  "importe_iva": 0.00,
  "importe_tributos": 0.00,
  "autorizar": true
}

Ejemplo de respuesta

La respuesta exitosa incluye el _id del comprobante, el tipo_comprobante resuelto y los importes. Si el comprobante se autorizó, también devuelve el cae y la fecha_vencimiento_cae:

{
  "success": true,
  "data": {
    "_id": "66f1a2b3c4d5e6f7a8b9c0d1",
    "tipo_comprobante": 11,
    "punto_venta": 1,
    "numero_comprobante": 87,
    "razon_social": "CONSUMIDOR FINAL",
    "condicion_iva": "Consumidor Final",
    "importe_total": 15000.00,
    "importe_neto_gravado": 0.00,
    "importe_iva": 0.00,
    "estado": "autorizado",
    "cae": "75123456789012",
    "fecha_vencimiento_cae": "2026-05-31"
  }
}

Factura A: IVA discriminado

En una factura A o B el IVA se discrimina: el campo iva_items debe llevar un elemento por cada alícuota aplicada. Cada elemento tiene tres campos: Id (entero, el código de la alícuota de IVA), BaseImp (número, el neto gravado al que se aplica esa alícuota) e Importe (número, el IVA resultante). En el ejemplo, la alícuota 5 corresponde al 21%:

{
  "tipo_comprobante": 1,
  "punto_venta": 1,
  "tipo_documento": 80,
  "numero_documento": "30709876543",
  "razon_social": "Comercial Andina S.A.",
  "condicion_iva": "Responsable Inscripto",
  "cotizacion": 1.0,
  "items": [
    {
      "descripcion": "Servicio de consultoría",
      "cantidad": 1,
      "precio_unitario": 100000.00
    }
  ],
  "iva_items": [
    {
      "Id": 5,
      "BaseImp": 100000.00,
      "Importe": 21000.00
    }
  ],
  "importe_total": 121000.00,
  "importe_neto_gravado": 100000.00,
  "importe_neto_no_gravado": 0.00,
  "importe_exento": 0.00,
  "importe_iva": 21000.00,
  "importe_tributos": 0.00,
  "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 se autoriza ante AFIP en un solo paso, devolviendo directamente el CAE y su fecha de vencimiento. Es el modo más simple cuando los datos ya están validados.

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 definitivamente.

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.

{}

La respuesta es el comprobante ya autorizado, con su cae y la fecha_vencimiento_cae asignados por AFIP:

{
  "success": true,
  "data": {
    "_id": "66f1a2b3c4d5e6f7a8b9c0d1",
    "tipo_comprobante": 1,
    "punto_venta": 1,
    "numero_comprobante": 142,
    "importe_total": 121000.00,
    "estado": "autorizado",
    "cae": "75987654321098",
    "fecha_vencimiento_cae": "2026-05-31"
  }
}

Notas de crédito y débito

Los endpoints POST /credit-note/ y POST /debit-note/ usan un body similar al de la factura, más un campo comprobantes_asociados (array) que referencia el comprobante original al que la nota se vincula.

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/: es el mismo body de una factura más el array comprobantes_asociados, donde cada elemento referencia el comprobante original por tipo_comprobante, punto_venta y numero_comprobante. El body de POST /debit-note/ es idéntico, cambiando el tipo_comprobante por el de la nota de débito:

{
  "tipo_comprobante": 3,
  "punto_venta": 1,
  "tipo_documento": 80,
  "numero_documento": "30709876543",
  "razon_social": "Comercial Andina S.A.",
  "condicion_iva": "Responsable Inscripto",
  "items": [
    {
      "descripcion": "Servicio de consultoría",
      "cantidad": 1,
      "precio_unitario": 100000.00
    }
  ],
  "iva_items": [
    {
      "Id": 5,
      "BaseImp": 100000.00,
      "Importe": 21000.00
    }
  ],
  "importe_total": 121000.00,
  "importe_neto_gravado": 100000.00,
  "importe_iva": 21000.00,
  "comprobantes_asociados": [
    {
      "tipo_comprobante": 1,
      "punto_venta": 1,
      "numero_comprobante": 142
    }
  ],
  "autorizar": true
}

La respuesta es la nota generada, con su propio _id y, si se autorizó, su cae:

{
  "success": true,
  "data": {
    "_id": "66f1a2b3c4d5e6f7a8b9c0d2",
    "tipo_comprobante": 3,
    "punto_venta": 1,
    "numero_comprobante": 17,
    "importe_total": 121000.00,
    "estado": "autorizado",
    "cae": "75112233445566",
    "fecha_vencimiento_cae": "2026-05-31"
  }
}

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"
}

La respuesta es la nota de crédito generada para revertir el comprobante original, ya autorizada y con su cae:

{
  "success": true,
  "data": {
    "_id": "66f1a2b3c4d5e6f7a8b9c0d3",
    "tipo_comprobante": 3,
    "punto_venta": 1,
    "numero_comprobante": 18,
    "importe_total": 121000.00,
    "estado": "autorizado",
    "cae": "75223344556677",
    "fecha_vencimiento_cae": "2026-05-31"
  }
}

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:

CampoTipoDescripción
environmentstring (opcional)"testing" o "production". Por defecto se usa el entorno configurado en la cuenta. Alias deprecado: "ambiente".
applybooleano (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.
tiposarray 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 emitís con tu propio CUIT y tu propio certificado.

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.

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 (no discrimina IVA)
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,
    "tipo_documento": 99,
    "numero_documento": "0",
    "razon_social": "CONSUMIDOR FINAL",
    "condicion_iva": "Consumidor Final",
    "items": [
      {"descripcion": "Producto demo", "cantidad": 1, "precio_unitario": 1000.00}
    ],
    "iva_items": [
      {"Id": 5, "BaseImp": 1000.00, "Importe": 210.00}
    ],
    "importe_total": 1210.00,
    "importe_neto_gravado": 1000.00,
    "importe_neto_no_gravado": 0.00,
    "importe_exento": 0.00,
    "importe_iva": 210.00,
    "importe_tributos": 0.00,
    "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"}'