Subir una factura y recoger el resultado
Qué se puede subir, cómo se dice si es compra o venta, el número de trabajo y cómo se consulta hasta que termina, respetando Retry-After.
Qué entra
Un PDF, JPEG o PNG de hasta 15 MB.
Se leen como mucho 5 páginas. Si el documento tiene más, el resultado llega con el aviso
PAGINAS_NO_LEIDAS.
Un documento con varias facturas se rechaza (MULTIFACTURA): un proceso es una factura.
Recibidas o emitidas. Lo que no es una factura (un ticket, una nómina, un extracto, un acta…) se rechaza
(NO_ES_FACTURA).
Compra o venta, siempre desde la empresa de la llave
En cada subida dices su tipo, y se dice desde el punto de vista de la empresa de la llave de procesar:
comprasi esa empresa recibe la factura (aparece como cliente; un proveedor se la emite);ventasi la emite (aparece como proveedor).
Decide cómo se lee la factura entera. El documento lo repite en direccion (RECIBIDA o EMITIDA). Si en el
papel la empresa aparece en el lado contrario, se lee igual como dijiste y llega con el aviso
TIPO_DISCREPANTE (lo cuenta la guía del resultado).
El NIF de la empresa es el de la llave: no se manda en la petición.
Subir y recoger, paso a paso
Sube la factura
POST /v1/trabajos, en multipart/form-data, con la llave de procesar, el tipo y el fichero en factura:
curl -sS https://api.munincloud.com/v1/trabajos \
-H "Authorization: Bearer $MC_LLAVE_PROCESAR" \
-H "Idempotency-Key: 7f6c1b0e-factura-0001" \
-F "tipo=compra" \
-F "factura=@factura.pdf;type=application/pdf"
La API contesta enseguida con un 202 y un número de trabajo:
{
"trabajo": "trb_01J9ZQ3K4T7W2B5N8C6D0F1G2H",
"estado": "en_curso",
"creado": "2026-10-02T16:18:24Z",
"pool": {"estado": "activa", "disponibles": 2412, "aviso_80": false}
}
Con las cabeceras Location (la URL del trabajo), Retry-After (los segundos que conviene esperar antes de
preguntar) y X-Pool-Disponibles / X-Pool-Estado (lo que le queda a la pool).
Si repites la subida con la misma cabecera Idempotency-Key (y la misma llave) en menos de 24 horas, recibes
el mismo trabajo y no se descuenta otra vez. Es un texto libre de 8 a 100 caracteres. Úsala si reintentas
tras un corte de red.
Consulta el trabajo hasta que deje de estar en curso
El proceso es asíncrono: tarda de segundos a unos minutos. GET /v1/trabajos/{trabajo} devuelve el trabajo
siempre con 200 mientras exista, en uno de tres estados:
en_curso: vuelve a preguntar tras los segundos deRetry-After;terminado:documentotrae la factura auditadav1, con susavisos;rechazado:errordice por qué.
El bucle del arranque rápido, con curl:
while :; do
curl -sS -D cabeceras.txt "$API/v1/trabajos/$TRABAJO" \
-H "Authorization: Bearer $(cat mc_llave_procesar)" > resultado.json
[ "$(campo estado < resultado.json)" = "en_curso" ] || break
ESPERA=$(sed -n 's/^[Rr]etry-[Aa]fter: *\([0-9]*\).*/\1/p' cabeceras.txt)
sleep "${ESPERA:-5}"
done
Y con Python, que además espera ante un 429 o un 503:
while True:
r = sesion.get(f"{API}/v1/trabajos/{trabajo}", timeout=30)
if r.status_code in (429, 503) or (r.status_code == 200 and r.json()["estado"] == "en_curso"):
time.sleep(int(r.headers.get("Retry-After", "5")))
continue
break
campo, $API, sesion y API son los del paso 0 de cada guion: los dos enteros están en la
documentación.
Terminado o rechazado
Un trabajo terminado trae el documento v1: cómo leerlo lo cuenta la
guía siguiente. Un trabajo rechazado trae el error, y no
descuenta de la pool:
{
"trabajo": "trb_01J9ZQ3K4T7W2B5N8C6D0F1G2K",
"estado": "rechazado",
"creado": "2026-10-02T16:20:00Z",
"terminado": "2026-10-02T16:20:31Z",
"error": {
"codigo": "MULTIFACTURA",
"mensaje": "El documento contiene más de una factura. Un proceso es una factura: súbalas por separado."
},
"pool": {"estado": "activa", "disponibles": 2412, "aviso_80": false}
}
En NO_ES_FACTURA, error.tipo_detectado dice qué se reconoció (TICKET, NOMINA, ONBOARDING o bancos),
o null si no se sabe.
No guardamos nada de la factura. El resultado se conserva 24 horas desde que termina (caduca dice
cuándo), para que lo recojas; después el trabajo ya no existe. Solo la llave que lo subió puede verlo: uno que
no existe, que es de otra llave o que ya caducó contesta lo mismo, 410 NO_ENCONTRADO.
Lo que puede contestar la subida
| HTTP | codigo | qué pasa |
|---|---|---|
| 401 | LLAVE_INVALIDA, LLAVE_ANULADA, IP_NO_PERMITIDA, LLAVE_SIN_PERMISO | la llave falta, no es válida, está anulada, se usa desde una IP no permitida o no sirve para esta ruta |
| 402 | POOL_AGOTADA, POOL_CERRADA | la pool no puede procesar |
| 413 | DOCUMENTO_NO_VALIDO | el fichero pasa de 15 MB |
| 415 | DOCUMENTO_NO_VALIDO | el fichero no es PDF, JPEG ni PNG |
| 422 | DOCUMENTO_NO_VALIDO, PETICION_NO_VALIDA | falta el fichero o está roto, o la petición está mal formada |
| 429 | RITMO_EXCEDIDO | demasiadas peticiones: espera Retry-After y reintenta |
| 500 | ERROR_INTERNO | un fallo nuestro; no se ha descontado nada y puedes reintentar |
| 503 | ERROR_INTERNO | el motor de lectura no está disponible ahora mismo: reintenta tras Retry-After; no se ha descontado nada |
Si el trabajo terminó pero en ese momento no se puede devolver el resultado, la consulta contesta 503 con
Retry-After: el trabajo sigue ahí; vuelve a consultar pasados esos segundos (dentro de las 24 horas).