# Go-live

Moving from sandbox to production. Prerequisite: `GET /partner/v1/sandbox/verification` returns `"readyForProduction": true`.

Your integration code does not change for production: point it at `https://api.app.bilnex.io/partner/v1` and use the key each customer generates. What is not self-serve yet is switching it on — Bilnex opens the Partner API per integration.

## 1. Opening production

When `GET /partner/v1/sandbox/verification` returns `readyForProduction: true`:

1. Email **partners@bilnex.io** the verification response and your ERP's name.
2. Open a Bilnex account for your own company at `https://app.bilnex.io`, activate e-invoice sending and add a bank account.
3. Bilnex switches the Partner API on for that company. You generate its key under **Settings → ERP integration** and run the production smoke test (section 5).
4. When you confirm it works, Bilnex switches it on for your customers. Each of them generates its own key under Settings → ERP integration and enters it in your ERP together with the URL.

Until the Partner API is switched on for a company, the Bilnex API option does not appear in its ERP list, key generation is refused, and every production call for it answers 503 `feature_disabled`. That answer is not retryable: nothing changes until Bilnex switches the company on.

Step 1 is yours: Bilnex sends nothing automatically when verification passes.

### How customers connect

Production keys belong to **customer companies**, not to the ERP vendor. Bilnex does not issue keys to vendors. For each customer:

1. The customer opens the Bilnex app → **Settings → ERP integration** and picks your software from the list (or *Other accounting software (Bilnex API)* if it is not listed yet).
2. Bilnex shows two values: the **data exchange URL** (`https://api.app.bilnex.io/partner/v1`) and a **`blx_live_` API key**, shown once.
3. The customer pastes both into your software and clicks **Connect** in Bilnex. The key starts working the moment the connection is saved.

Every request made with that key acts as that one company. Bilnex never sees your software's credentials and you never hold a key that reaches more than one customer. Production keys have the same limits as sandbox keys: 60 requests a minute, 5,000 a day and 200 sends a day per key ([rate limits](https://sandbox.bilnex.io/docs/rate-limits.md)).

### What your integration must have

- **Two customer-editable settings: Bilnex URL and Bilnex API key.** Do not hard-code the production URL — the customer's screen shows the exact base to use, and it already ends in `/partner/v1`; append endpoint paths to it (`<url>/invoices`, `<url>/purchase-invoices?since=…`).
- **A "test connection" action** — `GET /partner/v1/me` returns the company behind the key, its limits and today's usage.
- **Clear handling of 401.** A key returns 401 `unauthorized` with a `message` and a `hint`, most often in three cases: it was pasted before the customer clicked **Connect** (*"This API key has not been activated yet."*), the customer generated a new key or disconnected the ERP in Bilnex (*"This API key has been revoked."*), or it was mistyped (*"Unknown API key."*). Surface the hint to the user; it tells them what to do in Bilnex.
- **503 `feature_disabled` is final, not an outage.** The capability is switched off for that company. Show it to the user and stop; do not retry.
- **One send per invoice.** Bilnex does not refuse a repeated send — it delivers the invoice again. Record each send that answered 200 and never repeat it.
- **No automatic retry of 429 `email_rate_limited`.** A company that keeps reaching the email limits within an hour is suspended, and every call for it then answers 403 `account_suspended`.
- **No `X-Company-Id`.** A company key owns exactly one company; sending a different id returns 403 `company_not_owned`. Keep the header for the sandbox's vendor-style key only (it owns two companies).

The sandbox mirrors this: the provisioning response's `companyKeys.sender` and `companyKeys.receiver` are company keys of the same kind, so the settings screen you build for customers is testable against `https://sandbox.bilnex.io/partner/v1` before any customer exists.

### Getting listed by name

Until your software is listed, customers choose *Other accounting software (Bilnex API)*, which works identically. Listing your software under its own name in the ERP list ships in a Bilnex release: the name in four languages (English, Estonian, Latvian, Lithuanian) and, optionally, a logo. It is not a configuration change. Ask for it at **partners@bilnex.io**; going live does not wait for it.

## 2. Real channel activation in production

The sandbox simulates activations that are real-world processes in production. Before a production company can use each rail, these must be completed (in the Bilnex product, by the customer — your integration does not automate them, but must expect companies where they are not yet done):

