Guías5 de 11

Cómo se arma una integración

El quickstart muestra que se puede emitir en cuatro pedidos. Esta guía es lo otro: el recorrido entero de un producto que factura a sus clientes, en orden, con la decisión que hay que tomar en cada paso.

Todas las respuestas de esta guía se ejecutaron contra la API.

Antes de escribir código: el reparto

Hay una división que conviene entender primero porque determina la arquitectura de tu integración, y no se deduce leyendo la lista de endpoints.

QuéCómo se hacePor qué
Alta del contribuyentePanel, con sesión y 2FAEs administrar la cuenta, no facturar
Carga del certificadoPanel, con sesión y 2FAEs la credencial más sensible del sistema
Registrar webhooksPanel, con sesión y 2FAConfigura a dónde se manda información
Emitir, consultar, anular, PDFAPI keyEs el trabajo de tu producto

No es una convención: está impuesto. Una API key no puede dar de alta un contribuyente ni cargar un certificado, tenga los permisos que tenga quien la creó:

{
  "statusCode": 403,
  "errorCode": "API_KEY_NOT_ALLOWED_HERE",
  "message": "Esta operación no se puede hacer con API key: requiere una sesión de usuario",
  "details": "Esta operacion requiere una sesion de usuario"
}

Ésa es la respuesta real de POST /v1/taxpayers y de POST /v1/arca/credentials con una clave válida y con todos los scopes.

La consecuencia de diseño: el alta de cada CUIT que vas a facturar es un paso manual, una vez por CUIT. Si tu producto factura en nombre de muchos contribuyentes, planificá ese alta como parte del onboarding de cada cliente —no como algo que tu backend resuelve solo—. Si facturás siempre con tu propio CUIT, lo hacés una vez y no lo volvés a tocar.

1 · El contribuyente

Se da de alta desde el panel. Los datos son los que van impresos en el comprobante, así que salen de la constancia de inscripción del CUIT, no de lo que uno recuerda.

{
  "cuit": "20437136697",
  "legalName": "Tu Empresa SRL",
  "vatStatus": "Responsable Inscripto",
  "salePoint": 1,
  "environment": "HOMOLOGACION",
  "commercialAddress": "Av. Siempreviva 742, CABA",
  "grossIncomeTax": "901-123456-01",
  "activityStartDate": "2020-03-01"
}

Las tres decisiones de este paso:

  • vatStatus decide qué letras vas a poder emitir, y no se cambia después sin consecuencias. Un Responsable Inscripto emite A y B; un Monotributista o Exento, sólo C. Ver comprobantes A, B y C.
  • salePoint tiene que ser un punto de venta dado de alta en ARCA para webservice, que no es el mismo alta que la del portal. Ver qué es un punto de venta.
  • environment es HOMOLOGACION o PRODUCCION, y el ambiente del contribuyente tiene que coincidir con el modo de la clave que uses. Empezá en homologación: da CAE real.

2 · El certificado

También desde el panel: el .crt que te dio ARCA y su clave privada. Emissa los guarda cifrados y los usa para firmar cada pedido; la clave privada no vuelve a salir.

La decisión: la credencial es por contribuyente y por ambiente. El certificado de homologación y el productivo son dos cargas distintas sobre el mismo CUIT, y cargar uno no reemplaza al otro.

Al consultar las credenciales, la API devuelve sólo metadatos —sujeto, emisor, número de serie, huella, vigencia y estado—. Ni el certificado ni la clave privada vuelven a salir. Y revocar una credencial no la borra: el rastro de qué certificado estuvo activo y hasta cuándo es lo que permite reconstruir con qué se firmó cada comprobante.

Si intentás emitir sin haber cargado la credencial, la respuesta lo dice:

{
  "statusCode": 400,
  "errorCode": "CREDENTIALS_REQUIRED",
  "message": "Carga credenciales ARCA antes de autorizar"
}

3 · La API key

Acá se toman las dos decisiones que más se arrepienten después.

El modo. test opera contra homologación, live contra producción. La respuesta trae X-Emissa-Mode en cada pedido, así que logueá esa cabecera desde el primer día: es la forma de darte cuenta de que estás en el ambiente equivocado antes de que lo note tu cliente.

Los scopes. Pedí sólo los que tu proceso usa. Los cuatro que importan:

ScopeHabilita
invoice:writeEmitir
invoice:readConsultar, buscar, exportar, PDF
invoice:cancelAnular
taxpayer:readLeer datos del contribuyente

Si falta uno, la respuesta dice cuál:

{
  "statusCode": 403,
  "errorCode": "API_KEY_INSUFFICIENT_SCOPE",
  "message": "La API key no tiene el scope necesario para esta operación",
  "details": "La API key no declara invoice:cancel (taxpayer cms6n9hxh0008urbk44g07f66)"
}

Esa respuesta es de una clave con invoice:read solamente, intentando anular. La misma clave intentando emitir devuelve La API key no declara invoice:write.

Hay scopes que no se pueden pedir nunca: crear otras keys, mover miembros, cargar credenciales, dar de alta contribuyentes. Una key con eso sería una credencial de alcance ilimitado disfrazada de integración.

Recomendación concreta: si tu proceso de facturación no anula, no le pongas invoice:cancel. Y si tenés un proceso de reportes aparte, dale su propia key con invoice:read nada más.

Y de dónde sale el taxpayerId

Todas las operaciones de emisión lo piden, y no es el CUIT. Con la clave ya creada, GET /v1/consumo lista tus contribuyentes con su id:

{
  "taxpayers": [
    {
      "taxpayerId": "cms6q9ym80008ureocwo81ig2",
      "cuit": "20437136697",
      "billable": 0,
      "total": 0
    }
  ]
}

