> ## Documentation Index
> Fetch the complete documentation index at: https://www.emissa.com.ar/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Cómo se arma una integración

> El recorrido completo de un producto que factura a sus clientes, en orden, con la decisión que hay que tomar en cada paso.

El [quickstart](https://www.emissa.com.ar/docs/guias/quickstart.md) 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.

> [!NOTE]
>
> 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.

![En el panel, con sesión y segundo factor: crear API keys, registrar webhooks y mover miembros. Con API key: dar de alta contribuyentes, cargar certificados, emitir, consultar, anular y bajar el PDF.](https://mintcdn.com/emissa/vjd-QImXVEFbfvNe/images/diagramas/panel-vs-api.light.svg?fit=max\&auto=format\&n=vjd-QImXVEFbfvNe\&q=85\&s=f8ecd4397b4cb6e6d421cb1cc95353d7)

![](https://mintcdn.com/emissa/vjd-QImXVEFbfvNe/images/diagramas/panel-vs-api.dark.svg?fit=max\&auto=format\&n=vjd-QImXVEFbfvNe\&q=85\&s=a838368fb6ad391fd6c0152a785b9b05)

Qué se hace sólo en el panel y qué con una API key

> [!NOTE]
>
> **El reparto es de seguridad, no de capacidad.** Con una API key se puede dar
> de alta un contribuyente y cargar un certificado —el primero desde la 5.33, el
> segundo desde la 5.35—, siempre que la key tenga el scope que corresponde. Lo
> que **sólo se hace desde el panel**, con sesión y segundo factor, es lo que
> reconstruye o amplía el acceso: crear otras keys, mover miembros y registrar
> webhooks.

| Qué querés hacer | Panel | API key | Lo que exige la key |
| - | - | - | - |
| Dar de alta un contribuyente | Sí | Sí | Scope `taxpayer:write` y **sin restricción** a contribuyentes puntuales |
| Cargar un certificado propio | Sí | Sí | Scope `credential:write`; si la key está restringida, el contribuyente tiene que estar en su lista |
| Emitir, consultar, anular, el PDF | Sí | Sí | Los scopes de [más abajo](#los-scopes) |
| Crear otras API keys | Sí | No | Nunca: una key no crea keys |
| Registrar webhooks | Sí | No | Nunca: `403 API_KEY_NOT_ALLOWED_HERE` |

**La consecuencia de diseño:** si tu producto factura en nombre de muchos
contribuyentes, el alta de cada CUIT **lo puede hacer tu backend** como parte del
onboarding de cada cliente. No hace falta que una persona entre al panel por
cada uno.

**El certificado propio es opcional.** Un contribuyente que **autoriza a Emissa en
ARCA** (la delegación del servicio de facturación a la CUIT de Emissa) emite
firmando con el certificado de la plataforma, sin que cargues ninguno. Si cargás
uno propio, ése gana y no depende de la delegación. Lo que **no** se automatiza
desde Emissa es la autorización en sí: es un trámite que hace el contribuyente
con su clave fiscal, en ARCA, una vez por CUIT. El diagnóstico de conexión te
devuelve en `cuitAAutorizar` cuál es la CUIT a autorizar.

> [!WARNING]
>
> **Una key restringida no puede dar de alta contribuyentes.** Si pudiera, el
> contribuyente nuevo quedaría fuera de su lista y lo podría operar igual,
> escapándose de su propia restricción. Recibe `403
>     API_KEY_INSUFFICIENT_PERMISSIONS`. Para el alta por API, creá una key **sin**
> restricción de contribuyentes.

> [accordion] Qué contesta la API si la key no alcanza
>
> Sin el scope `taxpayer:write`, el alta devuelve el scope que falta:
>
> ```json theme={null}
> {
>   "statusCode": 403,
>   "errorCode": "API_KEY_INSUFFICIENT_SCOPE",
>   "message": "La API key no tiene el scope necesario para esta operación",
>   "details": { "scopeRequerido": "taxpayer:write" }
> }
> ```
>
> Con el scope pero con la key restringida a algunos contribuyentes, el código es
> `API_KEY_INSUFFICIENT_PERMISSIONS`. Y lo que sí es del panel —registrar un webhook,
> por ejemplo— devuelve `403 API_KEY_NOT_ALLOWED_HERE`, con el mensaje *«Esta operación
> no se puede hacer con API key: requiere una sesión de usuario»*.

## 1 · El contribuyente

Se da de alta desde el panel o con `POST /v1/taxpayers` y una key con `taxpayer:write`. 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.

**Las tres decisiones de este paso:**

| Decisión | Qué define | Dónde está el detalle |
| - | - | - |
| **Condición frente al IVA** | Qué letras vas a poder emitir. No se cambia después sin consecuencias | [Comprobantes A, B y C](https://www.emissa.com.ar/docs/guias/tipos-de-comprobante.md) |
| **Punto de venta** | Tiene que estar dado de alta en ARCA **para webservice** | [Qué es un punto de venta](https://www.emissa.com.ar/docs/guias/punto-de-venta.md) |
| **Ambiente** | Homologación o producción. Tiene que coincidir con el modo de la clave | [CUIT, CAE y homologación](https://www.emissa.com.ar/docs/guias/conceptos-fiscales.md) |

> [!TIP]
>
> Empezá en homologación: da CAE real y no tiene consecuencia fiscal.

> [accordion] Los campos del alta
>
> ```json theme={null}
> {
>   "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"
> }
> ```

## 2 · El certificado

Desde el panel o con `POST /v1/arca/credentials` y una key con `credential:write`: el `.crt` que te dio ARCA y su clave privada. Es opcional si el contribuyente autorizó a Emissa en ARCA. Emissa los
guarda cifrados y los usa para firmar cada pedido; **la clave privada no vuelve a
salir**.

> [!WARNING]
>
> **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—. 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.

> [accordion] Qué contesta si emitís sin haber cargado la credencial
>
> ```json theme={null}
> {
>   "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.

> [!TIP]
>
> La respuesta trae `X-Emissa-Mode` en cada pedido: **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.

| Scope | Habilita |
| - | - |
| `invoice:write` | Emitir |
| `invoice:read` | Consultar, buscar, exportar, PDF |
| `invoice:cancel` | Anular |
| `taxpayer:read` | Leer datos del contribuyente |
| `taxpayer:write` | Dar de alta o editar contribuyentes (key sin restricción) |
| `credential:write` | Cargar el certificado de ARCA |

> [!TIP]
>
> **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.

Hay scopes que **no se pueden pedir nunca**: crear otras keys y mover miembros.
Una key con eso sería una credencial de alcance ilimitado disfrazada de
integración: alcanzaría con filtrarla una vez para tener acceso permanente.

> [accordion] Si falta un scope, la respuesta dice cuál
>
> ```json theme={null}
> {
>   "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`.

### De dónde sale el id del contribuyente

Todas las operaciones de emisión lo piden, y **no es el CUIT**. Con la clave ya
creada, `GET /v1/taxpayers` lista tus contribuyentes con su id (alcanza con
`taxpayer:read`), y `?cuit=` encuentra el de un CUIT puntual, con o sin guiones. Si
justo lo estás dando de alta y ya existía, el `409` del alta trae el id: mirá
la sección «Varios productos en una organización», más abajo.

> [!TIP]
>
> **Guardalo junto a tu configuración**: es estable y lo vas a usar en cada
> emisión.

### Identificá tu producto

Si tu cuenta de Emissa sirve a más de un producto —o querés separar el consumo de
cada integración—, mandá en cada pedido la cabecera **`X-Emissa-Producto`** con el
nombre de tu producto:

```bash theme={null}
curl -X POST https://api.emissa.com.ar/v1/invoices/authorize \
  -H "X-API-Key: $EMISSA_API_KEY" \
  -H "X-Emissa-Producto: inmos" \
  ...
```

* **Es opcional** y sólo vale con API key. Sin ella, o vacía, el producto queda en
  `null`.
* **Formato:** de 1 a 32 caracteres entre minúsculas, números y guiones
  (`^[a-z0-9-]{1,32}$`). Cualquier otra cosa devuelve `400 INVALID_INPUT`, con el
  formato esperado en el mensaje.
* **Se estampa al emitir y no cambia.** Un reintento con la misma
  `Idempotency-Key` devuelve el comprobante original aunque mandes otra etiqueta.
* **Dónde la leés después:** el comprobante trae `producto`;
  `GET /v1/invoices/search?producto=inmos` filtra; `GET /v1/consumo` suma
  `porProducto` (`[{ producto, total, billable }]`) y acepta `?producto=`; el
  evento `invoice.resolved` trae `producto` en `data`; y el registro de pedidos
  del panel se puede filtrar por producto.

> [!NOTE]
>
> **Con una key sin producto, es atribución y no autorización.** No cambia lo
> que la key puede hacer, y cualquiera con la key puede mandar cualquier valor:
> no lo uses para decidir quién puede qué. **Con una key atada a un producto sí
> restringe**, y la cabecera deja de ser opcional en un sentido: ver la sección
> de abajo.

### Varios productos en una organización

Si más de un producto tuyo (`inmos`, `define`…) opera dentro de **una sola
organización** de Emissa, la cabecera sola no alcanza: cualquier key llega a todos
los contribuyentes de la organización. Para que el de un producto no vea ni opere
los de otro, **atá la key al producto**.

**1. Atá la key.** En el panel, al crear la key, completá **Producto**
(`inmos`). Una key que ya existía se puede atar **una sola vez**, desde la misma
pantalla; después no se cambia. Si te equivocás, creás otra.

**2. Qué cambia para esa key:**

* **Cada pedido queda etiquetado con ese producto**, mandes o no
  `X-Emissa-Producto`. Si mandás **otro** valor, la respuesta es
  `403 API_KEY_PRODUCTO_DISTINTO` y no se hace nada. **Antes se mira el formato**:
  una cabecera mal formada (`INMOS!`, con mayúsculas, con espacios, de más de 32
  caracteres) es `400 INVALID_INPUT`, no `403`. Un valor que no es un producto no
  es «otro producto»: el `403` mandaría a revisar la key cuando lo que está mal es
  la cabecera. Gana el formato; la comparación con la key viene después.
* **Sólo ve y opera los contribuyentes que incluyen su producto**: emitir, anular,
  reconciliar, buscar, exportar, el PDF, verificar la conexión, las credenciales,
  el logo y el consumo. Un contribuyente **sin tu producto entre los suyos**, o
  **sin ninguno**, responde `403 INSUFFICIENT_TAXPAYER_PERMISSIONS`: **igual** que
  uno de otra organización, a propósito. Decir «es de otro producto» confirmaría
  que ese id existe.
* Se **combina** con la restricción por contribuyentes: si además la key está
  acotada a una lista, la lista sigue valiendo.

**3. Un contribuyente puede ser de varios productos.** Si el mismo CUIT lo
facturan `inmos` y `define`, el contribuyente tiene `productos: ["inmos", "define"]`
y **las keys de los dos lo operan**. Una key de un tercer producto no. Los productos
de un contribuyente:

* **Los crea el alta**: `POST /v1/taxpayers` de un CUIT nuevo lo crea con
  `productos = [el producto de tu key]` o, si la key no está atada, `[el de
  X-Emissa-Producto]`.
* **No cambian por API.** Sumar o quitar productos lo hace el dueño de la cuenta
  desde el panel, con auditoría de la lista de antes y la de después. Es también la
  forma de migrar los contribuyentes que ya existían, que quedaron sin producto.

**4. Un CUIT que ya existe.** El alta responde `409` y, **si el contribuyente
incluye tu producto entre los suyos, trae su id** en `details.taxpayerId` —también
en producción—:

```json theme={null}
{
  "statusCode": 409,
  "errorCode": "RESOURCE_ALREADY_EXISTS",
  "details": { "taxpayerId": "cmt42x6jb000curi4sz1gyeij" }
}
```

Si el contribuyente existe pero **no incluye tu producto**, no tiene ninguno o es
de otra organización, el `409` viene **sin id**: el CUIT está tomado, pero no es un
contribuyente que tu key alcance. **Y no te suma a él**: una key no se apropia de un
contribuyente ajeno. Para que tu producto lo opere, pedile al dueño de la cuenta que
lo sume desde el panel.

**5. Listar los tuyos.** `GET /v1/taxpayers` devuelve los contribuyentes que tu key
ve, paginados como el resto de las listas (`pagination.nextCursor`), con
`?producto=`, `?cuit=` y `?environment=`. Cada fila es la misma que devuelve el alta,
más `productos`. `?producto=` filtra por **«contiene»**: un contribuyente de `inmos` y
`define` aparece bajo los dos. Una key atada a un producto sólo ve los suyos, aunque
pidas otro `?producto=`.

> [!NOTE]
>
> **`producto` (singular) está deprecado.** La API lo sigue devolviendo un
> tiempo, con el primer valor de `productos`, para el cliente que ya lo leía.
> Con más de un producto no dice cuáles son los demás: leé `productos`.

**6. Un contribuyente compartido, y lo que cada key ve.** Los comprobantes quedan
con el producto **de quien los emitió** (`producto` en el comprobante), no con el del
contribuyente. Dos cosas a tener presentes:

* **El acceso es por contribuyente, no por comprobante.** Si el mismo contribuyente es
  de `inmos` y de `define`, la key de cada uno puede **buscar, ver, anular y exportar
  todos** sus comprobantes, también los que emitió el otro producto. Es lo que
  significa compartir el contribuyente: comparten también la numeración de la serie.
  Para filtrar lo tuyo, usá `?producto=` en la búsqueda.
* **El consumo sí se separa.** `GET /v1/consumo` de una key atada devuelve sólo el
  consumo **de su producto**, también sobre un contribuyente compartido; un
  `?producto=` de otro devuelve vacío. La cuota de la organización (`organization`)
  sigue siendo una sola.

**7. Los webhooks.** Cada producto registra **su propio endpoint** en el panel y le
pone su producto. Un comprobante le llega al endpoint **del producto que lo emitió**,
sea cual sea el contribuyente; lo que es del contribuyente (`credential.expiring`,
`connection.established`) llega a los endpoints de **todos** sus productos; y lo de
la organización (`quota.exceeded`, `api_key.expiring`) llega a todos. Un endpoint
**sin producto** es general y recibe todo, como siempre. El detalle, en
[los webhooks](https://www.emissa.com.ar/docs/guias/webhooks.md).

**8. La `Idempotency-Key` es única por organización, no por producto.** Dos
productos que comparten organización comparten también el espacio de claves: si
`inmos` y `define` mandan la misma clave, el segundo recibe
`409 IDEMPOTENCY_KEY_REUSED` aunque sean pedidos distintos. No se filtra ningún
dato, pero el pedido no se hace. Cada producto tiene que **usar un UUID por
intento o prefijar la clave con su producto** —`inmos-sub_8841-2026-10`, no
`sub_8841-2026-10`—. Si derivás la clave de tu dominio, el prefijo es lo que la
mantiene distinta de la de tu vecino.

> [!WARNING]
>
> **Antes de atar una key que ya existe, asignale producto a sus
> contribuyentes.** Desde que se ata, la key deja de ver los que no incluyen ese
> producto —incluidos los que no tienen ninguno—. Una key **sin** producto sigue
> comportándose como siempre y alcanza a toda la organización.

## 4 · Emitir

> [!WARNING]
>
> **La decisión más importante de toda la integración es la clave de
> idempotencia**, 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. Está
> desarrollado en [cómo se numera una serie](https://www.emissa.com.ar/docs/guias/numeracion.md).

**Si ARCA rechaza el comprobante** recibís un `422 ARCA_WSFEV1_REJECTED` —un rechazo
fiscal, no una falla de Emissa— y **el motivo viaja en `details.arca`**, también en
producción: `{ errores: [{ codigo, mensaje }], observaciones: [{ codigo, mensaje }] }`.
Es el texto literal de ARCA (recortado a 500 caracteres y 20 ítems), pensado para que tu
cliente corrija el dato —el receptor, la condición de IVA, la fecha— y reintente. El mismo
motivo queda en el comprobante (`rechazoArca`) y en el webhook `invoice.resolved`. El detalle
está en [los errores de la API](https://www.emissa.com.ar/docs/guias/errores.md).

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:

| Forma | Para qué | La decisión |
| - | - | - |
| **Por id** | Cuando ya sabés cuál | Guardá el id que devuelve la emisión: es todo lo que necesitás |
| **Búsqueda paginada** | Una pantalla o un listado | Paginá con **cursor**, no con `offset` |
| **Exportación NDJSON** | Conciliar o archivar el período | Se procesa en streaming, no pagina |

> [!WARNING]
>
> **No uses la exportación para una pantalla ni la búsqueda para conciliar el
> mes.** La primera no pagina y la segunda te haría recorrer cientos de páginas.

> [accordion] Cómo se pagina con cursor
>
> La búsqueda devuelve un envelope de dos claves: `data`, con los comprobantes
> completos, y `pagination`. Ésta es la `pagination` textual de una corrida con
> `limit=2`:
>
> ```json theme={null}
> {
>   "limit": 2,
>   "hasMore": true,
>   "nextCursor": "eyJjIjoiMjAyNi0wNy0yOVQyMjoyNDo0My4zNzlaIiwiaSI6ImNtczZuazUweTAwMTF1cjhrcmlmZ3BwYmoifQ"
> }
> ```
>
> Se pagina pasando ese valor como `cursor`. Verificado: la primera página trajo los
> comprobantes 135 y 134, y la segunda el 133 y el 132. El orden es descendente por
> fecha de creación.
>
> **Un cursor no se desordena** cuando entran comprobantes nuevos mientras recorrés,
> que es lo que sí le pasa a un `offset`.

> [accordion] Cómo se exporta
>
> ```bash theme={null}
> 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
> ```
>
> NDJSON es un JSON por línea, así que se procesa en streaming sin cargar todo en
> memoria.

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

La nota tiene **su propio CAE** —es un comprobante fiscal por derecho propio— y la
factura original queda anulada conservando el suyo.

> [!WARNING]
>
> **La anulación también lleva clave de idempotencia, 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

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.

> [!TIP]
>
> **Pedilo cuando lo necesites, no al 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.

> [!WARNING]
>
> **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](https://www.emissa.com.ar/docs/guias/cae-rechazado.md).

## El orden, de principio a fin

**1. Dar de alta el contribuyente, en el panel**

Decidí la condición frente al IVA, el punto de venta y el ambiente.

**2. Cargar el certificado, en el panel**

Uno por ambiente.

**3. Crear la API key**

Decidí el modo y los scopes mínimos.

**4. Guardar el id de tu contribuyente**

Sale del endpoint de consumo. Va con tu configuración.

**5. Emitir con una clave de idempotencia derivada de tu dominio**

No de un UUID por intento.

**6. Guardar el id que devuelve la emisión**

Es lo único que necesitás para todo lo demás.

**7. Loguear el request id y el modo de cada respuesta**

Es lo que hace reportable un problema, y lo que te avisa si estás en el
ambiente equivocado.

**8. Consultar por id, paginar con cursor, exportar con NDJSON**

Cada una para lo suyo.

**9. Anular con una clave de idempotencia propia**

Distinta de la de la emisión.

**10. Revisar los comprobantes sin resolver de forma periódica**

Y reconciliar en vez de reintentar.

## Qué sigue

> [card] [Cómo se numera una serie](https://www.emissa.com.ar/docs/guias/numeracion.md)
>
> La idempotencia en detalle.

> [card] [Cuando el CAE viene rechazado](https://www.emissa.com.ar/docs/guias/cae-rechazado.md)
>
> El árbol de decisión de los errores.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.