Saltar al contenido principal

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.

Por defecto, el precio del ítem incluye el IVA

priceIncludesVat decide cómo se lee items[].price, y vale para todo el comprobante:

  • true (por defecto): el precio ya trae el IVA adentro y la API lo extrae.
  • false: el precio es neto y la API le suma el IVA por encima.

Un ítem de $121 al 21% da un comprobante de $121 con el default y de $146,41 con false. No hay forma de adivinar cuál querías: es el parámetro más importante del pedido después del importe mismo.

El default sigue la convención de ARCA para los comprobantes B —en sus ejemplos oficiales la columna de la tabla de ítems se titula "Precio Unitario (incluye IVA)"— y es el número que casi cualquier vendedor tiene a mano: el que ve el cliente. Si tu sistema guarda precios netos, mandá false.

En comprobantes C (11, 12, 13) el parámetro no hace nada: el emisor no traslada IVA, así que el precio es el precio.

Campos principales​

CampoObligatorioTipoEn ARCADescripción
idempotencySíString—Clave única de este comprobante. Máx. 128 caracteres: letras, números, - y _. Ver idempotencia.
posNumberSíIntegerPtoVtaPunto de venta. Tiene que estar dado de alta en NanoFactura y habilitado en ARCA.
voucherTypeSíIntegerCbteTipoTipo de comprobante (tabla).
conceptSíIntegerConcepto1 productos · 2 servicios · 3 productos y servicios.
voucherDateSíStringCbteFchFecha del comprobante, YYYYMMDD.
customerSíObject—Datos del receptor (ver tabla).
itemsSíArray—Líneas de detalle, mínimo 1 (ver tabla).
serviceFromCondicionalStringFchServDesdeInicio del servicio, YYYYMMDD. Obligatorio si concept es 2 o 3.
serviceToCondicionalStringFchServHastaFin del servicio, YYYYMMDD. Obligatorio si concept es 2 o 3.
serviceExpirationCondicionalStringFchVtoPagoVencimiento del pago, YYYYMMDD. Obligatorio si concept es 2 o 3.
associatedVouchersCondicionalArrayCbtesAsocComprobante que se modifica. Necesario en notas de crédito y débito (ver tabla).
currencyNoObject—Moneda. Por defecto, pesos argentinos (ver tabla).
priceIncludesVatNoBoolean—Si los items[].price traen el IVA incluido. Por defecto true. Ver cálculo de totales.
tributesNoArrayTributosImpuestos y percepciones que no son IVA (ver tabla).
emailsNoArray—Destinatarios del envío automático del PDF (ver tabla).
settingsNoObject—Precisión decimal de cantidades y precios (ver tabla).
legendNoString—Leyenda impresa en el comprobante. Máx. 200 caracteres.
commentsNoString—Comentario interno del emisor, no se imprime. Máx. 512 caracteres.
externalReferenceNoString—Identificador de tu sistema. Máx. 128 caracteres. Filtrable en el listado.
additionalDataNoObject—Objeto clave-valor libre que se guarda junto al comprobante. Las claves se definen en el punto de venta (ver datos adicionales) y se pueden imprimir en la plantilla del PDF.
cancelsInSameForeignCurrencyNoStringCanMisMonExt'S' o 'N'. Sólo aplica en moneda extranjera.
receiverTaxConditionIdNoIntegerCondicionIVAReceptorIdSi se envía, pisa a customer.taxType. Normalmente no hace falta.
Guardá externalReference

Es 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)​

CampoObligatorioTipoEn ARCADescripción
docTypeSíIntegerDocTipoTipo de documento (tabla).
docNumberSíStringDocNroNúmero sin guiones ni puntos.
nameSíString—Nombre, apellido o razón social.
taxTypeSíIntegerCondicionIVAReceptorIdCondición frente al IVA (tabla).
addressNoString—Domicilio. Se imprime en el comprobante.
stateIdRecomendadoInteger | null—Provincia (tabla).
Mandá siempre stateId

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

CampoObligatorioTipoDescripción
descriptionSíStringDescripción del producto o servicio.
quantitySíNumberCantidad. Mínimo 0.0001.
priceSíNumberPrecio unitario antes del descuento. Con el IVA incluido, salvo que mandes priceIncludesVat: false. Mínimo 0.
taxTypeSí, salvo en CIntegerAlícuota de IVA de la línea (tabla). Los comprobantes C no la llevan: no discriminan IVA.
codeNoStringTu código interno del producto.
unitTypeNoIntegerUnidad de medida (tabla). Por defecto 7 (unidades).
discountNoNumberBonificación del renglón entero, no por unidad (detalle). Por defecto 0.
discountTypeNoEnum'percentage' (defecto) o 'amount'.

