# POST /api/v2/sales-invoices

Create a sales invoice

- MCP id: sales-invoices.store
- Scope: api:write:sales_invoices
- Permission: sales_invoices::create
- Needs the billing feature on your organisation
- Rate cost: 1 unit, write bucket. A write costs the same whatever `view` you ask for.
- Cache-Control: private, no-store

Creates an invoice header and returns it in the detail view with a `Location` header.

It has no rows yet, so subtotal, `vat_total` and total are 0.00: post each row to `sales-invoices/{sales_invoice}/rows` afterwards and the totals follow. The invoice number is allocated by the server from the organisation's yearly sequence and cannot be supplied. The server sets the status too: a caller without `sales_invoices::finalize` creates a `pending_approval` invoice that a colleague holding that permission must approve in plnnrly; no API endpoint advances it. In an organisation with the accounting integration connected the invoice starts as a draft awaiting the sync, and the customer must be linked to that system or the sync will later fail; in one without it the invoice is local, immediately settled, and never emailed. Send `Idempotency-Key` to make a retry safe.

## Parameters
- view (string, optional): Which field set to render.
- X-Request-Id (header, optional): A correlation id of your own. It is recorded in our logs next to the server's request id but never returned: the response header always carries the server's id. A value that does not match the pattern is discarded silently, never rejected.
- Idempotency-Key (header, optional): A caller-chosen key that makes this POST safe to retry. Replaying it returns the original response with Idempotency-Replayed: true.

## Request body
- customer_id (integer → Customer.id, required): Customer this invoice is billed to.
- invoice_date (date (YYYY-MM-DD), required): Invoice date, a zone-free calendar date. Newest first is the default sort.
- project_id (integer or null → Project.id, optional): Project this invoice was raised for, when it was raised from one.
- customer_subscription_id (integer or null → CustomerSubscription.id, optional): Customer subscription this invoice was generated from, when it was generated from a renewal.
- due_date (date (YYYY-MM-DD) or null, optional): Date payment is due, a zone-free calendar date. Null when no term was applied. Must be on or after invoice_date.
- reference (string or null ≤ 255, optional): The customer's own reference, typically a purchase-order number, reproduced on the invoice.
- description (string or null ≤ 255, optional): One-line description of what was invoiced.
- internal_notes (string or null, optional): Notes for colleagues. Never rendered on the invoice the customer receives.
- should_email (boolean, optional): Whether the invoice is emailed to the customer when it syncs. The email is sent through the accounting integration, so it is forced to `false` in an organisation with no integration configured. Writing it requires `sales_invoices::finalize`.

Unknown keys are refused with `422` validation-failed, naming the key.

