# Bilnex Partner API — agents.md

Bilnex is an e-invoicing platform for Baltic/Nordic SMEs: it sends sales invoices as PDF-by-email, Estonian operator-channel e-invoices, or Peppol; receives purchase e-invoices; AI-digitizes PDF purchase invoices; and pushes documents to ERPs. The Partner API (`/partner/v1`) lets an ERP embed all of this behind a single API key.

This file is the entry point for coding agents building a Bilnex integration. Follow it top to bottom.

## You don't need an account to get started

1. `POST https://sandbox.bilnex.io/partner/v1/sandbox` with your email and ERP name (no signup, no login). You get an API key, two pre-provisioned Estonian test companies (a sender and a receiver), and three fixture counterparties.
2. Build the integration against `https://sandbox.bilnex.io` using that key. Sandbox email is intercepted (never delivered) and e-invoice/Peppol rails are simulated instantly, so every flow is testable end to end.
3. Poll `GET /partner/v1/sandbox/verification` until `readyForProduction` is `true` — that is the definition of done. Then request a production key (see [go-live](https://sandbox.bilnex.io/docs/go-live.md)).

If you are a human handing this to a coding agent, paste this prompt:

```text
Build a Bilnex e-invoicing integration into this codebase.
1. Read https://sandbox.bilnex.io/agents.md and follow it exactly.
2. Provision your own sandbox key: POST https://sandbox.bilnex.io/partner/v1/sandbox with {"email":"<my email>","erpName":"<this ERP's name>","ref":"agent"} — no account needed. Store the key in server-side config only, never in frontend code or the repo.
3. Implement outbound invoicing (capability check, e-invoice send, EMAIL/PDF fallback) and inbound purchase invoices (poll with `since`, then mark-exported), wired into this codebase's existing models.
4. Test every flow against the sandbox, not with mocks.
Do not report the integration complete until GET /partner/v1/sandbox/verification returns "readyForProduction": true. If a check fails, read its fixHint and docsUrl, fix, and re-run.
```

## Machine-readable resources

- `https://sandbox.bilnex.io/llms.txt` — llms.txt index of every docs page with descriptions.
- `https://sandbox.bilnex.io/partner/v1/openapi.json` — OpenAPI 3.1 spec for `/partner/v1`.
- `https://sandbox.bilnex.io/.well-known/agent-skills/index.json` — agent-skills discovery index. Install the integration skill with `npx skills add https://sandbox.bilnex.io`.
- `https://sandbox.bilnex.io/skills/bilnex-erp-integration/SKILL.md` — the skill itself (build procedure, routing table, references).
- Every page under `/docs/` is also plain markdown: append `.md` to any docs URL to get the raw source.

## Environments and keys

| | Sandbox | Production |
|---|---|---|
| Base URL | `https://sandbox.bilnex.io` | `https://api.app.bilnex.io` |
| Key prefix | `blx_sbx_` | `blx_live_` |
| How issued | Self-serve `POST /partner/v1/sandbox` | Issued by Bilnex (partners@bilnex.io) |
| Key lifetime | 2 days (one-time extension to 30 via `/partner/v1/sandbox/claim`) | Until rotated/revoked |
| Email | Intercepted, readable at `/partner/v1/sandbox/emails`, never delivered | Really delivered |
| E-invoice/Peppol rails | Simulated, instant | Real operator/Peppol network |
| `/partner/v1/sandbox/*` endpoints | Available | 404 |

Keys are host-bound: a `blx_sbx_` key on `api.app.bilnex.io` (or `blx_live_` on `sandbox.bilnex.io`) returns 401 with a hint naming the correct host. Paths and payloads are identical on both hosts. API version: `2026-08-01`.

Auth on every authenticated request: `Authorization: Bearer <YOUR_API_KEY>` (the header `X-API-Key: <YOUR_API_KEY>` is also accepted). The key acts as its sender company by default; add `X-Company-Id: <uuid>` to act as another company the key owns (a company it does not own returns 403 `company_not_owned`).

## Quickstart — the whole happy path, in order

Run these 13 steps in order. Each step names the [verification check(s)](https://sandbox.bilnex.io/docs/verification.md) it flips to `pass`. Checks are sticky — once passed they stay passed until `POST /partner/v1/sandbox/reset`.

### 1. Provision a sandbox key (no auth)

```bash
curl -s -X POST https://sandbox.bilnex.io/partner/v1/sandbox \
  -H "Content-Type: application/json" \
  -d '{"email":"dev@erp.example","erpName":"AwesomeERP","ref":"agent"}'
```

Response `201 Created`:

```json
{
  "apiKey": "blx_sbx_9tK4mQv7Xw2eR5pLnA8cJ1bYfD3hG6sZ",
  "keyId": "7c1f0a52-3b8d-4e69-9a24-5d0e7b61c3af",
  "keyPrefix": "blx_sbx_9tK4",
  "expiresAt": "2026-08-13T09:00:00Z",
  "limits": {
    "requestsPerMinute": 60,
    "requestsPerDay": 5000,
    "maxInvoices": 500,
    "maxEmails": 200
  },
  "companies": {
    "sender": {
      "companyId": "b7e486ae-59f0-4f3a-9d5c-1e8a2f6c4d21",
      "name": "Valge Klaar OÜ",
      "regCode": "95000002",
      "peppolId": "0191:95000002"
    },
    "receiver": {
      "companyId": "4a1c7b9e-2d5f-4e8a-b3c6-9f0d7e5a8b42",
      "name": "Sinilille Kaubandus OÜ",
      "regCode": "95000003"
    }
  },
  "fixtures": [
    { "name": "Sandbox Capable OÜ", "regCode": "95999901", "behavior": "accepts_einvoice" },
    { "name": "Sandbox Paper OÜ", "regCode": "95999902", "behavior": "not_einvoice_capable" },
    { "name": "Sandbox Rejector OÜ", "regCode": "95999903", "behavior": "rejects_einvoice" }
  ],
  "urls": {
    "docs": "https://sandbox.bilnex.io/docs/quickstart.md",
    "verification": "https://sandbox.bilnex.io/partner/v1/sandbox/verification",
    "openapi": "https://sandbox.bilnex.io/partner/v1/openapi.json",
    "claim": "https://sandbox.bilnex.io/partner/v1/sandbox/claim"
  }
}
```

`apiKey` is shown exactly once — store it in server-side config now (environment variable or secret store). Never write it into the repo. In every step below, replace `<YOUR_API_KEY>` with it and `<RECEIVER_COMPANY_ID>` with `companies.receiver.companyId`.

Satisfies: nothing yet (provisioning is the prerequisite).

### 2. Verify auth works

```bash
curl -s https://sandbox.bilnex.io/partner/v1/me \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

Response `200 OK`:

```json
{
  "environment": "sandbox",
  "keyId": "7c1f0a52-3b8d-4e69-9a24-5d0e7b61c3af",
  "keyExpiresAt": "2026-08-13T09:00:00Z",
  "actingCompany": {
    "companyId": "b7e486ae-59f0-4f3a-9d5c-1e8a2f6c4d21",
    "name": "Valge Klaar OÜ",
    "regCode": "95000002",
    "role": "ADMIN"
  },
  "companies": [
    { "companyId": "b7e486ae-59f0-4f3a-9d5c-1e8a2f6c4d21", "role": "ADMIN" },
    { "companyId": "4a1c7b9e-2d5f-4e8a-b3c6-9f0d7e5a8b42", "role": "ADMIN" }
  ],
  "limits": {
    "requestsPerMinute": 60,
    "requestsPerDay": 5000,
    "maxInvoices": 500,
    "maxEmails": 200
  },
  "usage": {
    "requestsToday": 148,
    "sendsToday": 3,
    "emailsCaptured": 5
  }
}
```

Satisfies: `AUTH_OK`.

### 3. Capability check — a capable receiver

```bash
curl -s https://sandbox.bilnex.io/partner/v1/customers/95000003/e-invoice-capability \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

Response `200 OK`:

```json
{
  "regCode": "95000003",
  "name": "Sinilille Kaubandus OÜ",
  "eInvoiceCapable": true,
  "operator": "Finbite",
  "peppolCapable": true,
  "peppolId": "0191:95000003",
  "recommendedChannel": "E_INVOICE",
  "checkedAt": "2026-08-06T09:02:10Z"
}
```

(`95999901` — Sandbox Capable OÜ — also returns `eInvoiceCapable: true` and works for this check.)

Satisfies: `CAPABILITY_CHECKED_CAPABLE`.

### 4. Capability check — a NOT capable receiver

```bash
curl -s https://sandbox.bilnex.io/partner/v1/customers/95999902/e-invoice-capability \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

Response `200 OK`:

```json
{
  "regCode": "95999902",
  "name": "Sandbox Paper OÜ",
  "eInvoiceCapable": false,
  "operator": null,
  "peppolCapable": false,
  "peppolId": null,
  "recommendedChannel": "EMAIL",
  "checkedAt": "2026-08-06T09:02:41Z"
}
```

Your integration must branch on `eInvoiceCapable`: `false` means fall back to `EMAIL`. Never assume capability without this call.

Satisfies: `CAPABILITY_CHECKED_NOT_CAPABLE`.

### 5. Create an invoice (addressed to the receiver company)

```bash
curl -s -X POST https://sandbox.bilnex.io/partner/v1/invoices \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6d1f9a3e-8c47-4b2a-9d05-e1f7a2c4b8d0" \
  -d '{
    "customer": {
      "name": "Sinilille Kaubandus OÜ",
      "regCode": "95000003",
      "email": "arved@sinilille.example",
      "address": "Sinilille tee 4, 10115 Tallinn, Estonia"
    },
    "date": "2026-08-06",
    "dueDate": "2026-08-20",
    "currency": "EUR",
    "language": "ET",
    "items": [
      {
        "description": "Consulting services, July 2026",
        "quantity": 10.00,
        "unit": "h",
        "price": 95.00,
        "vatRate": 24
      }
    ]
  }'
```

Response `201 Created`:

```json
{
  "id": "8c2a91d4-6e3b-4f7a-a1d9-5b8e2c7f0a63",
  "number": "2026-0001",
  "status": "SAVED",
  "category": "PDF",
  "date": "2026-08-06",
  "dueDate": "2026-08-20",
  "currency": "EUR",
  "language": "ET",
  "customer": {
    "name": "Sinilille Kaubandus OÜ",
    "regCode": "95000003",
    "email": "arved@sinilille.example",
    "address": "Sinilille tee 4, 10115 Tallinn, Estonia"
  },
  "items": [
    {
      "description": "Consulting services, July 2026",
      "quantity": 10.00,
      "unit": "h",
      "price": 95.00,
      "vatRate": 24,
      "totalPrice": 950.00
    }
  ],
  "netAmount": 950.00,
  "vatAmount": 228.00,
  "vatSummary": [
    { "rate": 24, "base": 950.00, "amount": 228.00 }
  ],
  "totalAmount": 1178.00,
  "createdDate": "2026-08-06T09:05:12Z"
}
```

The server computes `totalPrice`, `netAmount`, `vatAmount`, `vatSummary`, `totalAmount`. Store the server's values — never recompute header totals from line items in your ERP.

Satisfies: `INVOICE_CREATED`.

### 6. Fetch the invoice PDF

```bash
curl -s https://sandbox.bilnex.io/partner/v1/invoices/8c2a91d4-6e3b-4f7a-a1d9-5b8e2c7f0a63/pdf \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

Response `200 OK`:

```json
{
  "pdfUrl": "https://sandbox.bilnex.io/files/invoices/8c2a91d4-6e3b-4f7a-a1d9-5b8e2c7f0a63.pdf?sig=6f0a2b7c8d15e3a9&exp=1786359912",
  "expiresAt": "2026-08-06T09:20:12Z"
}
```

The URL is presigned and short-lived — download immediately, do not persist the URL.

Satisfies: `PDF_FETCHED`.

### 7. Send it as an e-invoice

```bash
curl -s -X POST https://sandbox.bilnex.io/partner/v1/invoices/8c2a91d4-6e3b-4f7a-a1d9-5b8e2c7f0a63/send \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 0b3c8e5d-2f7a-4c19-8a6b-d4e1f9a0c752" \
  -d '{"channel":"E_INVOICE"}'
```

Response `200 OK`:

```json
{
  "id": "8c2a91d4-6e3b-4f7a-a1d9-5b8e2c7f0a63",
  "status": "SENT",
  "category": "E_INVOICE",
  "channel": "E_INVOICE",
  "sentAt": "2026-08-06T09:06:30Z"
}
```

`SENT` is TERMINAL for `E_INVOICE` and `PEPPOL` — there is no `DELIVERED` status and no read receipt. Do not wait or poll for anything further on this invoice.

Satisfies: `EINVOICE_SENT`.

### 8. Switch to the receiver company and list its purchase invoices

The e-invoice from step 7 arrives at Sinilille Kaubandus OÜ instantly in sandbox. Act as the receiver with `X-Company-Id`:

```bash
curl -s "https://sandbox.bilnex.io/partner/v1/purchase-invoices?since=2026-08-06T00:00:00Z" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "X-Company-Id: <RECEIVER_COMPANY_ID>"
```

Response `200 OK`:

```json
{
  "items": [
    {
      "id": "d94b7f2a-1c5e-4a8b-9e3d-6f0a2b7c8d15",
      "status": "NEW",
      "source": "E_INVOICE",
      "sender": { "name": "Valge Klaar OÜ", "regCode": "95000002" },
      "invoiceNumber": "2026-0001",
      "date": "2026-08-06",
      "dueDate": "2026-08-20",
      "currency": "EUR",
      "netAmount": 950.00,
      "vatAmount": 228.00,
      "totalAmount": 1178.00,
      "receivedAt": "2026-08-06T09:06:31Z"
    }
  ],
  "page": 1,
  "pageSize": 50,
  "hasMore": false,
  "nextSince": "2026-08-06T09:06:31Z"
}
```

Persist `nextSince` and pass it as `since` on the next poll. Receiving is pull-based — there are no webhooks. Dedupe by `id`.

Satisfies: `EINVOICE_RECEIVED_AT_RECEIVER`, `PURCHASE_LISTED`.

### 9. Mark the purchase invoice exported

After importing the document into the ERP, always confirm the import:

```bash
curl -s -X POST https://sandbox.bilnex.io/partner/v1/purchase-invoices/d94b7f2a-1c5e-4a8b-9e3d-6f0a2b7c8d15/mark-exported \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "X-Company-Id: <RECEIVER_COMPANY_ID>"
```

Response `200 OK`:

```json
{
  "id": "d94b7f2a-1c5e-4a8b-9e3d-6f0a2b7c8d15",
  "status": "EXPORTED",
  "exportedAt": "2026-08-06T09:08:02Z"
}
```

Idempotent — calling it again returns the same result. This step is MANDATORY after every ERP import.

Satisfies: `MARKED_EXPORTED`.

### 10. Create and send an EMAIL invoice to the non-capable customer

Step 4 proved `95999902` cannot receive e-invoices, so the correct channel is `EMAIL`. Create (back as the sender — drop `X-Company-Id`):

```bash
curl -s -X POST https://sandbox.bilnex.io/partner/v1/invoices \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 91e4d7c2-5a0b-4f38-b6d9-3c8a1e7f2b04" \
  -d '{
    "customer": {
      "name": "Sandbox Paper OÜ",
      "regCode": "95999902",
      "email": "billing@sandbox-paper.example"
    },
    "date": "2026-08-06",
    "dueDate": "2026-08-20",
    "currency": "EUR",
    "language": "ET",
    "items": [
      {
        "description": "Monthly service fee, August 2026",
        "quantity": 1.00,
        "unit": "pcs",
        "price": 49.00,
        "vatRate": 24
      }
    ]
  }'
```

Response `201 Created` (abbreviated fields identical in shape to step 5):

```json
{
  "id": "f3b9d0e7-4a2c-4d1b-8e6f-7c5a9b0d3e21",
  "number": "2026-0002",
  "status": "SAVED",
  "category": "PDF",
  "date": "2026-08-06",
  "dueDate": "2026-08-20",
  "currency": "EUR",
  "language": "ET",
  "customer": {
    "name": "Sandbox Paper OÜ",
    "regCode": "95999902",
    "email": "billing@sandbox-paper.example",
    "address": null
  },
  "items": [
    {
      "description": "Monthly service fee, August 2026",
      "quantity": 1.00,
      "unit": "pcs",
      "price": 49.00,
      "vatRate": 24,
      "totalPrice": 49.00
    }
  ],
  "netAmount": 49.00,
  "vatAmount": 11.76,
  "vatSummary": [
    { "rate": 24, "base": 49.00, "amount": 11.76 }
  ],
  "totalAmount": 60.76,
  "createdDate": "2026-08-06T09:10:03Z"
}
```

Then send via EMAIL:

```bash
curl -s -X POST https://sandbox.bilnex.io/partner/v1/invoices/f3b9d0e7-4a2c-4d1b-8e6f-7c5a9b0d3e21/send \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: c5a9b0d3-7e21-4f6f-8e6d-1b4a2c4d0e97" \
  -d '{"channel":"EMAIL","to":"billing@sandbox-paper.example"}'
```

Response `200 OK`:

```json
{
  "id": "f3b9d0e7-4a2c-4d1b-8e6f-7c5a9b0d3e21",
  "status": "SENT",
  "category": "PDF",
  "channel": "EMAIL",
  "sentAt": "2026-08-06T09:10:44Z"
}
```

Sandbox email is NEVER delivered — it is intercepted (step 11).

Satisfies: `EMAIL_SENT`, and — because the same regCode was capability-checked `false` in step 4 — `NOT_CAPABLE_FALLBACK`.

### 11. Read the intercepted email

```bash
curl -s https://sandbox.bilnex.io/partner/v1/sandbox/emails \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

Response `200 OK`:

```json
{
  "items": [
    {
      "id": "eml_4c8d15a2b7f0",
      "to": "billing@sandbox-paper.example",
      "from": "invoices@bilnex.io",
      "subject": "Invoice 2026-0002 from Valge Klaar OÜ",
      "invoiceId": "f3b9d0e7-4a2c-4d1b-8e6f-7c5a9b0d3e21",
      "sentAt": "2026-08-06T09:10:44Z",
      "attachments": [
        { "filename": "invoice-2026-0002.pdf", "contentType": "application/pdf" }
      ]
    }
  ],
  "page": 1,
  "pageSize": 50,
  "hasMore": false
}
```

Satisfies: `EMAIL_FETCHED_FROM_INTERCEPT`.

### 12. Simulate the customer opening the email, and handle SEEN

Only `EMAIL` invoices ever reach `SEEN` (a tracking pixel in production; a trigger in sandbox):

```bash
curl -s -X POST https://sandbox.bilnex.io/partner/v1/sandbox/trigger \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"event":"invoice_seen","invoiceId":"f3b9d0e7-4a2c-4d1b-8e6f-7c5a9b0d3e21"}'
```

Response `200 OK`:

```json
{
  "event": "invoice_seen",
  "status": "triggered",
  "invoiceId": "f3b9d0e7-4a2c-4d1b-8e6f-7c5a9b0d3e21"
}
```

Then observe the new status from your integration:

```bash
curl -s https://sandbox.bilnex.io/partner/v1/invoices/f3b9d0e7-4a2c-4d1b-8e6f-7c5a9b0d3e21 \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

Response `200 OK` (abbreviated to the changed fields — full object shape as in step 10):

```json
{
  "id": "f3b9d0e7-4a2c-4d1b-8e6f-7c5a9b0d3e21",
  "number": "2026-0002",
  "status": "SEEN",
  "category": "PDF",
  "date": "2026-08-06",
  "dueDate": "2026-08-20",
  "currency": "EUR",
  "language": "ET",
  "customer": {
    "name": "Sandbox Paper OÜ",
    "regCode": "95999902",
    "email": "billing@sandbox-paper.example",
    "address": null
  },
  "items": [
    {
      "description": "Monthly service fee, August 2026",
      "quantity": 1.00,
      "unit": "pcs",
      "price": 49.00,
      "vatRate": 24,
      "totalPrice": 49.00
    }
  ],
  "netAmount": 49.00,
  "vatAmount": 11.76,
  "vatSummary": [
    { "rate": 24, "base": 49.00, "amount": 11.76 }
  ],
  "totalAmount": 60.76,
  "createdDate": "2026-08-06T09:10:03Z"
}
```

Satisfies: `INVOICE_SEEN_HANDLED` (optional check).

### 13. Run verification — the stopping condition

```bash
curl -s https://sandbox.bilnex.io/partner/v1/sandbox/verification \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

Response `200 OK` (shape; see [verification](https://sandbox.bilnex.io/docs/verification.md) for the full 16-check table):

```json
{
  "checks": [
    { "id": "AUTH_OK", "required": true, "status": "pass", "detail": "GET /partner/v1/me succeeded", "fixHint": null, "docsUrl": "https://sandbox.bilnex.io/docs/authentication.md" },
    { "id": "CAPABILITY_CHECKED_CAPABLE", "required": true, "status": "pass", "detail": "Capability checked for 95000003 (capable)", "fixHint": null, "docsUrl": "https://sandbox.bilnex.io/docs/sending-invoices.md" },
    { "id": "CAPABILITY_CHECKED_NOT_CAPABLE", "required": true, "status": "pass", "detail": "Capability checked for 95999902 (not capable)", "fixHint": null, "docsUrl": "https://sandbox.bilnex.io/docs/sending-invoices.md" },
    { "id": "INVOICE_CREATED", "required": true, "status": "pass", "detail": "2 invoices created", "fixHint": null, "docsUrl": "https://sandbox.bilnex.io/docs/sending-invoices.md" },
    { "id": "PDF_FETCHED", "required": true, "status": "pass", "detail": "PDF fetched for 2026-0001", "fixHint": null, "docsUrl": "https://sandbox.bilnex.io/docs/sending-invoices.md" },
    { "id": "EINVOICE_SENT", "required": true, "status": "pass", "detail": "2026-0001 sent via E_INVOICE", "fixHint": null, "docsUrl": "https://sandbox.bilnex.io/docs/sending-invoices.md" },
    { "id": "EINVOICE_RECEIVED_AT_RECEIVER", "required": true, "status": "pass", "detail": "Purchase invoice d94b7f2a received at Sinilille Kaubandus OÜ", "fixHint": null, "docsUrl": "https://sandbox.bilnex.io/docs/receiving-invoices.md" },
    { "id": "PURCHASE_LISTED", "required": true, "status": "pass", "detail": "Purchase invoices listed with since parameter", "fixHint": null, "docsUrl": "https://sandbox.bilnex.io/docs/receiving-invoices.md" },
    { "id": "MARKED_EXPORTED", "required": true, "status": "pass", "detail": "d94b7f2a marked exported", "fixHint": null, "docsUrl": "https://sandbox.bilnex.io/docs/receiving-invoices.md" },
    { "id": "EMAIL_SENT", "required": true, "status": "pass", "detail": "2026-0002 sent via EMAIL", "fixHint": null, "docsUrl": "https://sandbox.bilnex.io/docs/email-invoices.md" },
    { "id": "EMAIL_FETCHED_FROM_INTERCEPT", "required": true, "status": "pass", "detail": "Intercepted email eml_4c8d15a2b7f0 fetched", "fixHint": null, "docsUrl": "https://sandbox.bilnex.io/docs/email-invoices.md" },
    { "id": "NOT_CAPABLE_FALLBACK", "required": true, "status": "pass", "detail": "95999902 checked not capable, then invoiced via EMAIL", "fixHint": null, "docsUrl": "https://sandbox.bilnex.io/docs/sending-invoices.md" },
    { "id": "PEPPOL_SENT", "required": false, "status": "pending", "detail": "No PEPPOL send yet", "fixHint": "POST /partner/v1/invoices/{id}/send with channel PEPPOL to a peppolCapable customer", "docsUrl": "https://sandbox.bilnex.io/docs/peppol.md" },
    { "id": "PEPPOL_RECEIVED", "required": false, "status": "pending", "detail": "No PEPPOL purchase invoice listed yet", "fixHint": "Send via PEPPOL to the receiver company (or trigger inbound_peppol), then list its purchase invoices", "docsUrl": "https://sandbox.bilnex.io/docs/peppol.md" },
    { "id": "INVOICE_SEEN_HANDLED", "required": false, "status": "pass", "detail": "2026-0002 observed in status SEEN after open", "fixHint": null, "docsUrl": "https://sandbox.bilnex.io/docs/email-invoices.md" }
  ],
  "readyForProduction": true,
  "nextSteps": [
    "All 12 required checks pass. Optional: PEPPOL_SENT, PEPPOL_RECEIVED remain pending.",
    "See https://sandbox.bilnex.io/docs/go-live.md for a production key."
  ]
}
```

`readyForProduction` is `true` only when all 12 required checks pass. If any check is `fail` or `pending`, read its `fixHint` and `docsUrl`, fix your integration (not the curl — the point is that YOUR code performs these calls), and re-run this endpoint.

Do not report the integration complete until this endpoint returns `"readyForProduction": true`.

## When readyForProduction is true

1. Report completion, including the verification JSON output as evidence.
2. Follow [go-live](https://sandbox.bilnex.io/docs/go-live.md): email partners@bilnex.io with the passing verification JSON to get a `blx_live_` key, and complete the real-world channel activation steps (registry verification, receiving-operator election, Peppol registration, bank account) that the sandbox simulates.
3. In your integration, swap exactly two things: base URL `https://sandbox.bilnex.io` → `https://api.app.bilnex.io`, and the key `blx_sbx_...` → `blx_live_...`. All paths, payloads, and semantics are identical. Gate every `/partner/v1/sandbox/*` call behind an environment flag — those endpoints return 404 in production.

## Deep dives

- [Quickstart](https://sandbox.bilnex.io/docs/quickstart.md) — this happy path as a standalone page.
- [Authentication](https://sandbox.bilnex.io/docs/authentication.md) — keys, headers, `X-Company-Id`, key storage.
- [Sandbox](https://sandbox.bilnex.io/docs/sandbox.md) — provisioning, fixtures, state, reset, claim, triggers, intercepted email.
- [Sending invoices](https://sandbox.bilnex.io/docs/sending-invoices.md) — capability check, create, channels, lifecycle, idempotency.
- [Receiving invoices](https://sandbox.bilnex.io/docs/receiving-invoices.md) — polling with `since`, statuses, `/original`, mark-exported.
- [Peppol](https://sandbox.bilnex.io/docs/peppol.md) — participant IDs, sending and receiving over Peppol.
- [Digitization](https://sandbox.bilnex.io/docs/digitization.md) — uploading PDFs, extraction confidence, confirming into the ERP.
- [Email invoices](https://sandbox.bilnex.io/docs/email-invoices.md) — EMAIL channel, SEEN, sandbox interception.
- [Errors](https://sandbox.bilnex.io/docs/errors.md) — error envelope and code table.
- [Rate limits](https://sandbox.bilnex.io/docs/rate-limits.md) — headers, 429, Retry-After, sandbox caps.
- [Verification](https://sandbox.bilnex.io/docs/verification.md) — all 16 checks and exactly what flips each one.
- [Go-live](https://sandbox.bilnex.io/docs/go-live.md) — production keys, activation, sandbox-vs-production differences, smoke tests, rotation.
- [API reference](https://sandbox.bilnex.io/docs/api-reference.md) — every endpoint on one page.
- [Changelog](https://sandbox.bilnex.io/docs/changelog.md) · [Versioning policy](https://sandbox.bilnex.io/docs/versioning-policy.md)

API version 2026-08-01. /partner/v1 changes are additive-only; see [versioning-policy](https://sandbox.bilnex.io/docs/versioning-policy.md).

---
**Building with a coding agent?** Start from [agents.md](https://sandbox.bilnex.io/agents.md) or install the skill: `npx skills add https://sandbox.bilnex.io`. Machine-readable index: [llms.txt](https://sandbox.bilnex.io/llms.txt). Every page on this site is also plain markdown — append `.md`.
