# Invox · Facturación electrónica que opera tu agente.

_beta privada · Perú · SUNAT_

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.

valida → calcula → numera → envía → resuelve con SUNAT → guarda PDF, XML y CDR → envía el correo → avisa

## pásale esto a tu agente

```text
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.

```http
GET /v1
```

```json
{
  "name": "Invoicing",
  "description": "Electronic invoicing for Peru (SUNAT), built to be operated by agents.",
  "links": {
    "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>",
    "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.

```http
GET /v1
```

```text
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.

```http
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"
    }
  ]
}
```

```json
{
  "id": "00000000-0000-4000-8000-00000000e0e0",
  "object": "invoice",
  "environment": "test",
  "type": "factura",
  "series": "F001",
  "number": 123,
  "full_number": "F001-00000123",
  "status": "accepted",
  "settled": true,
  "status_reason": null,
  "currency": "PEN",
  "totals": {
    "taxable": "100.00",
    "exempt": "0.00",
    "unaffected": "0.00",
    "tax": "18.00",
    "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.

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

{
  "preview_token": "pv_…"
}
```

```json
{
  "code": "provider_account_suspended",
  "status": 503,
  "title": "The account with the invoicing provider is suspended",
  "retryable": "same_request",
  "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.",
  "next_steps": [
    "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.

```http
POST /v1/invoices
Test-Scenario: <escenario>
```

| | |
| --- | --- |
| `Test-Scenario: accepted` | The invoice becomes `accepted`. |
| `Test-Scenario: rejected` | The invoice becomes `rejected`. Its number was not used before SUNAT. |
| `Test-Scenario: pending` | The invoice stays `processing` — nothing resolves it by itself, however long you wait — until POST /v1/test/invoices/{id}/resolve decides. |
| `Test-Scenario: already_exists` | The service looks the number up, sees the document is its own, and the invoice becomes `accepted`. Exercises recovery without a duplicate. |
| `Test-Scenario: sunat_1033` | Its 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.

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

{
  "reason": "Error en el monto"
}
```

```json
{
  "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.",
  "next_steps": [
    "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.

```http
GET /v1/events?types=invoice.accepted,invoice.rejected&wait=20
```

| | |
| --- | --- |
| `invoice.accepted` | SUNAT accepted the invoice. |
| `invoice.rejected` | SUNAT rejected the invoice. Its number was not used. |
| `invoice.voided` | SUNAT accepted the void. The invoice is voided. |
| `invoice.files_stored` | The files of the invoice (XML, CDR, PDF) are stored and can be downloaded. |
| `invoice.email_sent` | An 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.

```http
GET /v1/readiness
```

```text
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.

[Pedir acceso](mailto:access@invox.page?subject=Acceso%20a%20la%20beta)

## 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`).

- [API](https://api.invox.page/v1)
- [Contrato (OpenAPI)](https://api.invox.page/v1/openapi.json)
- [Guías](https://api.invox.page/v1/docs)
- [Errores](https://api.invox.page/v1/errors)
- [Eventos](https://api.invox.page/v1/event_types)
