---
name: bilnex-erp-integration
description: Build a Bilnex e-invoicing integration into an ERP codebase — outbound invoices (Estonian operator-channel e-invoices, Peppol, PDF-by-email with capability-check fallback), inbound purchase-invoice polling with mark-exported, and AI digitization, verified end-to-end against the self-serve Bilnex sandbox. Use when asked to "integrate Bilnex", "add e-invoicing/e-arved to an ERP", or implement Estonian or Peppol e-invoicing.
license: CC-BY-4.0
compatibility: Any coding agent with HTTPS access and file-editing ability (Claude Code, Cursor, Codex). No local runtime requirements; all testing runs against the hosted sandbox.
metadata:
  api-version: "2026-08-01"
  docs: "https://sandbox.bilnex.io/llms.txt"
  publisher: "Bilnex"
---

# Bilnex ERP integration

Pinned API version: **2026-08-01** (additive-only within `/partner/v1`).
Base URLs: sandbox **`https://sandbox.bilnex.io`** (build + verify here), production **`https://api.app.bilnex.io`** (same paths). Keys: `blx_sbx_...` sandbox (self-serve, 2-day TTL, `claim` extends once to 30d) / `blx_live_...` production (issued by Bilnex). Keys are host-bound — wrong host returns 401.
Auth header: `Authorization: Bearer <YOUR_API_KEY>` (or `X-API-Key`). Acting company defaults to the sender; override with `X-Company-Id: <uuid>` (must be owned by the key, else 403 `company_not_owned`).

Canonical human-readable docs: `https://sandbox.bilnex.io/agents.md`. Every docs page is plain markdown (append `.md`). OpenAPI: `https://sandbox.bilnex.io/partner/v1/openapi.json`.

## Routing table — what the ERP needs → what to build

| ERP need | Flow | Endpoints | Reference |
|---|---|---|---|
| Send sales invoices as e-invoices | capability check → create → send `E_INVOICE` | `GET /partner/v1/customers/{regCode}/e-invoice-capability` · `POST /partner/v1/invoices` · `POST /partner/v1/invoices/{id}/send` | [references/flows.md](references/flows.md) |
| Send when customer can't receive e-invoices | same, branch to `EMAIL` on `eInvoiceCapable: false` | same + `GET /partner/v1/sandbox/emails` (sandbox assert) | [references/flows.md](references/flows.md) |
| Send over Peppol | capability (`peppolCapable`) → send `PEPPOL` | same endpoints, channel `PEPPOL` | [references/flows.md](references/flows.md) |
| Store/print the invoice PDF | fetch presigned PDF | `GET /partner/v1/invoices/{id}/pdf` | [references/flows.md](references/flows.md) |
| Show "customer opened it" | list/read invoices, handle `SEEN` (EMAIL only) | `GET /partner/v1/invoices?status=` · `GET /partner/v1/invoices/{id}` | [references/flows.md](references/flows.md) |
| Import incoming purchase invoices | poll with `since` cursor → detail → ERP write → mark-exported | `GET /partner/v1/purchase-invoices` · `GET .../{id}` · `GET .../{id}/original` · `POST .../{id}/mark-exported` | [references/receiving.md](references/receiving.md) |
| Digitized purchase invoices | they arrive as `DIGITIZED_PENDING` incoming documents; upload-for-extraction is NOT in the Partner API v1 | the receiving flow | [references/receiving.md](references/receiving.md) |
| Robust error/retry behavior | envelope, code table, 429 `Retry-After`, idempotency | all | [references/errors-and-limits.md](references/errors-and-limits.md) |
| Ship to production | verification pass → live key → activation → smoke | `GET /partner/v1/sandbox/verification` | [references/go-live.md](references/go-live.md) |
| Understand the Estonian context | — | — | [references/estonia-einvoicing-context.md](references/estonia-einvoicing-context.md) |

## Build procedure (8 steps)

