Emitir un comprobante
POST /:id-empresa/invoices
La emisión es síncrona: la API le pide el CAE a ARCA y responde recién con la autorización en mano. Una emisión sana tarda entre 1 y 3 segundos.
Los importes no se envían: mandás cantidad, precio y alícuota por ítem, y la API calcula netos, IVA y totales con el redondeo que exige ARCA. Ver cálculo de totales.
Campos principales
| Campo | Obligatorio | Tipo | En ARCA | Descripción |
|---|---|---|---|---|
idempotency | Sí | String | — | Clave única de este comprobante. Máx. 128 caracteres: letras, números, - y _. Ver idempotencia. |
posNumber | Sí | Integer | PtoVta | Punto de venta. Tiene que estar dado de alta en NanoFactura y habilitado en ARCA. |
voucherType | Sí | Integer | CbteTipo | Tipo de comprobante (tabla). |
concept | Sí | Integer | Concepto | 1 productos · 2 servicios · 3 productos y servicios. |
voucherDate | Sí | String | CbteFch | Fecha del comprobante, YYYYMMDD. |
customer | Sí | Object | — | Datos del receptor (ver tabla). |
items | Sí | Array | — | Líneas de detalle, mínimo 1 (ver tabla). |
serviceFrom | Condicional | String | FchServDesde | Inicio del servicio, YYYYMMDD. Obligatorio si concept es 2 o 3. |
serviceTo | Condicional | String | FchServHasta | Fin del servicio, YYYYMMDD. Obligatorio si concept es 2 o 3. |
serviceExpiration | Condicional | String | FchVtoPago | Vencimiento del pago, YYYYMMDD. Obligatorio si concept es 2 o 3. |
associatedVouchers | Condicional | Array | CbtesAsoc | Comprobante que se modifica. Necesario en notas de crédito y débito (ver tabla). |
currency | No | Object | — | Moneda. Por defecto, pesos argentinos (ver tabla). |
tributes | No | Array | Tributos | Impuestos y percepciones que no son IVA (ver tabla). |
emails | No | Array | — | Destinatarios del envío automático del PDF (ver tabla). |
settings | No | Object | — | Precisión decimal de cantidades y precios (ver tabla). |
legend | No | String | — | Leyenda impresa en el comprobante. Máx. 200 caracteres. |
comments | No | String | — | Comentario interno del emisor, no se imprime. Máx. 512 caracteres. |
externalReference | No | String | — | Identificador de tu sistema. Máx. 128 caracteres. Filtrable en el listado. |
additionalData | No | Object | — | Objeto clave-valor libre que se guarda junto al comprobante. |
cancelsInSameForeignCurrency | No | String | CanMisMonExt | 'S' o 'N'. Sólo aplica en moneda extranjera. |
receiverTaxConditionId | No | Integer | CondicionIVAReceptorId | Si se envía, pisa a customer.taxType. Normalmente no hace falta. |
externalReferenceEs el campo que ata el comprobante a tu operación (número de pedido, id de venta). Es el único que después podés usar para buscarlo desde tu sistema sin llevar una tabla de equivalencias propia.
Qué campos son obligatorios según tu condición fiscal y el tipo de comprobante: ver Equivalencia de campos con ARCA.
Cliente (customer)
| Campo | Obligatorio | Tipo | En ARCA | Descripción |
|---|---|---|---|---|
docType | Sí | Integer | DocTipo | Tipo de documento (tabla). |
docNumber | Sí | String | DocNro | Número sin guiones ni puntos. |
name | Sí | String | — | Nombre, apellido o razón social. |
taxType | Sí | Integer | CondicionIVAReceptorId | Condición frente al IVA (tabla). |
address | No | String | — | Domicilio. Se imprime en el comprobante. |
stateId | Recomendado | Integer | null | — | Provincia (tabla). |
stateIdEs opcional a nivel técnico, pero es el único dato estructurado de ubicación del receptor: address es texto libre y no se puede agrupar. Con la provincia cargada podés después responder por jurisdicción —dónde está tu facturación, qué te corresponde para Ingresos Brutos, cómo se reparte tu cartera— y armar reportes de impuestos sin trabajo manual. Sin ella, esa pregunta no tiene respuesta y no hay forma de reconstruirla hacia atrás.
Si genuinamente no sabés de dónde es el receptor, mandá null: significa "no informada" y es mejor que inventar 0, que es CABA.
Podés reenviar tal cual el stateId que devuelve la consulta de padrón.
Ítems (items)
Mínimo uno. Cada objeto es una línea de detalle.
| Campo | Obligatorio | Tipo | Descripción |
|---|---|---|---|
description | Sí | String | Descripción del producto o servicio. |
quantity | Sí | Number | Cantidad. Mínimo 0.0001. |
price | Sí | Number | Precio unitario, sin descuento. Mínimo 0. |
taxType | Sí, salvo en C | Integer | Alícuota de IVA de la línea (tabla). Los comprobantes C no la llevan: no discriminan IVA. |
code | No | String | Tu código interno del producto. |
unitType | No | Integer | Unidad de medida (tabla). Por defecto 7 (unidades). |
discount | No | Number | Descuento de la línea. Por defecto 0. |
discountType | No | Enum | 'percentage' (defecto) o 'amount'. |
Moneda (currency)
| Campo | Obligatorio | Tipo | En ARCA | Descripción |
|---|---|---|---|---|
id | No | String | MonId | Código de moneda (tabla). Por defecto 'PES'. |
quotation | No | Number | MonCotiz | Cotización contra el peso. Por defecto 1. |
Si usás una moneda distinta de PES, mandá la cotización del día: no se consulta sola.
Tributos (tributes)
Impuestos y percepciones que no son IVA: Ingresos Brutos, tasas municipales, impuestos internos, percepciones.
| Campo | Obligatorio | Tipo | En ARCA | Descripción |
|---|---|---|---|---|
type | Sí | Integer | Id | Tipo de tributo (tabla). |
baseAmount | Sí | Number | BaseImp | Base imponible. |
aliquot | Sí | Number | Alic | Alícuota aplicada. |
subtotal | Sí | Number | Importe | Importe del tributo. |
description | No | String | Desc | Descripción. |
A diferencia de los ítems, acá el importe sí lo calculás vos: ARCA recibe el subtotal tal como lo mandás.
Comprobantes asociados
Vincula la factura original que una nota de crédito o nota de débito modifica.
| Campo | Obligatorio | Tipo | En ARCA | Descripción |
|---|---|---|---|---|
voucherType | Sí | Integer | Tipo | Tipo del comprobante asociado. |
posNumber | Sí | Integer | PtoVta | Punto de venta del asociado. |
voucherNumber | Sí | Integer | Nro | Número del asociado. |
cuit | No | String | Cuit | CUIT del emisor, sólo si es distinto al tuyo. |
{
"voucherType": 13,
"associatedVouchers": [{ "voucherType": 11, "posNumber": 1, "voucherNumber": 45 }]
}
La nota de crédito debe ser de la misma letra que la factura que anula. Ver tipos de comprobante.
Correos (emails)
Despacha el PDF por correo en segundo plano, sin bloquear la respuesta.
| Campo | Obligatorio | Tipo | Descripción |
|---|---|---|---|
email | Sí | String | Dirección del destinatario. |
name | No | String | Nombre del destinatario. |
comments | No | String | Texto a incluir en el cuerpo del correo. |
pdfVersions | No | Array | Copias a adjuntar: 1 original, 2 duplicado, 3 triplicado. Por defecto [1]. |
Máximo 3 destinatarios por comprobante, contando los que agregues después.
Precisión (settings)
| Campo | Tipo | Descripción |
|---|---|---|
quantityPrecision | Integer | Decimales de la cantidad: 2 (defecto), 4 o 6. |
pricePrecision | Integer | Decimales del precio unitario: 2 (defecto), 4 o 6. |
Subilos si vendés por unidades fraccionadas o con precios de más de dos decimales. Los importes resultantes se redondean siempre a 2 decimales, que es lo que exige ARCA.
Cálculo de totales
Por cada ítem, en este orden y redondeando a 2 decimales en cada paso:
subtotal = quantity × price
descuento = discountType === 'percentage' ? subtotal × discount / 100 : discount
neto = subtotal − descuento
El destino del neto depende del taxType de la línea:
taxType | Tratamiento | Va a | ¿Genera IVA? |
|---|---|---|---|
4, 5, 6, 8, 9 | Gravado | ImpNeto | Sí |
2, 3 | Exento / 0% | ImpOpEx | No |
1 u otro | No gravado | ImpTotConc | No |
Y el total es ImpNeto + ImpIVA + ImpTrib + ImpOpEx + ImpTotConc.
En Factura C, Nota de Débito C y Nota de Crédito C (11, 12, 13) todas las líneas van a ImpNeto, ImpIVA queda en 0 y no se informa IVA. Por eso los ítems de un comprobante C no llevan taxType: no hay alícuota que declarar. Si llega igual, se acepta y se ignora.
Es correcto y es lo que corresponde: un monotributista no discrimina IVA.
Lo que la API completa sola
| Campo ARCA | De dónde sale |
|---|---|
CbteDesde / CbteHasta | Número correlativo que reserva la API. |
ImpNeto | Suma de las líneas gravadas (y de todas en comprobantes C). |
ImpIVA | Suma del IVA calculado por línea. |
ImpOpEx | Suma de las líneas exentas o al 0%. |
ImpTotConc | Suma de las líneas no gravadas. |
ImpTrib | Suma de los tributes[].subtotal. |
ImpTotal | La suma de todo lo anterior. |
Iva[] | Una entrada por cada alícuota gravada presente en los ítems. |
Tributos[] | Una entrada por cada elemento de tributes. |
Idempotencia
idempotency identifica un comprobante. Es lo que hace segura la red: si se corta la conexión y no sabés si el comprobante se emitió, reintentás con la misma clave y la API te devuelve el original en vez de emitir uno nuevo.
{ "ok": true, "result": { "id": 1485, "cae": "76251489623547", "idempotencyHit": true } }
idempotencyHit: true significa "esto ya existía, no se emitió nada nuevo".
Cómo armar la clave
Una clave identifica exactamente un comprobante. No se reparte por tipo ni por punto de venta: si mandás una clave que ya pertenece a un comprobante de otro punto de venta o de otro tipo, el pedido se rechaza con 409, y es un error permanente.
{
"ok": false,
"error": "La clave de idempotencia ya corresponde al comprobante 1-6-45, no al que se está pidiendo (2-6). Usá una clave distinta para cada comprobante.",
"code": "VALIDATION_ERROR"
}
Lo más robusto no es un UUID al azar: es una clave determinística, que podés recalcular sin haberla guardado. Si tu proceso se cae después de emitir pero antes de registrar el resultado, con un UUID perdiste la clave y el reintento duplica el comprobante; con una clave derivada la volvés a calcular igual y recuperás el original.
La condición es que incluya todo lo que hace único al comprobante, no sólo la operación:
{tuIdDeVenta}-{voucherType}-{posNumber}
venta-8842-11-1 Factura C de la venta 8842
venta-8842-13-1 su nota de crédito
Así el mismo pedido puede tener su factura y su nota de crédito sin chocar. Sólo venta-8842 para las dos daría 409 en la segunda.
Si preferís un UUID, es igual de válido —pero guardalo junto a la venta antes de emitir, no después.
Sólo se admiten letras, números, - y _, hasta 128 caracteres. Las claves son independientes por entorno: la misma puede existir en homologación y en producción sin pisarse, así que podés ensayar con las claves reales.
Respuesta (201 Created)
{
"ok": true,
"result": {
"id": 1485,
"voucherNumber": 45,
"cae": "76251489623547",
"totalAmount": 142500,
"arcaObservations": null,
"idempotencyHit": false
},
"quota": {
"type": "invoices",
"granted": 500,
"used": 138,
"remaining": 362,
"overage": 0,
"periodEnd": "2026-09-16",
"breakdown": { "production": 112, "homologation": 26 }
}
}
| Campo | Descripción |
|---|---|
id | Identificador interno. Es el que se usa en las rutas de PDF y email. |
voucherNumber | Número correlativo dentro del punto de venta y tipo. |
cae | CAE otorgado por ARCA. |
totalAmount | Importe total calculado. |
arcaObservations | null, o un array [{ code, msg }] con observaciones que ARCA devolvió aprobando igual el comprobante. Conviene registrarlas. |
idempotencyHit | true si la clave ya correspondía a un comprobante existente. |
id, cae, voucherNumber, posNumber y voucherType. Con eso alcanza para todo lo demás.El PDF no está listo en este momento: se genera en segundo plano. Ver PDF y envío por email.
El objeto quota
Va al lado de result, no adentro: es el estado del cupo mensual de tu plan, no un dato del comprobante. Sirve para frenar antes de chocar sin consultar nada aparte.
| Campo | Descripción |
|---|---|
granted | Cupo total del período (los adicionales contratados ya están sumados). |
used | Consumido, incluyendo este comprobante. |
remaining | granted − used, con piso en 0. Nunca es negativo. |
periodEnd | Día en que se renueva el cupo. |
breakdown | Consumo por entorno. |
Tres cosas a tener en cuenta: el cupo es mensual y no se acumula; homologación consume el mismo cupo; y quota puede venir en null (en reintentos idempotentes, que no consumen) — tu cliente tiene que tolerarlo. null no significa "cero disponible", significa "no hay nada que informar".
quota no es parte del contrato estableA diferencia de result, la forma de este objeto puede cambiar: pueden aparecer campos nuevos, y los actuales pueden dejar de estar. Usalo para mostrar o alertar, no como algo de lo que dependa tu emisión.
En concreto: leelo siempre de forma defensiva (quota?.remaining), tolerá que sea null y no lo uses como condición para decidir si emitís. La única fuente confiable de que el cupo se agotó es el error 429 SUBSCRIPTION_QUOTA_EXCEEDED.
Errores
Los cuatro que vas a ver en la práctica:
| HTTP | code | Qué pasó | ¿Reintentar? |
|---|---|---|---|
400 | VALIDATION_ERROR | Payload inválido o punto de venta inexistente | No, corregí el pedido |
422 | ARCA_ERROR | ARCA rechazó el comprobante | No, corregí el pedido |
429 | VOUCHER_LOCK_TIMEOUT | Otra emisión del mismo punto de venta sigue en curso | Sí, con la misma idempotency |
504 | — | Se agotó el tiempo del pedido | Sí, con la misma idempotency |
Un 504 no significa que el comprobante no se haya emitido. Reintentá con la misma idempotency: si se había emitido, lo recuperás.
La lista completa, con qué hacer en cada caso: Errores.
Emitir en volumen
ARCA exige numeración consecutiva por punto de venta y tipo, así que la API emite de a un comprobante por vez para cada combinación posNumber + voucherType. Si llega un pedido mientras otro se está autorizando, espera su turno solo (hasta 20 segundos).
Secuenciá los pedidos de un mismo punto de venta y tipo. Lanzarlos en paralelo no acelera nada —igual se serializan— y sólo produce errores 429. Distintos puntos de venta sí pueden emitir en paralelo. No intentes paralelizar llamados a la API, solo generarás errores.
Ver Límites y buenas prácticas.
Ejemplo completo
{
"idempotency": "9f1c3a5e-7b2d-4c81-9e6f-0a2b4c6d8e10",
"posNumber": 1,
"voucherType": 11,
"concept": 2,
"voucherDate": "20260613",
"serviceFrom": "20260601",
"serviceTo": "20260613",
"serviceExpiration": "20260630",
"customer": {
"docType": 96,
"docNumber": "35888999",
"name": "Pedro López",
"address": "Av. de Mayo 500, CABA",
"stateId": 0,
"taxType": 5
},
"items": [
{
"code": "SRV-DEV",
"description": "Servicios de desarrollo de software — Junio 2026",
"quantity": 1,
"unitType": 7,
"price": 150000,
"discount": 5,
"discountType": "percentage"
}
],
"emails": [
],
"externalReference": "EXT-ORD-99882",
"legend": "Gracias por su compra"
}
Implementaciones completas en JavaScript, PHP y Python: Ejemplos.