Invox

beta privada · Perú · SUNAT

Facturación electrónica que opera tu agente.

Un API para emitir facturas y boletas ante SUNAT, hecho para que un agente lo integre, lo pruebe y lo lleve a producción en tu nombre. Tú apruebas lo que importa.

validacalculanumeraenvíaresuelve con SUNATguarda PDF, XML y CDRenvía el correoavisa

pásale esto a tu agente
Integra facturación electrónica para Perú (SUNAT) con este API: https://api.invox.page/v1

1. Empieza por GET https://api.invox.page/v1: dice cómo conseguir una credencial (yo confirmo el inicio de sesión en mi navegador) y dónde están el contrato y las guías.
2. Trabaja en la organización de pruebas hasta que todo funcione, también lo que sale mal: provoca un rechazo, una respuesta pendiente y una cuenta del proveedor suspendida, y comprueba que mi sistema maneja cada caso.
3. Antes de anular o corregir un comprobante, muéstrame la vista previa y espera mi aprobación.
4. Al terminar, dime qué falta para producción (GET https://api.invox.page/v1/readiness) y quién debe hacer cada cosa.

La sesión de un agente, paso a paso

Cada paso trae su prueba: la respuesta real del API, tomada de producción. Nada de lo que ves abajo está escrito a mano.

01

Se descubre solo

Tu agente empieza con una sola URL. Sin credencial, GET /v1 le dice cómo autenticarse y dónde están el contrato, las guías y los catálogos de errores y de eventos.

solicitud
GET /v1
respuesta
{
"name": "Invoicing",
"description": "Electronic invoicing for Peru (SUNAT), built to be operated by agents.",
"links": { ← el mapa
"openapi": "https://api.invox.page/v1/openapi.json",
"docs": "https://api.invox.page/v1/docs",
"errors": "https://api.invox.page/v1/errors",
"event_types": "https://api.invox.page/v1/event_types"
},
"authentication": {
"header": "Authorization: Bearer <credential>", ← cómo autenticarse
"credentials": {
"person_access_token": {
"what": "A person signs in once in a browser and the client receives a short-lived access token and a refresh token."
},
"service_account_key": {
"what": "A key of a service account of one organization (`sk_test_…` or `sk_live_…`). It acts only on that organization, with the scopes of its service account."
}
}
}
}
respuesta real · GET /v1 · extracto · tomada el 14 de noviembre de 2026
02

La credencial, sin pantallas ni secretos en el chat

La persona confirma una vez en su navegador y el agente recibe su credencial: nunca un secreto pegado en la conversación. Para un servidor, una llave de cuenta de servicio que se muestra una sola vez.

solicitud
GET /v1
respuesta
1. POST `device_authorization_endpoint` with `client_id` (form-encoded).
2. Show `verification_uri_complete` to the person and wait for them to confirm.
3. Poll `token_endpoint` with `grant_type=device_grant_type`, `device_code` and `client_id` every `interval` seconds.
4. Use `access_token` as the Bearer credential; when it expires, exchange `refresh_token` at `token_endpoint` with `grant_type=refresh_grant_type`.
5. Never print the tokens; keep them in the operating system's credential store.
respuesta real · GET /v1 → authentication.credentials.person_access_token.how_to_obtain · tomada el 14 de noviembre de 2026
03

Una llamada por comprobante

El servicio valida, calcula el IGV, toma el número de la serie y lo envía. Lo que diga SUNAT llega como estado del comprobante, nunca como un error HTTP. Repetir la llamada con la misma Idempotency-Key nunca duplica nada.

solicitud
POST /v1/invoices?wait=10
Authorization: Bearer sk_test_…
Idempotency-Key: venta-0001
Test-Scenario: accepted

{
  "type": "factura",
  "prices_include_tax": false,
  "customer": {
    "id_type": "ruc",
    "id_number": "20601188750",
    "name": "CLIENTE S.A.C."
  },
  "items": [
    {
      "description": "Soporte técnico mensual",
      "quantity": "1",
      "unit": "ZZ",
      "unit_price": "100.00"
    }
  ]
}
respuesta
{
"id": "00000000-0000-4000-8000-00000000e0e0",
"object": "invoice",
"environment": "test",
"type": "factura",
"series": "F001",
"number": 123,
"full_number": "F001-00000123", ← numerado por el servicio
"status": "accepted", ← aceptado por SUNAT
"settled": true,
"status_reason": null,
"currency": "PEN",
"totals": {
"taxable": "100.00",
"exempt": "0.00",
"unaffected": "0.00",
"tax": "18.00", ← IGV calculado
"total": "118.00"
},
"customer": {
"id_type": "ruc",
"id_number": "20601188750",
"name": "CLIENTE S.A.C."
},
"external_ref": null,
"created_at": "2026-01-15T15:04:05.000Z"
}
forma real del contrato · GET /v1/event_types → invoice.accepted.example.data.invoice · tomada el 14 de noviembre de 2026
04

Errores que enseñan

Cada error dice qué pasó, qué NO pasó (si se creó algo, si se gastó un número) y qué hacer. Una cuenta suspendida en el proveedor nunca parece un error del comprobante: lo que estaba en proceso sigue en proceso.

solicitud
POST /v1/invoices/{id}/correction
Idempotency-Key: correccion-0001

{
  "preview_token": "pv_…"
}
respuesta
{
"code": "provider_account_suspended",
"status": 503,
"title": "The account with the invoicing provider is suspended", ← qué pasó
"retryable": "same_request", ← ¿reintentar?
"effect": "Nothing was created or changed, and no invoice number was used. No invoice is marked failed or rejected because of the account: what is in process stays in process.", ← qué NO pasó
"next_steps": [ ← qué hacer
"Nothing in the request is wrong. Send the same request again after `Retry-After`.",
"GET /v1/provider shows the state of the account and since when it is blocked.",
"Invoices can still be issued: they are taken (202) and sent by themselves when the account recovers."
]
}
respuesta real · GET /v1/errors/provider_account_suspended · extracto · tomada el 14 de noviembre de 2026
05

El modo Pruebas también prueba lo que sale mal

Cada organización nace con una gemela de pruebas y un proveedor simulado. Con una cabecera, tu agente provoca el resultado que necesite y ve cómo el servicio se recupera, sin tocar nada real.

solicitud
POST /v1/invoices
Test-Scenario: <escenario>
respuesta
Test-Scenario: acceptedThe invoice becomes `accepted`.Test-Scenario: rejectedThe invoice becomes `rejected`. Its number was not used before SUNAT.Test-Scenario: pendingThe invoice stays `processing` — nothing resolves it by itself, however long you wait — until POST /v1/test/invoices/{id}/resolve decides.Test-Scenario: already_existsThe service looks the number up, sees the document is its own, and the invoice becomes `accepted`. Exercises recovery without a duplicate.Test-Scenario: sunat_1033Its number is NEVER changed automatically. The invoice stays `processing` while SUNAT is asked again (three times, ten minutes apart: skip each wait with POST /v1/test/invoices/{id}/advance); then it ends `failed` with `status_reason: number_already_registered`. That reason says how it is corrected to the next number.
respuesta real · GET /v1/docs/test-mode · extracto · tomada el 14 de noviembre de 2026
06

Lo irreversible se ve antes

Anular y corregir exigen el comprobante firmado de una vista previa. El agente se la muestra a la persona, la persona aprueba, y sin ella el servidor se niega.

solicitud
POST /v1/invoices/{id}/void
Idempotency-Key: anulacion-0001

{
  "reason": "Error en el monto"
}
respuesta
{
"code": "preview_token_required",
"status": 400,
"title": "This action needs the token of its preview",
"retryable": "after_fixing_request",
"effect": "The action was not performed. Nothing was created, sent or changed.", ← qué NO pasó
"next_steps": [ ← qué hacer
"Ask for the preview of the action first (POST /v1/invoices/{id}/void/preview, GET /v1/invoices/{id}/correction): its answer carries `preview_token`.",
"Show the preview to the person who approves, then send the action with that `preview_token`."
]
}
respuesta real · GET /v1/errors/preview_token_required · extracto · tomada el 14 de noviembre de 2026
07

Se sigue sin una URL pública

Una consulta de eventos que espera responde en cuanto algo ocurre, sin exponer nada. Si tienes una URL, los webhooks siguen Standard Webhooks y se verifican con la librería oficial.

solicitud
GET /v1/events?types=invoice.accepted,invoice.rejected&wait=20
respuesta
invoice.acceptedSUNAT accepted the invoice.invoice.rejectedSUNAT rejected the invoice. Its number was not used.invoice.voidedSUNAT accepted the void. The invoice is voided.invoice.files_storedThe files of the invoice (XML, CDR, PDF) are stored and can be downloaded.invoice.email_sentAn email about the invoice was sent.
respuesta real · GET /v1/event_types · extracto · tomada el 14 de noviembre de 2026
08

¿Qué me falta para producción?

El API responde la lista de lo pendiente y quién debe hacer cada cosa: tu agente, una persona de tu organización o el proveedor.

solicitud
GET /v1/readiness
respuesta
What is still missing to issue for real, and who must do each thing

Answerable with a credential of either half of a pair (live or test), and always about the pair. `requirements` lists what production needs, each with its `owner` — `agent` (doable through this API), `person` or `provider` — whether it is `blocking`, and `how`. `integration_evidence` is what the API has seen the integration exercise in the TEST organization (a rejection, a blocked provider account, a webhook acknowledged); events fabricated with the test tools do not count. It advises: nothing is allowed or refused because of this answer.
respuesta real · GET /v1/openapi.json → GET /v1/readiness · tomada el 14 de noviembre de 2026

Lo que todavía no hace

Preferimos decirlo antes de que lo descubras:

  • Un solo proveedor: Bizlinks.
  • Solo soles (PEN) y pago al contado.
  • Sin notas de crédito ni de débito, y sin detracción.
  • Sin servidor MCP todavía: hoy se integra por HTTP.
  • Beta privada, sin precios publicados.

Beta privada

Hoy lo usamos en nuestras propias empresas. Si quieres probarlo con la tuya, escríbenos con tu nombre, tu empresa y su RUC, y te enviamos la invitación.

Para agentes

Tu agente tiene su propia versión de esta página, escrita para él: la raíz de este sitio (también en Markdown con Accept: text/markdown, y /llms.txt).