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 baseAmbiente
https://autoinvoice.com.arPROD, sólo acepta claves mf_live_
https://test.autoinvoice.com.arTEST, sólo acepta claves mf_test_

Autenticación

Esquema http, bearer.

Authorization: Bearer <clave>. La clave es mf_live_ (PROD) o mf_test_ (TEST) seguida de 43 caracteres base64url.

Todas las respuestas llevan Cache-Control: no-store. Piden la clave 9 de 9 operaciones. Una clave rechazada responde 401.

Encabezados de pedido

Además de la clave, estas operaciones piden encabezados propios.

Idempotency-Key

Clave elegida por el cliente, única por pedido.

Tipo string . Patrón: ^[\x21-\x7e]{1,255}$

Lo piden:

Cómo se comporta, según Pedir un comprobante:

Repetir el pedido con el mismo Idempotency-Key y 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 escribir 121 en vez de 121.00 no cambia el pedido. La misma clave con otro cuerpo es un 409. Ante un 500 o 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.

Encabezado X-ModoFactura-Firma, patrón ^t=[0-9]+,v1=[0-9a-f]{64}$.

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.

La forma completa del evento está en la referencia.

Para administrar el webhook de tu cuenta:

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.

Verificación de X-ModoFactura-Firmajs
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.

CampoTipoDescripción
typeobligatoriostring
titleobligatoriostring
statusobligatoriointeger
detailopcionalstring
  • No admite campos que no estén en esta tabla.

ProblemaDeValidacion

RFC 9457, con la lista de campos a corregir.

CampoTipoDescripción
typeobligatoriostring
titleobligatoriostring
statusobligatoriointeger
erroresobligatorioobject[]
  • Elementos: desde 1
errores[].campoobligatoriostring

Ruta del campo, por ejemplo renglones.0.importe.

errores[].mensajeobligatoriostring
  • No admite campos que no estén en esta tabla.

400

401

  • La clave falta, es inválida, está revocada o es de otro ambiente. La respuesta no distingue cuál.

    Encabezados: WWW-Authenticate.

    En todas las operaciones.

404

409

  • El Idempotency-Key ya se usó con otro cuerpo, o a la cuenta le falta el punto de venta o sus datos fiscales de emisor. title dice cuál. No se encoló nada.

    En: POST /v1/comprobantes.

  • 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-Key ya se usó con otro cuerpo; o a la cuenta le falta el punto de venta o sus datos fiscales. title dice cuál. No se encoló nada.

    En: POST /v1/comprobantes/{id}/notas-de-credito.

  • No hay PDF para servir. title dice 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.

    En: GET /v1/comprobantes/{id}/pdf.

  • No hay nada que enviar. title dice por qué: la cuenta no tiene webhook registrado, o el comprobante todavía no tiene un resultado.

    En: POST /v1/comprobantes/{id}/webhook/reenviar.

415

422

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.

    Encabezados: Retry-After (Segundos hasta poder volver a intentar.).

    En todas las operaciones.

500

  • Error interno, sin detalles. Se puede reintentar; en un POST, con el mismo Idempotency-Key.

    En todas las operaciones.

503