Alcanza con invoice:read. Guardalo junto a tu configuración: es estable y lo vas a usar en cada emisión.

(No hay una operación cuyo único propósito sea listar contribuyentes: POST /v1/taxpayers los crea y devuelve el id, pero exige sesión de panel. GET /v1/consumo es el camino con API key.)

4 · Emitir

curl -X POST https://api.emissa.com.ar/v1/invoices/authorize \
  -H "X-API-Key: $EMISSA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: sub_8123-2026-07" \
  -d '{ ... }'

La decisión más importante de toda la integración es la Idempotency-Key, y no es la que parece. Derivala de la intención de facturar —el id de la suscripción más el período, por ejemplo— y no de un UUID por intento. Un comprobante fiscal duplicado no se borra: se anula con una nota de crédito y quedan los dos en tu historia. Está desarrollado en cómo se numera una serie.

Lo que no tenés que hacer: llevar tu propia numeración, ni tomar un lock, ni reservar números. La serie la resuelve Emissa contra ARCA, y está medido que tres emisiones simultáneas salen consecutivas.

5 · Consultar

Tres formas, para tres propósitos distintos:

Por id, cuando ya sabés cuál:

curl https://api.emissa.com.ar/v1/invoices/{id} -H "X-API-Key: $EMISSA_API_KEY"

Búsqueda paginada, para una pantalla o un listado. Devuelve un envelope de dos claves: data, con los comprobantes completos, y pagination. Ésta es la pagination textual de una corrida con limit=2:

{
  "limit": 2,
  "hasMore": true,
  "nextCursor": "eyJjIjoiMjAyNi0wNy0yOVQyMjoyNDo0My4zNzlaIiwiaSI6ImNtczZuazUweTAwMTF1cjhrcmlmZ3BwYmoifQ"
}

Se pagina pasando cursor=<nextCursor>. Verificado: la primera página trajo los comprobantes 135 y 134, y la segunda —con ese cursor— el 133 y el 132. El orden es descendente por fecha de creación.

La decisión: paginá con el cursor, no con offset. Un cursor no se desordena cuando entran comprobantes nuevos mientras recorrés.

Exportación completa, para conciliar o archivar. Devuelve NDJSON —un JSON por línea— y se procesa en streaming:

curl "https://api.emissa.com.ar/v1/invoices/export?taxpayerId=TU_TAXPAYER_ID" \
  -H "X-API-Key: $EMISSA_API_KEY" -o comprobantes.ndjson
HTTP 200 · 8007 bytes · Content-Type: application/x-ndjson; charset=utf-8
5 líneas

La decisión: no uses export para una pantalla ni search para conciliar el mes. El primero no pagina y el segundo te haría recorrer cientos de páginas.

6 · Anular

Un comprobante autorizado no se borra: se anula con una nota de crédito que lo referencia, y Emissa la emite por vos eligiendo el tipo correcto.

curl -X POST https://api.emissa.com.ar/v1/invoices/{id}/cancel \
  -H "X-API-Key: $EMISSA_API_KEY" \
  -H "Idempotency-Key: anular-sub_8123-2026-07"

La nota de crédito tiene su propio CAE —es un comprobante fiscal por derecho propio— y la factura original queda en CANCELLED conservando el suyo:

status: CANCELLED | cancelledByInvoiceId: cms6l58la000kur045qi0nfta | cae: 86300696468093

La decisión: la anulación también lleva Idempotency-Key, y tiene que ser distinta de la de la emisión. Si reusás la misma, estás pidiendo "lo mismo que antes", que era emitir.

7 · El PDF

curl https://api.emissa.com.ar/v1/invoices/{id}/pdf \
  -H "X-API-Key: $EMISSA_API_KEY" -o comprobante.pdf

Se genera al pedirlo, así que no hay nada que almacenar de tu lado. Trae el bloque del emisor, el detalle, las leyendas de transparencia fiscal y el QR de la RG 4892.

La decisión: pedilo cuando lo necesites, no en el momento de emitir. Guardar el PDF te obliga a regenerarlo si algo del formato cambia; guardar el id no.

8 · Lo que puede salir mal y no es culpa tuya

Un pedido a ARCA puede quedarse sin respuesta. Cuando eso pasa, el comprobante no queda ni autorizado ni rechazado: queda en un estado que hay que resolver preguntándole al organismo.

curl "https://api.emissa.com.ar/v1/invoices/unresolved?taxpayerId=TU_TAXPAYER_ID" \
  -H "X-API-Key: $EMISSA_API_KEY"
[]

Vacío es lo normal. Cuando no lo está, POST /v1/invoices/{id}/reconcile le pregunta a ARCA qué pasó de verdad.

Y la regla que más importa de toda la integración: ante un comprobante en ese limbo, no reintentes la emisión a ciegas. Reconciliá primero. Reintentar puede duplicar un comprobante fiscal, y eso no se deshace. El detalle de qué error se reintenta y cuál no está en qué hacer cuando el CAE viene rechazado.

El orden, en una lista

  1. Dar de alta el contribuyente en el panel — decidí vatStatus, salePoint, environment.
  2. Cargar el certificado en el panel — uno por ambiente.
  3. Crear la API key — decidí el modo y los scopes mínimos.
  4. Emitir con Idempotency-Key derivada de tu dominio.
  5. Leer tu taxpayerId de GET /v1/consumo y guardarlo con tu configuración.
  6. Guardar el id que devuelve la emisión. Es lo único que necesitás para todo lo demás.
  7. Loguear X-Request-Id y X-Emissa-Mode de cada respuesta.
  8. Consultar por id, paginar con cursor, exportar con NDJSON.
  9. Anular con una Idempotency-Key propia.
  10. Revisar unresolved de forma periódica, y reconciliar en vez de reintentar.

Qué sigue