| Activation | What actually happens |
|---|---|
| Company onboarding | The person connecting the company passes **representative-rights verification against the Estonian business registry** — only a verified legal representative can activate invoicing channels for a company. |
| Sending e-invoices | **E-invoice sending is activated and the company has a bank account** (it is printed on the invoice and carried in the e-invoice). Without either, an `E_INVOICE` send answers 400 `send_failed`. |
| E-invoice receiving | The company makes a **receiving-operator election**: exactly one operator per company is registered in the registry as its e-invoice receiver. Until elected, the company cannot receive operator-channel e-invoices (and other companies' capability checks for it return `eInvoiceCapable: false`). |
| Peppol | **Peppol registration** for the company's participant ID (`0191:<regCode>`): CREATE when the company has never been on Peppol, or MIGRATE when it is registered with another access point. The Partner API does not use Peppol yet: sending answers 501 `peppol_send_unavailable`, and incoming Peppol documents are not listed ([peppol](https://sandbox.bilnex.io/docs/peppol.md)). |

Practical consequence for your integration: in production, capability checks return real registry facts that change over time, a send to a customer that was capable last month can be refused with 400 `send_failed`, and newly connected companies may not be able to send until their activation and bank account are in place. A refused e-invoice goes back to `DRAFT` and needs a fallback (EMAIL) or a person. The sandbox fixture behaviors exist precisely so your code already handles all of this.

## 3. Sandbox vs production — the canonical differences

| Aspect | Sandbox | Production |
|---|---|---|
| Partner API switched on | For every sandbox | Per integration, by Bilnex (section 1); until then 503 `feature_disabled` |
| E-invoices | Simulated; delivered at once, and only to sandbox registry codes (your receiver and the fixtures) | The real operator network. There is no delivery status: an e-invoice stays `SENT` |
| Refused e-invoice | 422 `customer_not_einvoice_capable` (`95999902`) or 422 `einvoice_rejected` (`95999903`); any other refusal 400 `send_failed` | 400 `send_failed` |
| Peppol | Sending answers 501 `peppol_send_unavailable`; incoming Peppol documents are not listed | The same |
| Email | Intercepted at `/partner/v1/sandbox/emails`, NEVER delivered | Really delivered to the recipient |
| Counterparties | Fixed fixtures (`95999901/02/03`) + your two sandbox companies | Real companies in real registries |
| Capability checks | Fixed fixture behaviors | Live registry lookups that change over time |
| Channel activation | Automatic — sandbox companies come pre-activated | Verified: representative rights, operator election, Peppol registration, bank account |
| API key | Self-serve; 2 days, extendable once to 30; one vendor-style key over two companies plus one company key per company | Generated by each customer company in Bilnex Settings → ERP integration; one company per key; no expiry; replaced by the customer |
| Limits | 60 requests/minute, 5,000/day, 200 sends/day per key | The same |
| `/partner/v1/sandbox/*` | Available | **404** `sandbox_unavailable` |
| `SEEN` status | Set by `POST /partner/v1/sandbox/trigger` `invoice_seen` | Only when the recipient opens an emailed invoice (tracking pixel, best effort) |

Everything else — paths, payloads, status lifecycles, error envelope, rate-limit headers — is identical by design.

## 4. Switching your integration

Nothing in your code changes between sandbox and production. The customer's two settings do:

```
Bilnex URL      https://api.app.bilnex.io/partner/v1     # sandbox: https://sandbox.bilnex.io/partner/v1
Bilnex API key  blx_live_...                             # sandbox: blx_sbx_...
```

Plus one code requirement: every call to `/partner/v1/sandbox/*` (state, reset, claim, emails, verification, trigger) must be gated behind an environment flag — in production those paths answer 404 `sandbox_unavailable`, and an importer that depends on them will break.

## 5. Production smoke test

Run it on your own company once Bilnex has switched the Partner API on for it (step 3 of section 1) — before that, every call answers 503 `feature_disabled`. Generate the key under Settings → ERP integration: choose your software (or *Other accounting software (Bilnex API)*), paste the URL and the key into your ERP, and click **Connect**. Production has real customers and real deliveries — test accordingly.

**Safe (nothing leaves your company, run these):**

- `GET /partner/v1/me` — the key works, `environment` reads `production`, the right company is listed.
- `GET /partner/v1/customers/{regCode}/e-invoice-capability` — for a real counterparty you know.
- `POST /partner/v1/invoices` — creates a real invoice in your own company (status `SAVED`). Nobody else sees it until it is sent, but it counts as a document on your company's Bilnex plan.
- `GET /partner/v1/invoices/{id}/pdf` — render check.
- `GET /partner/v1/purchase-invoices?since=...` — poller works against production data.
- `POST /partner/v1/purchase-invoices/{id}/mark-exported` — only for documents you genuinely imported.

**With care:**

- `POST /partner/v1/invoices/{id}/send` with `channel: "EMAIL"` and `"to"` set to **your OWN email address only** — one send, verify receipt, done.

**Never:**

- Any `/partner/v1/sandbox/*` call (404 anyway — but code attempting it is a bug).
- Bulk test sends of any kind.
- `E_INVOICE` test sends to third parties — a delivered e-invoice is a real legal document in someone's accounting inbox and cannot be unsent.

## 6. Key replacement and revocation

- **The customer holds the key, so the customer replaces it.** *Generate new key* in Bilnex Settings → ERP integration issues a fresh key and revokes the old one **immediately** — there is no overlap window. Your software should make updating the Bilnex key a one-field change and show the 401 hint when the old key stops working.
- **Disconnecting the ERP in Bilnex revokes the key.** Reconnecting issues a new one.
- **Storage**: keys live in your server-side configuration or secret store per customer — never in frontend/mobile code, logs, or the repository. Read the key from config at request time, not process start, so a replacement takes effect without a restart.
- **On leak**: if a customer's key appears in a log, a ticket, or an error report, tell the customer to generate a new key in Bilnex. That is the whole procedure. Sandbox keys need no ceremony: reset or let them expire.
- Monitor 401 `unauthorized` in your logs — a stream of them after a customer replaced their key means the new key was not saved in your software.

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