Moneda (currency)​

CampoObligatorioTipoEn ARCADescripción
idNoStringMonIdCódigo de moneda (tabla). Por defecto 'PES'.
quotationNoNumberMonCotizCotizació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.

CampoObligatorioTipoEn ARCADescripción
typeSíIntegerIdTipo de tributo (tabla).
baseAmountSíNumberBaseImpBase imponible.
aliquotSíNumberAlicAlícuota aplicada.
subtotalSíNumberImporteImporte del tributo.
descriptionNoStringDescDescripción.

A diferencia de los ítems, acá el importe sí lo calculás vos: el subtotal se informa tal como lo mandás.

Comprobantes asociados​

Vincula la factura original que una nota de crédito o nota de débito modifica.

CampoObligatorioTipoEn ARCADescripción
voucherTypeSíIntegerTipoTipo del comprobante asociado.
posNumberSíIntegerPtoVtaPunto de venta del asociado.
voucherNumberSíIntegerNroNúmero del asociado.
cuitNoStringCuitCUIT del emisor, sólo si es distinto al tuyo.
Nota de crédito C que anula la factura C 0001-00000045
{
"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.

CampoObligatorioTipoDescripción
emailSíStringDirección del destinatario.
nameNoStringNombre del destinatario.
commentsNoStringTexto a incluir en el cuerpo del correo.
pdfVersionsNoArrayCopias 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)​

CampoTipoDescripción
quantityPrecisionIntegerDecimales de la cantidad: 2 (defecto), 4 o 6.
pricePrecisionIntegerDecimales mínimos del precio unitario: 2 (defecto), 4 o 6. Ver el comprobante impreso cierra solo.

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
importe = subtotal − descuento

Bonificaciones​

El descuento se aplica sobre el renglón, ya multiplicado por la cantidad, y nunca sobre el precio unitario:

10 unidades a $100DescuentaImporte
discount: 10, discountType: 'percentage'subtotal $1.000$100$900
discount: 10, discountType: 'amount'subtotal $1.000$10$990
amount es del renglón, no por unidad

discount: 10 con discountType: 'amount' son $10 en todo el renglón, sin importar la cantidad. Si querés $10 por unidad, el descuento es 10 × cantidad.

El descuento va en la misma base que el precio: con el default, discount: 10 de tipo amount son $10 que el cliente no paga; con priceIncludesVat: false, son $10 menos de neto. En porcentaje da lo mismo en las dos bases, porque un porcentaje no depende de la base.

La bonificación no viaja a ARCA: FECAESolicitar no tiene campo para ella, así que se declara el neto ya descontado. Se imprime en el comprobante, donde la fila cierra: precio unitario × cantidad − descuento = importe.

A dónde va el importe​

El destino del importe depende del taxType de la línea:

taxTypeTratamientoSuma a¿Entra en el desglose de IVA?
4, 5, 6, 8, 9GravadoNeto gravadoSí, con su IVA
3Gravado al 0%Neto gravadoSí, con IVA 0
2ExentoImporte exentoNo
1 u otroNo gravadoImporte no gravadoNo

En las líneas gravadas, de dónde sale el IVA depende de priceIncludesVat:

true (defecto) → base = importe / (1 + alícuota) IVA = importe − base
false → base = importe IVA = base × alícuota

La resta del primer caso es deliberada: garantiza que base + IVA dé exactamente el importe del renglón, sin el centavo que dejaría redondear las dos puntas por separado.

Y el total del comprobante es la suma de los cinco importes:

neto gravado + IVA + importe exento + importe no gravado + otros tributos

Con el default eso equivale a la suma de los importes de los renglones más los tributos: los precios que mandaste son finales y cierran contra el total.

Redondeo​

Todo se redondea a 2 decimales con medio hacia arriba, y en este orden: primero cada renglón, después las sumas. Como los renglones ya vienen redondeados, los totales son sumas exactas de números de dos decimales, y de ahí salen las tres igualdades que sostienen el comprobante:

  • El neto gravado es la suma exacta de las bases del desglose por alícuota.
  • El IVA es la suma exacta de los impuestos de ese mismo desglose.
  • El total es la suma de los cinco importes de arriba.
Por qué la base por la alícuota puede no dar el IVA

