facturaUno Docs de integradores — API v1

Emitir comprobantes por API

Una sola API para emitir facturas, notas de crédito y notas de débito electrónicas en Argentina (ARCA) y Paraguay (SIFEN / e-Kuatia). La emisión es siempre asíncrona: la API responde al instante y el comprobante se autoriza en segundos; te enterás por webhook firmado, por polling o por el link público.

1. Autenticación

Todas las llamadas a /api/v1/* llevan el header X-Api-Key. Las keys se emiten por integración y tienen una o dos audiencias:

AudienciaPermite
facturauno.issueEmitir, cancelar, suscribir webhooks — y también todo lo de lectura.
facturauno.readSolo consultar (detalle, listado, XML, catálogo).

Auth inválida o ausente responde 401/403 con cuerpo genérico. Los errores de negocio tienen siempre la forma { "error": "<código>", "detail": "…" }.

curl -s https://api.facturauno.com/api/v1/invoices \
  -H "X-Api-Key: $FACTURAUNO_KEY"

2. Emitir un comprobante issue

POST /api/v1/invoices encola la emisión y responde 202 de inmediato. Por defecto el modelo es NETO: los unitPrice van sin IVA; el impuesto se calcula por ítem según su taxRateCode. Si vendés a precio final (IVA incluido) y tenés que facturar exactamente lo cobrado, usá el modo pricesIncludeTax.

curl -s -X POST https://api.facturauno.com/api/v1/invoices \
  -H "X-Api-Key: $FACTURAUNO_KEY" -H "Content-Type: application/json" \
  -d '{
    "issuerId": "0b0e8a1e-1111-2222-3333-444455556666",
    "docClass": "INVOICE",
    "idempotencyKey": "pedido-84512",
    "currency": "PES",
    "recipient": {
      "docType": "CUIT",
      "docNumber": "30123456789",
      "name": "Cliente SRL",
      "email": "facturas@cliente.com",
      "fiscalProfile": { "condicionIVAReceptorId": 1 }
    },
    "items": [
      { "description": "Servicio de plataforma — julio", "qty": 1,
        "unitPrice": 100000.00, "taxRateCode": "IVA_21" }
    ],
    "ar": { "concepto": 2, "servicioDesde": "2026-07-01",
            "servicioHasta": "2026-07-31", "vtoPago": "2026-08-15" },
    "notifyEmail": "facturas@cliente.com",
    "webhookUrl": "https://miapp.com/hooks/facturauno"
  }'

Campos del body

CampoDetalle
issuerIdObligatorio. El emisor (empresa) por el que se factura.
emissionPointIdOpcional; si el emisor tiene un solo punto de emisión activo, se resuelve solo.
docClassINVOICE · CREDIT_NOTE · DEBIT_NOTE.
docTypeLocalOpcional. Override del tipo local (AR: CbteTipo "1"/"6"/"11"…). Sin él, el tipo se deriva de la condición fiscal emisor × receptor.
relatedInvoiceIdObligatorio en NC/ND: el comprobante que se ajusta (ver §5).
idempotencyKeyObligatorio, único por emisor (máx. 80). Reintentá con la misma key sin miedo (ver Idempotencia).
externalRefOpcional. Tu referencia (id de pedido/cobro); vuelve en consultas y webhooks.
currencyCódigo de moneda del WS: PES (pesos AR) o DOL (dólares; requiere exchangeRate).
recipientPor referencia (recipientId ya validado) o inline: docType (AR: CUIT|CUIL|DNI|CF · PY: RUC con DV|CI (cédula paraguaya)|PASAPORTE|CIE (cédula extranjera: documento de identidad de otro país — DNI argentino, RG/CPF brasileño, CI uruguaya…; nunca como CI)), docNumber, name, email, address y fiscalProfile.condicionIVAReceptorId (RG 5616 — obligatoria en AR; ids en el catálogo). En PY el número de CI/PASAPORTE/CIE se normaliza (sin puntos ni espacios; letras en mayúscula en PASAPORTE/CIE) y tiene que quedar con 1 a 20 letras, dígitos o guiones — si no, 422. recipient: null = consumidor final innominado (en PY solo facturas, y por menos de 7.000.000 guaraníes — en USD, total × exchangeRate: desde ese monto SIFEN exige identificar al receptor (D208c, rechazo 1321; no rige con py.tipoTransaccion 13, muestras médicas) y la API responde 422; las NC/ND exigen receptor identificado — ver §5).
pricesIncludeTaxOpcional, default false. Con true, items[].unitPrice es el precio FINAL (IVA incluido) y se factura exactamente lo cobrado (ver Precios con IVA incluido).
expectedTotalOpcional. El total que cobraste. Si el total calculado no es exactamente ese, responde 422 y no se crea nada. En AR vale con o sin pricesIncludeTax; en PY exige pricesIncludeTax: true (ver expectedTotal).
supersedesInvoiceIdOpcional. El comprobante REJECTED (mismo emisor, misma docClass, mismo ambiente) que este viene a reemplazar. Declararlo evita la doble factura: nadie podrá re-encolar el rechazado mientras este siga vivo (ver Reemplazar un comprobante rechazado).
items[]description, qty, unitPrice NETO (o FINAL con pricesIncludeTax), taxRateCode (AR: IVA_21|IVA_10_5|IVA_27|IVA_5|IVA_2_5|IVA_0|EXENTO|NO_GRAVADO · PY: IVA_10|IVA_5|EXENTA|EXONERADO), discountAmount opcional.
pyOpciones PY: tipoTransaccion (iTipTra 1-13), condicionVenta CONTADO|CREDITO (+plazoCredito), indicadorPresencia (iIndPres) y motivoEmision (iMotEmi de NC/ND). Moneda PYG (entera) o USD (exchangeRate obligatorio). El comprobante autorizado devuelve el CDC de 44 dígitos en authCode y el PDF es el KuDE con QR verificable en e-Kuatia.
arOpciones AR: concepto (1=Productos, 2=Servicios, 3=Ambos — 2/3 exigen servicioDesde/servicioHasta), vtoPago, y condicionIVAReceptorId que pisa la del perfil si viene.
notifyEmailOpcional. Al autorizarse, el receptor recibe un mail con el link público.
webhookUrlOpcional. URL https adicional para los webhooks de ESTE comprobante (además de tus suscripciones).

Respuesta

HTTP/1.1 202 Accepted
{ "invoiceId": "6f2b…", "status": "QUEUED", "docTypeLocal": "1",
  "publicUrl": "https://facturauno.com/c/<token>",
  "totalAmount": 121000.00, "pricesIncludeTax": false }

totalAmount y pricesIncludeTax salen del comprobante persistido: en la misma respuesta confirmás que se va a facturar lo que cobraste (en un reintento son los del comprobante original). Un canónico inválido responde 422 validation_failed con el detalle y no crea nada.

Una salvedad en Paraguay con el modelo neto (sin pricesIncludeTax): ese totalAmount es el total persistido, calculado por alícuota, y el dTotGralOpe que se firma ante SIFEN se calcula por ítem — pueden diferir en guaraníes. Si necesitás que el número de la respuesta sea exactamente el que se firma, usá pricesIncludeTax (ahí son el mismo por construcción). En Argentina el persistido es siempre el ImpTotal que va a ARCA, con o sin el flag.

Idempotencia

Repetir el POST con la misma idempotencyKey del mismo emisor devuelve 200 con el comprobante existente (mismo body que el 202, con los status y totales actuales) — no se re-emite ni se re-encola. El reintento se resuelve ANTES de validar el pedido: si la key ya existe, responde el original aunque el pedido hoy no validaría (emisor pausado, punto de emisión dado de baja, receptor editado). Un reintento nunca se convierte en un 422.

Lo que sí se compara es que sea el mismo pedido: si la key ya existe y el expectedTotal que mandás no coincide con el total del comprobante existente, el docClass es otro (p.ej. una nota de crédito con la key de la factura), el pricesIncludeTax es distinto del que tiene el comprobante existente (ausente cuenta como false: los mismos números leídos como precios finales o como netos son otro comprobante), o el supersedesInvoiceId es otro (ausente cuenta como ninguno), responde 409 idempotency_conflict con el detalle — no se crea nada y no se devuelve el original como si fuera la respuesta a ese pedido. Por eso conviene mandar expectedTotal siempre, y repetir el pedido tal cual (flag incluido): es lo que permite detectar una key reutilizada por error.

HTTP/1.1 409 Conflict
{ "error": "idempotency_conflict",
  "detail": "la idempotencyKey ya se usó para un comprobante con otro total: expectedTotal=120, total del comprobante existente=115 (invoiceId 6f2b…)" }

Precios con IVA incluido — pricesIncludeTax

Para quien vende a precio final (entradas, retail): con el modelo neto tendrías que des-ivar antes de llamar, y des-ivar y re-ivar no son operaciones inversas — una entrada de $ 100,00 da neto 82,64 + IVA 17,35 = 99,99; una de ₲ 50.000 da 50.001. Con pricesIncludeTax: true mandás lo que cobraste y el IVA se calcula por adentro: el total del comprobante es exactamente la suma de qty × unitPrice. Es opt-in: sin el flag nada cambia.

Consumidor final innominado en Argentina, precio final (Factura B):

curl -s -X POST https://api.facturauno.com/api/v1/invoices \
  -H "X-Api-Key: $FACTURAUNO_KEY" -H "Content-Type: application/json" \
  -d '{
    "issuerId": "0b0e8a1e-1111-2222-3333-444455556666",
    "docClass": "INVOICE",
    "idempotencyKey": "venta-991204",
    "externalRef": "orden-991204",
    "currency": "PES",
    "pricesIncludeTax": true,
    "expectedTotal": 115.00,
    "recipient": null,
    "items": [
      { "description": "Entrada general", "qty": 1,
        "unitPrice": 100.00, "taxRateCode": "IVA_21" },
      { "description": "Cargo por servicio", "qty": 1,
        "unitPrice": 15.00, "taxRateCode": "IVA_21" }
    ]
  }'
HTTP/1.1 202 Accepted
{ "invoiceId": "9c41…", "status": "QUEUED", "docTypeLocal": "6",
  "publicUrl": "https://facturauno.com/c/<token>",
  "totalAmount": 115.00, "pricesIncludeTax": true }

El comprobante sale por 115,00: neto gravado 95,04 + IVA 19,96.

PaísCómo se calcula
ARPor alícuota (así valida ARCA): se suman los brutos de cada taxRateCode, base = bruto ÷ (1 + tasa) redondeada a 2 decimales e IVA por diferencia (bruto − base) → el total es exacto. IVA_0/EXENTO/NO_GRAVADO: base = bruto, IVA 0. El neto por renglón (items[].lineTotal) es informativo: cada línea lleva bruto ÷ (1 + tasa) redondeado, y el residuo contra la base de la alícuota se reparte de a 0,01 por línea (primero las líneas que más perdieron al redondear), así que un renglón puede diferir 1 centavo de su propio bruto ÷ (1 + tasa) — los totales, el IVA por alícuota y lo que va a ARCA no se mueven.
PYPor ítem (así liquida SIFEN): total del ítem = qty × unitPrice, IVA = total × tasa ÷ (100 + tasa), redondeado a la moneda (PYG entero, USD 2 decimales). Una entrada de ₲ 50.000 al 10%: IVA 4.545, base 45.455, total 50.000 — el mismo número en el XML firmado y en el KuDE.

Restricciones del modo (todas 422 validation_failed):

Al consultar, los campos de siempre siguen siendo NETOS. En GET /api/v1/invoices/{id}, items[].unitPrice, discountAmount y lineTotal son netos en todos los comprobantes, con o sin el flag (con el flag, unitPrice es lineTotal ÷ qty y puede traer hasta 6 decimales: no es un precio para mostrar). Los importes IVA incluido — tal cual los mandaste — van en items[].grossUnitPrice y items[].grossLineTotal, que son null en los comprobantes del modelo neto. invoice.pricesIncludeTax dice con qué modo nació el comprobante. El PDF y la página pública muestran los importes brutos persistidos, sin re-derivarlos.

expectedTotal

Declarás cuánto cobraste; si la cuenta no da exactamente eso, el pedido rebota con los dos valores en el detalle (con toda la precisión que mandaste: un 115.00004 se muestra tal cual, no como "115") y no se crea nada. En AR sirve con y sin pricesIncludeTax (en el modelo neto es la red que atrapa la diferencia de 1 centavo antes de facturarla):

HTTP/1.1 422 Unprocessable Entity
{ "error": "validation_failed",
  "detail": "expectedTotal no coincide con el total calculado: expectedTotal=50001, total calculado=50000" }

En PY expectedTotal exige pricesIncludeTax: true. En el modelo neto el total que se guarda se calcula por alícuota y el que se firma ante SIFEN (dTotGralOpe) por ítem, y pueden diferir en guaraníes: un expectedTotal que cierra contra uno no garantiza el otro, así que en vez de prometer de más responde 422 ("expectedTotal en PY requiere pricesIncludeTax=true: …"). Con el flag, el total guardado y el firmado son el mismo número por construcción.

Reemplazar un comprobante rechazado — supersedesInvoiceId

Un REJECTED (la autoridad fiscal rechazó el comprobante) se corrige emitiendo uno nuevo, con otra idempotencyKey. Declarale a quién reemplaza con supersedesInvoiceId: así la venta queda con un solo comprobante vivo — mientras el nuevo esté en vuelo o autorizado, nadie puede re-encolar el rechazado desde el back office (responde 409), ni emitir un segundo reemplazo del mismo rechazado. Es lo que evita facturar dos veces la misma venta.

curl -s -X POST https://api.facturauno.com/api/v1/invoices \
  -H "X-Api-Key: $FACTURAUNO_KEY" -H "Content-Type: application/json" \
  -d '{
    "issuerId": "0b0e8a1e-1111-2222-3333-444455556666",
    "docClass": "INVOICE",
    "idempotencyKey": "venta-991204-r2",
    "externalRef": "orden-991204",
    "supersedesInvoiceId": "6f2b…",
    "currency": "PES", "pricesIncludeTax": true, "expectedTotal": 115.00,
    "recipient": { "docType": "DNI", "docNumber": "30111222", "name": "Ana Pérez",
                   "fiscalProfile": { "condicionIVAReceptorId": 5 } },
    "items": [ { "description": "Entrada general", "qty": 1,
                 "unitPrice": 115.00, "taxRateCode": "IVA_21" } ]
  }'
HTTP/1.1 422 Unprocessable Entity
{ "error": "validation_failed",
  "detail": "solo se puede reemplazar un comprobante REJECTED (supersedesInvoiceId está DEAD); para DEAD usá el requeue admin tras verificar contra la autoridad" }

HTTP/1.1 409 Conflict
{ "error": "conflict",
  "detail": "supersedesInvoiceId ya fue reemplazado por 9c41… (AUTHORIZED): esa venta ya tiene un comprobante vivo" }

Estados del ciclo de vida: QUEUED → SENT → AUTHORIZED | REJECTED (rechazo de la autoridad) · VERIFYING (timeout en vuelo, se verifica antes de reintentar) · DEAD (agotó reintentos) · CANCELED.

3. Consultar issue · read

Detalle (con items y eventos)

curl -s https://api.facturauno.com/api/v1/invoices/{invoiceId} \
  -H "X-Api-Key: $FACTURAUNO_KEY"

Devuelve { invoice, items, events }: estado, número, authCode (CAE/CDC) y su vencimiento (authCodeExpiresAt, fecha sin zona: "2026-09-27T00:00:00"), totales, pricesIncludeTax, supersedesInvoiceId (el rechazado que este reemplaza, o null), pdfStatus, publicUrl y el historial de transiciones (cada evento con fromStatus, toStatus, detail, actor, at — ahí queda el motivo de un rechazo aunque el comprobante se haya re-encolado después). El number aparece antes de la autorización (tentativo en AR, al firmar en PY) y es definitivo solo en AUTHORIZED. En items[], unitPrice/discountAmount/lineTotal son siempre netos; los comprobantes emitidos con pricesIncludeTax traen además grossUnitPrice y grossLineTotal (IVA incluido), que en el resto son null. Para conciliar muchos comprobantes de una vez usá el estado por lote.

Listado con filtros

curl -s "https://api.facturauno.com/api/v1/invoices?issuerId=…&status=AUTHORIZED&from=2026-07-01&page=1&pageSize=30" \
  -H "X-Api-Key: $FACTURAUNO_KEY"

Filtros: issuerId, country, status, docClass, search, from/to. Respuesta { total, items }.

XML / payload fiscal

curl -s https://api.facturauno.com/api/v1/invoices/{invoiceId}/xml \
  -H "X-Api-Key: $FACTURAUNO_KEY"

Solo con el comprobante AUTHORIZED; antes responde 409 conflict. El PDF no se descarga por la API: se sirve desde el link público cuando está materializado.

4. Estado por lote issue · read

POST /api/v1/invoices/status devuelve el estado de hasta 200 comprobantes en una sola llamada — por invoiceId, por idempotencyKey o mezclando ambos. Es la consulta para conciliar una tanda de emisiones sin hacer un GET por comprobante (y sin depender de haber guardado el invoiceId: con tu propia key de idempotencia alcanza).

curl -s -X POST https://api.facturauno.com/api/v1/invoices/status \
  -H "X-Api-Key: $FACTURAUNO_KEY" -H "Content-Type: application/json" \
  -d '{
    "issuerId": "0b0e8a1e-1111-2222-3333-444455556666",
    "invoiceIds": [ "6f2b…", "9c41…" ],
    "idempotencyKeys": [ "venta-991204", "venta-991205" ]
  }'
CampoDetalle
issuerIdOpcional si solo consultás por invoiceIds; obligatorio con idempotencyKeys (la key es única por emisor).
invoiceIdsOpcional. Lista de GUIDs.
idempotencyKeysOpcional. Las keys con las que emitiste, del emisor issuerId.
HTTP/1.1 200 OK
{ "items": [
  { "invoiceId": "9c41…", "issuerId": "0b0e…", "issuerTaxId": "30111111111",
    "idempotencyKey": "venta-991204", "externalRef": "orden-991204",
    "supersedesInvoiceId": null,
    "status": "AUTHORIZED", "environment": "Prod", "number": 16615,
    "docClass": "INVOICE", "docTypeLocal": "6",
    "authCode": "75123456789012", "authCodeExpiresAt": "2026-09-27T00:00:00",
    "netAmount": 95.04, "taxAmount": 19.96, "totalAmount": 115.00,
    "pricesIncludeTax": true,
    "publicUrl": "https://facturauno.com/c/<token>",
    "authorizedAt": "2026-09-17T14:03:22Z", "updatedAt": "2026-09-17T14:03:22Z",
    "lastDetail": null }
] }

lastDetail es el detalle del último evento hacia el estado actual del comprobante — en un REJECTED, el motivo que dio la autoridad fiscal (el vigente, si fue rechazado más de una vez); en un DEAD, el último error de la cola; null en un AUTHORIZED. Es el detalle del estado en que está hoy, no el último que hubo: si un REJECTED se re-encola desde el back office pasa a QUEUED y lastDetail pasa a "requeue manual" — el motivo del rechazo deja de verse ahí (sigue en events del detalle). supersedesInvoiceId es el rechazado que este comprobante reemplaza (null si no reemplaza a nadie).

authCode, authCodeExpiresAt y authorizedAt son null hasta la autorización. number no: en Argentina se asigna tentativo al armar el pedido, antes de enviarlo a ARCA, y en Paraguay al firmar — así que un comprobante SENT/VERIFYING ya trae número, y hasta un REJECTED puede traerlo. El número es definitivo solo en AUTHORIZED: no lo uses antes. authCodeExpiresAt es una fecha sin zona, con hora en cero ("2026-09-27T00:00:00", igual que en GET /invoices/{id}); authorizedAt/updatedAt son instantes UTC con offset. publicUrl es null si el link público fue revocado. Los items vuelven ordenados por fecha de creación — no en el orden en que los pediste: indexalos por invoiceId o idempotencyKey.

5. Notas de crédito / débito y anulación

Una NC o ND es un POST /api/v1/invoices más, con docClass: "CREDIT_NOTE" (o DEBIT_NOTE) y el relatedInvoiceId del comprobante que ajusta — obligatorio. En Paraguay la nota además exige recipient identificado (RUC, CI, PASAPORTE o CIE): SIFEN rechaza NC/ND innominadas (código 1331) y la API lo corta local con 422. La Factura referenciada también tiene que haberse emitido con receptor identificado — sobre una FE innominada SIFEN no asocia notas (haría falta el evento de nominación, hoy no soportado), así que si el comprador puede llegar a necesitar una NC, identificalo desde la factura.

curl -s -X POST https://api.facturauno.com/api/v1/invoices \
  -H "X-Api-Key: $FACTURAUNO_KEY" -H "Content-Type: application/json" \
  -d '{ "issuerId": "…", "docClass": "CREDIT_NOTE",
        "relatedInvoiceId": "6f2b…", "idempotencyKey": "nc-pedido-84512",
        "currency": "PES", "recipient": { … }, "items": [ … ] }'
En Argentina no existe la cancelación: un comprobante autorizado se anula emitiendo la nota de crédito relacionada. POST /api/v1/invoices/{id}/cancel sobre un comprobante AR responde 409 use_credit_note a propósito. En Paraguay sí existe: el mismo endpoint (body opcional {"reason": "…"}) registra el evento de cancelación ante SIFEN — solo sobre un DTE AUTHORIZED y dentro de la ventana (48 h la Factura electrónica, 168 h NC/ND, desde la aprobación). Responde 202 con el evento PENDING; cuando SIFEN lo acepta, el comprobante pasa a CANCELED y te llega el webhook invoice.canceled. Vencida la ventana, anulá con nota de crédito como en AR.

6. Webhooks firmados issue

Suscribirse

curl -s -X POST https://api.facturauno.com/api/v1/webhooks/subscriptions \
  -H "X-Api-Key: $FACTURAUNO_KEY" -H "Content-Type: application/json" \
  -d '{ "url": "https://miapp.com/hooks/facturauno", "events": "*" }'
HTTP/1.1 200 OK
{ "subscriptionId": "…", "url": "https://miapp.com/hooks/facturauno", "events": "*",
  "secret": "<base64 — SE MUESTRA UNA SOLA VEZ, guardalo>",
  "signature": { "header": "X-FacturaUno-Signature",
                 "scheme": "t=<unix>,v1=hex(HMACSHA256(secret, '<unix>.' + body))",
                 "toleranceSeconds": 300 } }

El secret viaja en claro una única vez en esta respuesta y queda cifrado de nuestro lado. events acepta * o una lista separada por comas. GET /api/v1/webhooks/subscriptions lista las tuyas (sin secretos).

Eventos

EventoCuándo
invoice.authorizedLa autoridad fiscal autorizó (hay CAE y link público descargable).
invoice.rejectedLa autoridad rechazó el comprobante.
invoice.deadLa emisión agotó los reintentos (interviene soporte/admin).

Entrega y verificación de firma

Cada entrega es un POST JSON con headers X-FacturaUno-Event y X-FacturaUno-Signature: t=<unixSeconds>,v1=<hex>. Para verificar: reconstruí "<t>." + body crudo, calculá HMAC-SHA256 con tu secret, comparalo en tiempo constante con v1 y rechazá si |ahora − t| > 300 s.

{ "eventType": "invoice.authorized", "invoiceId": "6f2b…", "issuerId": "…",
  "country": "AR", "docClass": "INVOICE", "docTypeLocal": "1", "status": "AUTHORIZED",
  "number": 1234, "authCode": "75123456789012", "totalAmount": 121000.00,
  "currency": "PES", "externalRef": "pedido-84512", "idempotencyKey": "pedido-84512",
  "publicUrl": "https://facturauno.com/c/<token>", "at": "2026-08-07T14:03:22Z" }

Respondé 2xx; ante otra cosa reintentamos con backoff (1m, 5m, 15m, 1h, 4h…) hasta 24 h y después la entrega queda DEAD.

9. Emisores de tu key issue · read

curl -s https://api.facturauno.com/api/v1/issuers \
  -H "X-Api-Key: $FACTURAUNO_KEY"

Devuelve los emisores visibles para tu API key, cada uno con sus puntos de emisión: de acá salen el issuerId (y, si el emisor tiene más de un punto activo, el emissionPointId) que pide el POST /api/v1/invoices — sin hardcodear GUIDs por ambiente. Una key acotada a emisores concretos ve solo esos; lo que no figura en esta respuesta, esa key no lo puede emitir ni consultar (emitir para otro emisor responde 403).

HTTP/1.1 200 OK
{ "items": [
  { "issuerId": "0b0e8a1e-1111-2222-3333-444455556666", "taxId": "30111111111",
    "country": "AR", "legalName": "Mi Empresa S.A.", "status": "ACTIVE",
    "emissionPoints": [
      { "emissionPointId": "78fd…", "status": "ACTIVE",
        "countryKey": { "posNumber": 4 } } ] },
  { "issuerId": "8bf0…", "taxId": "80012345-6",
    "country": "PY", "legalName": "Mi Empresa Paraguay S.A.", "status": "ACTIVE",
    "emissionPoints": [
      { "emissionPointId": "b5f5…", "status": "ACTIVE",
        "countryKey": { "establishment": "001", "expeditionPoint": "003",
                        "timbradoNumber": "12345678", "timbradoValidFrom": "2026-01-01" } } ] }
] }

Sin paginado. Se listan todos los emisores de la key, no solo los operativos: mirá status (ACTIVE | SUSPENDED | ARCHIVED; puntos: ACTIVE | DISABLED) antes de emitir. countryKey es el identificador fiscal del punto: en AR el punto de venta del WS (posNumber), en PY establecimiento + punto de expedición + timbrado. No expone domicilio, configuración fiscal, branding ni credenciales.

10. Códigos de respuesta

Los errores tienen siempre la forma { "error": "<código>", "detail": "…" } (detail es opcional; los de autenticación son genéricos a propósito).

HTTPerrorCuándo
202—Emisión aceptada y encolada (POST /invoices); cancelación PY registrada (/cancel).
200—Consultas. En POST /invoices: la idempotencyKey ya existía y se devuelve el comprobante original, sin re-emitir.
400invalid_requestPedido mal formado. Un JSON ilegible o con tipos que no corresponden (un string donde va un número, un GUID inválido) responde 400 sin cuerpo.
401unauthorizedX-Api-Key ausente, inválida o sin la audiencia que pide el endpoint.
403forbiddenLa key está acotada a otros emisores y mandaste el issuerId de uno que no es suyo (al emitir, listar o consultar por lote).
404not_foundEl comprobante no existe o es de un emisor fuera del alcance de tu key (la respuesta es idéntica: no se confirma existencia).
409idempotency_conflictLa idempotencyKey ya se usó para otro pedido: el expectedTotal no coincide con el total del comprobante existente, el docClass es distinto, el pricesIncludeTax no es el del comprobante existente, o el supersedesInvoiceId es otro. No se crea nada (ver Idempotencia).
409conflictEl estado no admite la operación: XML de un comprobante todavía no autorizado, cancelación PY fuera de ventana o repetida, o supersedesInvoiceId sobre una venta que ya tiene otro comprobante vivo (ver Reemplazar un comprobante rechazado).
409use_credit_note/cancel sobre un comprobante AR: se anula con nota de crédito (ver §5).
422validation_failedEl pedido no valida: canónico inválido, expectedTotal que no coincide con el total calculado, restricciones de pricesIncludeTax (descuento, decimales), supersedesInvoiceId que no es un REJECTED del mismo emisor/clase/ambiente, más de 200 elementos o idempotencyKeys sin issuerId en el estado por lote. El detail dice qué. No se crea nada.
429too_many_requestsRate limit (hoy, el link público /c/* por IP).
500—Error nuestro. Sale sin cuerpo (no hay un código error para esto: no asumas JSON en un 500). En la emisión reintentá con la misma idempotencyKey: si el comprobante llegó a crearse, vuelve el original con 200; si no, se crea recién ahora.
Un 202 no es una autorización. El rechazo de la autoridad fiscal (ARCA/SIFEN) no llega como código HTTP: el comprobante pasa a REJECTED y te enterás por el webhook invoice.rejected, por el estado por lote (lastDetail trae el motivo) o por el detalle.