# Sending invoices

Outbound flow: check the customer's e-invoice capability → create the invoice → send it on the right channel. Three channels exist: `E_INVOICE` (Estonian operator network), `PEPPOL` (see [peppol](https://sandbox.bilnex.io/docs/peppol.md)), `EMAIL` (PDF attached; see [email-invoices](https://sandbox.bilnex.io/docs/email-invoices.md)).

## Lifecycle and categories

```
DRAFT → SAVED → SENT → SEEN
```

- `POST /partner/v1/invoices` creates the invoice in status `SAVED` (the Partner API has no draft-editing step; `DRAFT` exists in the platform but Partner-created invoices start ready to send).
- `send` moves it to `SENT`.
- `SEEN` exists ONLY for `EMAIL` sends — it means the recipient opened the email (tracking pixel in production, `invoice_seen` trigger in sandbox).
- **`SENT` is TERMINAL for `E_INVOICE` and `PEPPOL`.** There is no `DELIVERED` status anywhere in this API. Never wait or poll for delivery of an e-invoice or Peppol invoice.

`category` records the format: `PDF` (created; also EMAIL-sent), `E_INVOICE`, `PEPPOL` (set by the send channel).

Never re-send an invoice as a retry: one invoice = one send. If an invoice is wrong after sending, issue a credit note (a new invoice with negative quantities) — do not send the same document twice.

## 1. Capability check

`GET /partner/v1/customers/{regCode}/e-invoice-capability` — a registry lookup answering "can this Estonian company receive e-invoices, and on which channel?" Always call it before choosing a channel; never assume capability.

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

```json
{
  "regCode": "95999901",
  "name": "Sandbox Capable OÜ",
  "eInvoiceCapable": true,
  "operator": "Finbite",
  "peppolCapable": false,
  "peppolId": null,
  "recommendedChannel": "E_INVOICE",
  "checkedAt": "2026-08-06T09:02:10Z"
}
```

| Field | Meaning |
|---|---|
| `eInvoiceCapable` | `true` = registered to receive operator-channel e-invoices |
| `operator` | Receiving operator name, `null` when not capable |
| `peppolCapable` / `peppolId` | Peppol registration; `peppolId` format `0191:<regCode>` |
| `recommendedChannel` | `E_INVOICE`, `PEPPOL`, or `EMAIL` — the server's suggested channel; follow it unless you have a reason not to |
| `checkedAt` | Timestamp of the registry lookup |

Branching rule: `eInvoiceCapable: false` → send via `EMAIL`. This branch is a required verification check (`NOT_CAPABLE_FALLBACK`): after checking a regCode that returns `false`, an invoice to that same regCode must be sent with channel `EMAIL`.

Verification: a check returning `eInvoiceCapable: true` flips `CAPABILITY_CHECKED_CAPABLE`; one returning `false` flips `CAPABILITY_CHECKED_NOT_CAPABLE`.

## 2. Create — `POST /partner/v1/invoices`

```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
      }
    ]
  }'
```

Request fields: `customer.name`, `customer.regCode`, `customer.email` required; `customer.address` optional. `date`, `dueDate` are `YYYY-MM-DD`. `currency` ISO 4217 (`EUR` for Estonian counterparties). `language` one of `EN`, `ET`, `LV`, `LT` — the language of the rendered PDF and email. Each item: `description`, `quantity`, `unit`, `price` (net unit price), `vatRate` (percent, `24` is the Estonian standard rate).

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

Flips `INVOICE_CREATED`.

**Totals contract.** The server computes `totalPrice` per item, `netAmount`, `vatAmount`, `vatSummary`, and `totalAmount` (rounding per VAT group, 2 decimals). Store these values in your ERP as received. Never recompute header totals from line items — rounding differences will make your books disagree with the legal document.

**Idempotency.** `POST /partner/v1/invoices` and `POST /partner/v1/invoices/{id}/send` accept an `Idempotency-Key` header (any unique string ≤128 chars; a UUID is recommended). Retrying with the same key returns the original response instead of creating/sending twice. Keys are retained for 24 hours. Reusing a key with a DIFFERENT body returns 409 `idempotency_conflict`. Always send an idempotency key from code paths that retry on network errors.

## 3. Read back — list and get

`GET /partner/v1/invoices?status=&since=&page=`:

```bash
curl -s "https://sandbox.bilnex.io/partner/v1/invoices?status=SENT&since=2026-08-01T00:00:00Z&page=1" \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

```json
{
  "items": [
    {
      "id": "8c2a91d4-6e3b-4f7a-a1d9-5b8e2c7f0a63",
      "number": "2026-0001",
      "status": "SENT",
      "category": "E_INVOICE",
      "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"
    }
  ],
  "page": 1,
  "pageSize": 50,
  "hasMore": false,
  "nextSince": "2026-08-06T09:05:12Z"
}
```

Query parameters: `status` filters by lifecycle status; `since` (ISO 8601) returns invoices created after that instant — pass the previous response's `nextSince` verbatim; `page` is 1-based, page size fixed at 50, `hasMore` tells you to fetch the next page.

`GET /partner/v1/invoices/{id}` returns the same invoice object shape as create.

## 4. Fetch the PDF — `GET /partner/v1/invoices/{id}/pdf`

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

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

`pdfUrl` is presigned and short-lived (`expiresAt`, ~15 minutes). Download it immediately; store the bytes, not the URL. Available in every status. Flips `PDF_FETCHED`.

## 5. Send — `POST /partner/v1/invoices/{id}/send`

Body: `{"channel": "E_INVOICE" | "PEPPOL" | "EMAIL", "to": "<optional email override, EMAIL only>"}`.

```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"}'
```

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

Flips `EINVOICE_SENT` (channel `E_INVOICE`), `PEPPOL_SENT` (channel `PEPPOL`), or `EMAIL_SENT` (channel `EMAIL`).

Channel rules:

| Channel | Requires | Result |
|---|---|---|
| `E_INVOICE` | Customer `eInvoiceCapable: true` | Delivered over the Estonian operator network. `SENT` terminal. |
| `PEPPOL` | Customer `peppolCapable: true` | Delivered over Peppol. `SENT` terminal. |
| `EMAIL` | Customer email (from the invoice, or `to` override) | PDF attached to an email. Can later reach `SEEN`. |

Sending an already-`SENT` invoice returns 409 `invalid_state`. Sending `E_INVOICE` to a not-capable customer returns 422 `customer_not_einvoice_capable`.

## Error drill: the rejector fixture

`95999903` (Sandbox Rejector OÜ) is capability-capable but its operator rejects the document at handoff — use it to prove your error handling:

```bash
curl -s -X POST https://sandbox.bilnex.io/partner/v1/invoices/<INVOICE_ID>/send \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"channel":"E_INVOICE"}'
```

Response `422 Unprocessable Entity` (for an invoice addressed to regCode `95999903`):

```json
{
  "error": {
    "code": "einvoice_rejected",
    "message": "The receiving operator rejected the e-invoice for 95999903 (Sandbox Rejector OÜ).",
    "hint": "The invoice remains in status SAVED. Correct the document or fall back to the EMAIL channel; do not retry the same send blindly.",
    "docsUrl": "https://sandbox.bilnex.io/docs/errors.md"
  }
}
```

A rejected send leaves the invoice in `SAVED` — it was never `SENT`, so sending again (after fixing the cause, or on another channel) is legitimate and is not a duplicate.

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