1. **Provision** — `POST https://sandbox.bilnex.io/partner/v1/sandbox` with `{"email":"<dev email>","erpName":"<this ERP>","ref":"agent"}` (no auth, no account). Response contains the API key (shown ONCE), a sender company, a receiver company, and three fixture counterparties (`95999901` capable, `95999902` not capable, `95999903` rejects).
2. **Config** — store the key, base URL, and receiver companyId in the codebase's existing server-side config mechanism (env var / secrets). Never in frontend code, mobile code, or committed files. Add an `environment` flag that gates all `/partner/v1/sandbox/*` calls (they 404 in production).
3. **HTTP client module** — one thin client used by everything else: injects the auth header and optional `X-Company-Id`; parses the error envelope `{"error":{code,message,hint,docsUrl}}` into typed errors; on 429 sleeps `Retry-After` then retries; on 500/503 retries with backoff (1s/2s/4s, max 3); other 4xx never retried; generates an `Idempotency-Key` (UUID) on `POST /invoices` and `POST /invoices/{id}/send`; tolerates unknown response fields.
4. **Auth ping** — call `GET /partner/v1/me` through the client; confirm `environment`, acting company, and limits. (Flips `AUTH_OK`.)
5. **Outbound mapping** — map the ERP's invoice model to `POST /partner/v1/invoices` (customer{name,regCode,email,address?}, date, dueDate, currency, language EN/ET/LV/LT, items[{description,quantity,unit,price,vatRate}]). Store the server's returned totals (`netAmount`, `vatAmount`, `vatSummary`, `totalAmount`) — never recompute. Implement channel selection: capability check → `recommendedChannel`; `eInvoiceCapable: false` → `EMAIL`. Implement send + persist `status`/`category`/`sentAt`. Fetch and store the PDF.
6. **Inbound poller** — scheduled job: `GET /partner/v1/purchase-invoices?since=<cursor>` (as the receiving company via `X-Company-Id`), paginate while `hasMore`, dedupe by `id`, fetch detail, write to the ERP, then `POST .../mark-exported` (mandatory, idempotent — only after the ERP write is durable), persist `nextSince` as the new cursor. Branch on `DIGITIZED_PENDING` + extraction confidence if digitization is in scope.
7. **Exercise every flow through the integration code, not curls** — run the ERP's own send path against `95000003` (e-invoice), `95999902` (EMAIL fallback after a `false` capability check), and the poller against the receiver company; read `/partner/v1/sandbox/emails` to assert the EMAIL send; use `POST /partner/v1/sandbox/trigger` for `inbound_einvoice`/`inbound_peppol`/`invoice_seen`. Optionally drill error handling against `95999903` (422 `einvoice_rejected`).
8. **Verify** — `GET /partner/v1/sandbox/verification`; iterate on failing checks.

**Stopping condition:** 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, re-run.

## Hard rules (condensed)

1. Never assume e-invoice capability — always call the capability endpoint; `false` → `EMAIL`.
2. Never wait for `DELIVERED` — `SENT` is terminal for `E_INVOICE`/`PEPPOL`; only `EMAIL` reaches `SEEN`.
3. Never expect webhooks — receiving is `since`-cursor polling; dedupe by `id`.
4. Never poll faster than every 2 seconds; always honor `Retry-After`.
5. Never re-send an e-invoice as a retry — one invoice = one send; corrections are credit notes.
6. Never recompute header totals from line items — server values are authoritative.
7. Never skip `mark-exported` after an ERP import — idempotent and mandatory.
8. Never put the API key in frontend/mobile code or the repo — server-side config only.
9. Never use `blx_sbx_` on `api.app.bilnex.io` or `blx_live_` on `sandbox.bilnex.io`.
10. Never call `/partner/v1/sandbox/*` from production code paths — gate behind the env flag.

## References

- [references/flows.md](references/flows.md) — all outbound flows end-to-end with full JSON: capability branching, totals contract, three send channels, rejector error drill.
- [references/receiving.md](references/receiving.md) — inbound importer pattern: statuses, cursor polling + dedupe + pagination, `/original`, mark-exported idempotency, sandbox triggers, reference poller pseudocode.
- [references/errors-and-limits.md](references/errors-and-limits.md) — error envelope, code table with retryability, 429 + `Retry-After`, limits/usage, sandbox expiry and reset semantics.
- [references/go-live.md](references/go-live.md) — production key, real channel activation, sandbox-vs-production differences, smoke test, rotation (condensed from the full [go-live docs](https://sandbox.bilnex.io/docs/go-live.md)).
- [references/estonia-einvoicing-context.md](references/estonia-einvoicing-context.md) — operator-channel e-invoices, the registry, Peppol `0191:<regCode>`, reg codes, VAT, IBANs.
