Documentación · v1.0.0
ModoFactura API
Esta documentación se arma con el contrato de la API: si el contrato cambia, la página cambia con él. La referencia de cada endpoint está en Referencia, y el contrato completo, en openapi.yaml.
Antes de integrar, tu CUIT tiene que estar preparado en ARCA: los pasos están en Alta de clientes.
Cómo funciona
Pedido y consulta de comprobantes electrónicos ante ARCA.
ARCA puede tardar o caerse, así que la API no promete la factura en el momento: POST acepta el
pedido y responde 202, y GET dice cómo sigue. Mientras esté procesando hay que volver a
consultar; ese estado no tiene un plazo garantizado. Si en_revision es true, el comprobante espera
a una persona: sigue procesando hasta que se resuelva, y no hace falta pedirlo de nuevo.
Un comprobante que una persona de ModoFactura cierra sin mandarlo a ARCA queda descartado: no tiene
número ni CAE y no cambia más. Si corresponde, se pide de nuevo con otra Idempotency-Key.
La API puede sumar campos, tipos de evento y valores de estado nuevos sin cambiar de versión: hay que
ignorar los campos y eventos que no se conozcan, y tratar un estado desconocido como final y sin CAE.
Un webhook de un tipo desconocido se contesta con un 2xx igual.
Sólo factura A y B, y sus notas de crédito. Una nota de crédito se pide sobre la factura que corrige,
con POST /v1/comprobantes/{id}/notas-de-credito, y sale por la misma cola: se consulta igual que una
factura. Los importes los calcula el servidor a partir del precio final de cada renglón.
El PDF de un comprobante emitido se descarga de /v1/comprobantes/{id}/pdf: sale siempre de lo que
autorizó ARCA, y el mismo comprobante da siempre el mismo archivo.
En vez de consultar, se puede registrar un webhook con PUT /v1/webhook: el resultado de cada
comprobante llega firmado a la URL del cliente. La forma de cada evento está en webhooks.
Ambientes
| URL base | Ambiente |
|---|---|
https://autoinvoice.com.ar | PROD, sólo acepta claves mf_live_ |
https://test.autoinvoice.com.ar | TEST, sólo acepta claves mf_test_ |
Autenticación
Authorization: Bearer <clave>. La clave es mf_live_ (PROD) o mf_test_ (TEST) seguida de 43
caracteres base64url.
Encabezados de pedido
Idempotency-Key
Clave elegida por el cliente, única por pedido.
- POST
/v1/comprobantesPedir un comprobante - POST
/v1/comprobantes/{id}/notas-de-creditoPedir una nota de crédito sobre una factura
Repetir el pedido con el mismo
Idempotency-Keyy el mismo cuerpo devuelve el mismo comprobante, en su estado actual, y no crea otro. La comparación es por contenido: el orden de los campos o escribir121en vez de121.00no cambia el pedido. La misma clave con otro cuerpo es un409. Ante un500o un corte, hay que repetir con la misma clave.
Webhooks
Resultado de un comprobante
Cada POST lleva X-ModoFactura-Firma: t=<unix>,v1=<hex>: HMAC-SHA256 en hexadecimal, con el
secreto entero como clave, sobre <t>.<cuerpo crudo>. Hay que verificarlo contra los bytes
recibidos, antes de interpretar el JSON, y rechazar un t con más de 5 minutos de diferencia. Cada
intento firma con su propio t. La documentación trae un ejemplo de verificación.
La entrega es «al menos una vez» y sin orden garantizado: un mismo evento puede llegar más de una
vez, siempre con el mismo id, y conviene deduplicar por ese campo. comprobante.estado es el
estado al momento de entregar. Cualquier 2xx confirma; todo lo demás, también un 3xx, se
reintenta con espera creciente durante 24 horas. No se siguen redirecciones, y el pedido se corta a
los 10 segundos.
comprobante.emitido: ARCA autorizó, y trae número y CAE. comprobante.rechazado: ARCA dijo que no;
motivo trae lo mismo que rechazo en el GET. comprobante.error_datos: los datos del comprobante
no se pueden enviar a ARCA; sigue procesando hasta que lo resuelva una persona.
comprobante.en_revision: espera la revisión de una persona; sigue procesando y no hace falta pedirlo
de nuevo. Estos dos últimos no se entregan si el comprobante se resolvió mientras esperaban: llega el
resultado. comprobante.descartado: ModoFactura lo cerró sin mandarlo a ARCA; no tiene número ni CAE y
no va a cambiar más.
- POST
/v1/comprobantes/{id}/webhook/reenviarVolver a enviar el resultado de un comprobante - GET
/v1/webhookConsultar el webhook de la cuenta - PUT
/v1/webhookDar de alta el webhook o cambiar su URL - DELETE
/v1/webhookBorrar el webhook de la cuenta - POST
/v1/webhook/secretoRotar el secreto del webhook
Verificar la firma
Un ejemplo en Node.js: es el que menciona la descripción del webhook, y el mismo código que corre la suite de pruebas de ModoFactura.
const { createHmac, timingSafeEqual } = require('node:crypto');
const TOLERANCIA_S = 300;
/**
* `crudo` son los bytes recibidos. Con Express: `express.raw({ type: 'application/json' })`.
* `firma` es el encabezado X-ModoFactura-Firma.
*/
function verificarFirma({ secreto, crudo, firma, ahora = Date.now() }) {
const partes = Object.fromEntries(String(firma).split(',').map((p) => p.split('=', 2)));
if (!/^\d+$/.test(partes.t ?? '') || !partes.v1) return false;
if (Math.abs(ahora / 1000 - Number(partes.t)) > TOLERANCIA_S) return false;
const esperada = createHmac('sha256', secreto).update(`${partes.t}.`).update(crudo).digest();
const recibida = Buffer.from(partes.v1, 'hex');
return recibida.length === esperada.length && timingSafeEqual(recibida, esperada);
} Errores
Los errores llegan como application/problem+json.
Cuerpos posibles: Problema, ProblemaDeValidacion.
Problema
RFC 9457.
| Campo | Tipo | Descripción |
|---|---|---|
typeobligatorio | string | |
titleobligatorio | string | |
statusobligatorio | integer | |
detailopcional | string |
- No admite campos que no estén en esta tabla.
ProblemaDeValidacion
RFC 9457, con la lista de campos a corregir.
| Campo | Tipo | Descripción |
|---|---|---|
typeobligatorio | string | |
titleobligatorio | string | |
statusobligatorio | integer | |
erroresobligatorio | object[] |
|
errores[].campoobligatorio | string | Ruta del campo, por ejemplo |
errores[].mensajeobligatorio | string |
- No admite campos que no estén en esta tabla.
400
-
Falta el
Idempotency-Key, es inválido, o el cuerpo no es JSON válido. -
El cuerpo no es JSON válido.
401
-
La clave falta, es inválida, está revocada o es de otro ambiente. La respuesta no distingue cuál.
404
-
No hay un comprobante de este cliente con ese id. La respuesta no distingue si existe para otro.
-
La cuenta no tiene un webhook registrado.
409
-
El
Idempotency-Keyya se usó con otro cuerpo, o a la cuenta le falta el punto de venta o sus datos fiscales de emisor.titledice cuál. No se encoló nada. -
El comprobante no es una factura o todavía no está emitido; se emitió con un CUIT distinto del que tiene hoy la cuenta; la nota superaría a la factura sumada a las otras; el
Idempotency-Keyya se usó con otro cuerpo; o a la cuenta le falta el punto de venta o sus datos fiscales.titledice cuál. No se encoló nada. -
No hay PDF para servir.
titledice por qué: el comprobante no está emitido (todavía, o fue rechazado); se creó sin los datos del emisor; o el archivo generado no coincide con el original, caso en el que no se sirve y lo revisa una persona. Reintentar sólo sirve en el primer caso, cuando el comprobante esté emitido. -
No hay nada que enviar.
titledice por qué: la cuenta no tiene webhook registrado, o el comprobante todavía no tiene un resultado.
413
-
El cuerpo supera los 256 KB.
415
-
El cuerpo no es
application/json, o viene comprimido.
422
-
El pedido está bien formado pero no se puede emitir.
erroresdice por qué. No se encoló nada. -
La URL no se admite. Un destino que no resuelve y uno interno dan el mismo error.
429
-
Se superó el límite de pedidos del cliente, su tope de pedidos sin procesar, el de autenticaciones fallidas de la IP, o hay demasiados PDF generándose a la vez. Por defecto, 300 pedidos por minuto por cliente, 1000 sin procesar por cliente y 20 autenticaciones fallidas por minuto por IP. Reintentar con el mismo Idempotency-Key es seguro.
500
-
Error interno, sin detalles. Se puede reintentar; en un
POST, con el mismoIdempotency-Key.
503
-
La entrega de webhooks no está encendida en este ambiente. No se registró ni se encoló nada.