## Response 201
- id (integer, required): Unique identifier.
- customer_id (integer, required): Customer this invoice is billed to.
- customer (Ref or CustomerSummary, required): The customer this invoice is billed to. Expanding it with `?include=customer` additionally requires the `customers::read` permission and a token scope covering the customers prefix.
- project_id (integer or null, required): Project this invoice was raised for, when it was raised from one.
- customer_subscription_id (integer or null, required): Customer subscription this invoice was generated from, when it was generated from a renewal.
- invoice_number (string ≤ 255, required, server-assigned): Invoice number, allocated by the server from the organisation's yearly sequence: B-26-00001 for an invoice raised in this flow, L-1234 for one converted from a legacy invoice. It also works in the path: `GET sales-invoices/B-26-00001` works. This is the field `?q=` searches.
- reference (string or null ≤ 255, required): The customer's own reference, typically a purchase-order number, reproduced on the invoice.
- description (string or null ≤ 255, required): One-line description of what was invoiced.
- invoice_date (date (YYYY-MM-DD), required): Invoice date, a zone-free calendar date. Newest first is the default sort.
- due_date (date (YYYY-MM-DD) or null, required): Date payment is due, a zone-free calendar date. Null when no term was applied.
- status (string, required, server-assigned): Lifecycle status. Resolved by the server on create and never writable: a caller without `sales_invoices::finalize` creates a `pending_approval` invoice, and any update by such a caller returns the invoice to `pending_approval`.
- should_email (boolean, required): Whether the invoice is emailed to the customer when it syncs. The email is sent through the accounting integration, so it is forced to `false` in an organisation with no integration configured. Writing it requires `sales_invoices::finalize`.
- subtotal (decimal (2 dp), required, server-assigned): Sum of the row line totals excluding VAT, as a decimal string. Recomputed from the rows; never written directly.
- vat_total (decimal (2 dp), required, server-assigned): Sum of the row VAT amounts, as a decimal string. Recomputed from the rows; never written directly.
- total (decimal (2 dp), required, server-assigned): Subtotal plus VAT, as a decimal string. Recomputed from the rows; never written directly.
- exact_invoice_id (string or null ≤ 255, required, server-assigned): The accounting system's own identifier for this invoice, written by the sync. Not a local id.
- exact_journal (string or null ≤ 255, required, server-assigned): Sales journal the invoice is booked in, resolved from the organisation's accounting settings on create. Null in an organisation with no integration configured.
- sync_status (string, required, server-assigned): How far the invoice has got through the accounting sync. Set on create: local in an organisation with no integration configured, pending otherwise.
- sync_error (string or null, required, detail only, server-assigned, multiline): Why the last sync attempt failed, verbatim from the accounting system. Null while nothing has failed.
- synced_at (timestamp (YYYY-MM-DDThh:mm:ssZ) or null, required, server-assigned): When the invoice last synced successfully.
- sync_attempted_at (timestamp (YYYY-MM-DDThh:mm:ssZ) or null, required, server-assigned): When the invoice was last handed to the sync, successfully or not.
- paid_at (timestamp (YYYY-MM-DDThh:mm:ssZ) or null, required, server-assigned): When the invoice was settled. Set on create for a local invoice that is immediately definitive; otherwise written when payment is registered.
- is_legacy (boolean, required, server-assigned): Whether this invoice was converted from a legacy invoice. Such an invoice is read-only: PATCH and DELETE answer `409` `state-conflict` with reason `locked_by_legacy`.
- legacy_invoice_id (integer or null, required, server-assigned): The id of the legacy invoice this invoice was converted from, so it can be matched with `GET /api/v2/invoices`. The legacy invoice may since have been removed.
- internal_notes (string or null, required, detail only, multiline): Notes for colleagues. Never rendered on the invoice the customer receives.
- rows_count (integer ≥ 0, required): Number of line rows on this invoice.
- created_at (timestamp (YYYY-MM-DDThh:mm:ssZ), required): Creation timestamp.
- updated_at (timestamp (YYYY-MM-DDThh:mm:ssZ), required): Last-write timestamp; use for incremental sync. It also feeds the `ETag` revision, because a sales invoice has no `version` field.
- deleted_at (timestamp (YYYY-MM-DDThh:mm:ssZ) or null, required): Soft-delete timestamp, `null` while live.
- links (SalesInvoiceLinks, required)
  - self (url, required): The canonical URI of this record.
  - rows (url, required): Line rows on this invoice.

## Errors
- 403 forbidden-by-policy | insufficient-permission | insufficient-token-scope: Forbidden.
- 409 idempotency-key-in-flight | idempotency-key-reused: Conflict.
- 415 unsupported-media-type: Unsupported media type.
- 422 unprocessable-relationship | validation-failed: Unprocessable content.
- 400 idempotency-key-required | invalid-cursor | invalid-query-parameter | malformed-request | unsupported-parameter: Bad request.
- 401 token-expired | unauthenticated: Unauthenticated.
- 404 endpoint-not-found | resource-not-found: Not found.
- 406 not-acceptable: Not acceptable.
- 429 rate-limit-exceeded: Too many requests.
- 500 internal-error: Server error.
- 503 tenant-unavailable: Service unavailable.

## Example
```bash
curl -X POST \
  'https://<your-url>/api/v2/sales-invoices' \
  -H 'Authorization: Bearer <token>' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <key>' \
  -d '{
    "customer_id": 1,
    "invoice_date": "2026-02-01",
    "due_date": "2026-03-03",
    "reference": "PO-4471",
    "description": "Development work, January 2026"
  }'
```
