# POST /api/v2/invoices

Create an invoice

- MCP id: invoices.store
- Scope: api:write:invoices
- Permission: 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 a legacy invoice against one project and returns it in the detail view with a `Location` header.

All four body fields are required: none has a default. The invoice is attributed to the authenticated user; `user_id` is not writable and there is no on-behalf-of field, so an API-created invoice reads differently from the historical invoices, which carry the project manager instead. `project_id` is validated across into the projects prefix, so creating an invoice additionally requires the `projects::read` permission and a token scope covering the projects prefix; without either, `project_id` is refused as if it did not exist. Creating one bumps the project's `updated_at` and lands in the project's activity timeline. It has no line items and no way to add them (invoice rows are not part of this API), so the project's billed figure is unaffected.

## 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
- project_id (integer → Project.id, required): Project this invoice was raised for.
- description (string, required): What was invoiced, as free text. This is the field `?q=` searches.
- date (date (YYYY-MM-DD), required): Invoice date, a zone-free calendar date. Newest first is the default sort.
- hourly_rate (decimal (2 dp) 0 to 999999.99, required): Hourly rate this invoice was billed at, as a decimal string with two places. Never rounded or rescaled. At least 0. At most 999999.99.

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

## Response 201
- id (integer, required): Unique identifier.
- project_id (integer, required): Project this invoice was raised for.
- project (Ref or ProjectSummary, required): The project this invoice was raised for.
- user_id (integer or null, required, server-assigned): Employee the invoice is attributed to. Assigned from the authenticated user on create and never writable; `null` once that user is deleted.
- user (Ref or UserSummary or null, required): The employee this invoice is attributed to. Expanding it with `?include=user` additionally requires the `users::read` permission and a token scope covering the users prefix.
- description (string, required, multiline): What was invoiced, as free text. This is the field `?q=` searches.
- date (date (YYYY-MM-DD), required): Invoice date, a zone-free calendar date. Newest first is the default sort.
- hourly_rate (decimal (2 dp) ≥ 0, required): Hourly rate this invoice was billed at, as a decimal string with two places. Never rounded or rescaled.
- 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 legacy invoice has no `version` field.
- links (InvoiceLinks, required)
  - self (url, required): The canonical URI of this record.

## 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/invoices' \
  -H 'Authorization: Bearer <token>' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <key>' \
  -d '{
    "project_id": 1,
    "description": "Sprint 14: portal rebuild",
    "date": "2026-03-31",
    "hourly_rate": "85.00"
  }'
```
