# POST /api/v2/absences

Book an absence

- MCP id: absences.store
- Scope: api:write:absences
- Permission: absences::manage
- Rate cost: 1 unit, write bucket. A write costs the same whatever `view` you ask for.
- Cache-Control: private, no-store

Books an absence for an employee and returns it with a `Location` header.

`start` and `end` are stored exactly as sent: they are RFC 3339 UTC instants and no timezone conversion is applied, unlike the form in plnnrly, which collects local wall-clock time. Omit `end` for an absence that is still running; it then reaches through now everywhere it is read, so close it when the employee returns. Naming a `user_id` or `manager_id` additionally requires read access to users (`users::read`) and a token covering that prefix, because the reference is checked against what the caller may see. Nothing prevents two overlapping absences for one employee.

## 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
- user_id (integer → User.id, required): The employee this absence is for. Writing it requires the `absences::manage` permission.
- manager_id (integer → User.id, required): Manager who filed or approved the absence, or `null` when none was recorded. The foreign key nulls on delete, so removing the manager leaves the absence behind unattributed.
- start (timestamp (YYYY-MM-DDThh:mm:ssZ), required): When the absence begins. RFC 3339 UTC.
- end (timestamp (YYYY-MM-DDThh:mm:ssZ) or null, optional): When the absence ends, or `null` while it is still running. An ongoing absence reaches through now everywhere it is read (planning capacity and reporting both count it), and that reach is uncapped, so an absence nobody closed keeps consuming capacity. RFC 3339 UTC. Must be on or after start.

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

## Response 201
- id (integer, required): Unique identifier.
- user_id (integer, required): The employee this absence is for. Writing it requires the `absences::manage` permission.
- user (Ref or UserSummary, required): The employee the absence is for. Renders as an `{"id": N}` reference unless expanded, and stays a reference when the employee has since been deleted.
- manager_id (integer or null, required): Manager who filed or approved the absence, or `null` when none was recorded. The foreign key nulls on delete, so removing the manager leaves the absence behind unattributed.
- manager (Ref or UserSummary or null, required): The manager who filed or approved the absence; absent when none was recorded.
- start (timestamp (YYYY-MM-DDThh:mm:ssZ), required): When the absence begins. RFC 3339 UTC.
- end (timestamp (YYYY-MM-DDThh:mm:ssZ) or null, required): When the absence ends, or `null` while it is still running. An ongoing absence reaches through now everywhere it is read (planning capacity and reporting both count it), and that reach is uncapped, so an absence nobody closed keeps consuming capacity. RFC 3339 UTC.
- 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.
- links (AbsenceLinks, 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/absences' \
  -H 'Authorization: Bearer <token>' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <key>' \
  -d '{
    "user_id": 1,
    "manager_id": 1,
    "start": "2026-03-02T08:00:00Z",
    "end": "2026-03-06T17:00:00Z"
  }'
```