Cuando el IVA se extrae de un precio final, la base es la que queda redondeada y el impuesto es la diferencia. Un ítem de $100 al 21% da base 82,64 e IVA 17,36, aunque 82,64 × 0,21 sea 17,3544, que redondearía a 17,35.

El centavo está bien puesto: lo que tiene que cerrar es 82,64 + 17,36 = 100,00, el importe que el cliente paga. Es el mismo criterio de los ejemplos oficiales de ARCA, que declaran una base de 9.917,36 con un IVA de 2.082,64.

El comprobante impreso cierra solo​

Lo que sale impreso es exactamente lo que se declara: la suma de los subtotales de los renglones es el neto gravado, el desglose de IVA impreso es el que se informa, y el total es el mismo. Y cada fila cierra por sí misma:

precio unitario × cantidad − descuento = importe del renglón

Cuando la letra imprime una base distinta de la que mandaste, el precio unitario es un número derivado y la API le da los decimales que esa igualdad necesite. Una Factura A de 50 unidades a $100 finales imprime el unitario neto como 82,6446, no como 82,64: con dos decimales la fila mostraría 4.132,00 contra un subtotal de 4.132,23.

settings.pricePrecision es el mínimo de decimales de esa columna, no el máximo: si mandás los precios en la base que el comprobante imprime, se usa tal cual el número que pusiste.

Qué imprime cada letra​

El neto y el IVA se calculan y se informan siempre, en las tres letras. Lo que cambia es el comprobante impreso, y de eso se ocupa la API sola:

Precio unitario y subtotal del renglónColumna "Subtotal c/IVA"Al pie
ANetosSíNeto gravado y una línea por alícuota
BFinales, con IVANoSubtotal y el "IVA Contenido" de transparencia fiscal
CFinalesNoSubtotal

Mandes los precios en la base que los mandes, el PDF sale igual: es el mismo comprobante declarado de dos formas.

Los comprobantes C no discriminan IVA

En Factura C, Nota de Débito C y Nota de Crédito C (11, 12, 13) todo el importe va al neto, el IVA queda en 0 y no hay desglose por alícuota. 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​

QuéDe dónde sale
Número del comprobanteCorrelativo por punto de venta y tipo, que reserva la API.
Neto gravadoSuma de las bases gravadas (y de todas las líneas en comprobantes C).
IVASuma del IVA de cada línea.
Importe exentoSuma de las líneas exentas.
Importe no gravadoSuma de las líneas no gravadas.
Otros tributosSuma de los tributes[].subtotal.
TotalLa suma de todo lo anterior.
Desglose por alícuotaUna entrada por cada alícuota gravada presente en los ítems, el 0% incluido.

Cómo se llama cada uno de estos importes del lado de ARCA, si venís de una integración directa contra el webservice: Equivalencia de campos.

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"
}
Derivala de tu dominio, incluyendo lo que distingue al comprobante

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 }
}
}
CampoDescripción
idIdentificador interno. Es el que se usa en las rutas de PDF y email.
voucherNumberNúmero correlativo dentro del punto de venta y tipo.
caeCAE otorgado por ARCA.
totalAmountImporte total calculado.
arcaObservationsnull, o un array [{ code, msg }] con observaciones que ARCA devolvió aprobando igual el comprobante. Conviene registrarlas.
idempotencyHittrue si la clave ya correspondía a un comprobante existente.
Guardá 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.

CampoDescripción
grantedCupo total del período (los adicionales contratados ya están sumados).
usedConsumido, incluyendo este comprobante.
remaininggranted − used, con piso en 0. Nunca es negativo.
periodEndDía en que se renueva el cupo.
breakdownConsumo 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 estable

A 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:

HTTPcodeQué pasó¿Reintentar?
400VALIDATION_ERRORPayload inválido o punto de venta inexistenteNo, corregí el pedido
422ARCA_ERRORARCA rechazó el comprobanteNo, corregí el pedido
429VOUCHER_LOCK_TIMEOUTOtra emisión del mismo punto de venta sigue en cursoSí, con la misma idempotency
504—Se agotó el tiempo del pedidoSí, 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​

Factura C de servicios, con período, descuento y envío por email
{
"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": [
{ "email": "[email protected]", "name": "Pedro López", "pdfVersions": [1] }
],
"externalReference": "EXT-ORD-99882",
"legend": "Gracias por su compra"
}

Implementaciones completas en JavaScript, PHP y Python: Ejemplos.