> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.westiq.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.westiq.ai/_mcp/server.

# Completed Update Delivery

> **Warning**
>
> **Not available on deployed endpoints.** This API is disabled by default.
> These pages describe the prepared contract; they do not announce availability.
> Separate activation and delivery permission are required before use.

Completed delivery retains finished updates so an authorized service can recover
them in order across reconnects. It has **one logical consumer per lease**. It is
separate from [Session Events](/guides/session-events): those event frames and
their cursors are not delivery updates, receipts, or acknowledgements.

## Authentication and ownership

Use the [OAuth2 service access token](/guides/authentication) with the required
`westiq/search` scope. Delivery also requires explicit permission for the exact
calling service identity. An ordinary API credential does not grant that permission.
Tenant and client ownership come from the token's established server-side mapping.
Never supply tenant or client authority in the body, query, or headers.

The seven scoped routes use the server-minted passenger session's canonical,
lowercase, hyphenated, non-nil UUID in `{id}`. It is a **locator, not a credential**.
Do not substitute `X-Session-Id`, `seatId`, `memberSlot`, or a query parameter.
Delivery rejects `X-Airline-Id`, `X-Tenant-Id`, `X-Client-Id`, and `X-Session-Id`.
Receipts likewise grant no authentication or ownership authority.

The authorized service must validate the browser session cookie before opening a
lease and attest its expiry in `cookieExpiresAtMs`. This is a trust boundary:
the platform cannot detect an authorized service deliberately re-attesting an old
session under a new lease generation. Keep browser-cookie validation and freshness
checks in that service; a new generation is not proof of a fresh browser session.

## Operations and shapes

All success responses include `schemaVersion: "1"` and `correlationId`.
The **Completed delivery** group in the [API Reference](/api-reference) defines
the complete generated request and response shapes, including the eight update
families. Objects are closed recursively: unknown fields are refused.

| Method | Path                                      | Request fields beyond `schemaVersion`                       | Success fields beyond the shared fields                                                      |
| ------ | ----------------------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| GET    | `/v1/delivery/slots/available`            | None                                                        | `slot`, `expectedGeneration`                                                                 |
| POST   | `/v1/sessions/{id}/delivery/open`         | `slot`, `expectedGeneration`, `openId`, `cookieExpiresAtMs` | `epoch`, `expiresAtMs`, `committedThrough`, `acknowledgedThrough`                            |
| GET    | `/v1/sessions/{id}/delivery`              | None                                                        | `epoch`, `expiresAtMs`, `state`, `committedThrough`, `acknowledgedThrough`, `prunedThrough`  |
| POST   | `/v1/sessions/{id}/delivery/append`       | `epoch`, `updateId`, `update`                               | `epoch`, `updateId`, `sequence`, `payloadSha256`                                             |
| POST   | `/v1/sessions/{id}/delivery/receipt-slot` | `epoch`                                                     | `cell`, `expectedGeneration`                                                                 |
| POST   | `/v1/sessions/{id}/delivery/read`         | `epoch`, `after`, `readId`, `cell`, `expectedGeneration`    | `epoch`, `status`, `events`, `nextCursor`, `committedThrough`, `hasMore`, optional `receipt` |
| POST   | `/v1/sessions/{id}/delivery/ack`          | `epoch`, `through`, `receipt`                               | `epoch`, `acknowledgedThrough`, `status`                                                     |
| POST   | `/v1/sessions/{id}/delivery/close`        | `epoch`                                                     | `epoch`, `state`                                                                             |

Open and append return **201 for a new commit, 200 for exact replay**. Other
successful operations return 200. Slot/cell discovery is advisory, not a reservation.

* `epoch` contains `slot` and `generation`. Slot is an integer from 0 through 29;
  receipt cell is an integer from 0 through 7.
* Generations, sequences, cursors and frontiers are canonical **decimal strings**,
  from `"0"` through `"18446744073709551615"`, with no leading zeros. Do not convert
  them to a floating-point number.
* `openId`, `updateId`, `readId` and receipt `token` are lowercase hyphenated UUIDs.
  The nil UUID is valid for these fields, unlike the session locator.
* Timestamps are nonnegative integer Unix milliseconds, no larger than
  `9007199254740991`.
* Each event contains `sequence`, `updateId`, `payloadSha256`, and `update`.
  A receipt contains `cell`, `generation`, `token`, `from`, `through`, and
  `expiresAtMs`. Ack sends only its proof fields: `cell`, `generation`, and `token`.

GET requests have no body or query parameters. POST requests use
`application/json`, optionally with UTF-8 charset, with no content encoding.
Duplicate properties, malformed UTF-8, unknown fields and excessive nesting refuse.

## Commit, read, apply, acknowledge

1. Find an available slot, then open it with a stable `openId` and the returned
   generation. Preserve that entire request when retrying.
2. Complete and freeze an update before append. Preserve its `updateId` and body
   across retries. **Notify the consumer only after append reports durable success.**
   Preparing a payload or allocating an ID is not a commit.
3. Obtain an available receipt cell. Read after the consumer's applied cursor,
   with a stable `readId`, cell and expected cell generation. Preserve the full
   read request when retrying.
4. Apply the returned contiguous range. `nextCursor` is the last returned sequence,
   not `committedThrough`. Only then acknowledge the applied range with its receipt.
   Receipt proof records delivery, not whether the consumer applied it.
5. Close when finished. Closing prevents new appends and receipts; it does not
   promise immediate capacity release or slot reuse.

Read returns at most 100 contiguous updates and considers at most one additional
candidate. Response size can reduce the page further. A `page` result carries
events and a receipt. An `empty` result echoes the supplied cursor. A known fully
acknowledged retry returns `already_applied`, no events or receipt, and the original
supplied cursor. Neither result advances local application state by itself.

An exact live read retry preserves the original range, token and expiry; **it does
not renew the receipt**. Closed or lease-expired reads refuse, including replay.
Ack returns `advanced` or `already_applied`; an already applied acknowledgement can
remain a no-op while its owned generation is still addressable. A receipt for a
different owner, generation or range does not authorize an acknowledgement.

A gap is a refusal, not permission to skip updates or invent a new cursor. A
timeout or client disconnect is **not proof of rollback**. An ambiguous mutation
may already have committed: resolve or repeat the same identity and request, never
generate a replacement ID or automatically restart inference. The request has one
shared two-second operation budget; this is not a network-latency guarantee.

## Time and capacity limits

| Limit                                | Contract                                              |
| ------------------------------------ | ----------------------------------------------------- |
| Lease                                | At most two hours; cannot outlive the attested cookie |
| Event recovery retention             | 15 minutes                                            |
| Receipt proof                        | At most 120 seconds, bounded by lease expiry          |
| Receipt cells                        | Eight fixed cells per lease                           |
| Lease pool                           | 30 slots                                              |
| Retained events per lease            | 2,000                                                 |
| Charged event data per lease         | 32 MiB                                                |
| Retained update identities per lease | 10,000                                                |

Capacity exhaustion refuses visibly; updates are not overwritten or silently
truncated. Ack does not immediately delete events or release capacity. Logical
expiry and close likewise do not imply immediate physical deletion. Reuse requires
confirmed reclamation; clients must not assume that a timer automatically frees a slot.

Request-rate and zero-queue concurrency gates apply before body processing. Their
process limits are not a promise of a global quota across service instances.

## Byte and payload limits

| Representation                              | Maximum                                                    |
| ------------------------------------------- | ---------------------------------------------------------- |
| Append request, including wrapper           | 393,216 actual streamed bytes                              |
| Other POST request body                     | 16,384 actual streamed bytes                               |
| Completed update                            | 262,144 canonical UTF-8 bytes, before and after protection |
| Entire response, including receipt metadata | 524,288 encoded UTF-8 bytes                                |

Declared content length cannot replace the actual byte count. Field bounds count
**decoded UTF-8 bytes**, not characters: generally 16 KiB prose, 32 KiB reasoning
narrative, 128-byte identifiers and 2 KiB URI/path fields. The API Reference gives
each field's bound; array, nesting and total-envelope bounds apply together.

The eight update families are `score-update`, `content-rerank`, `persona-update`,
`context-update`, `destination-update`, `shopping-update`, `terminal-event`, and
`error`. Outer `enrichmentCycle` and `timestamp` must match the corresponding
`data` values. Required, optional and nullable fields are distinct. Numeric ranges
are validated without coercion or clamping. Shopping `forYouRow` admits at most
12 products and at most two per category.

Every declared string is protected. If protection would alter an identifier or
link, the update refuses rather than breaking its references. Unknown extension
objects and raw exceptions are not accepted. Do not recursively append delivery
errors into a lease that is already exhausted or refusing writes.

## Refusals and retry

Delivery operation errors use `schemaVersion`, `code`, a generic `message`,
`correlationId`, and **explicit `ruleId: null`** when no rule applies. This is a
delivery-specific exception to the null omission described for other endpoints in
[Errors & Limits](/guides/errors). Foreign and missing ownership both yield a
generic not-found response; no error discloses a foreign owner or internal state.

| HTTP | Codes                                                                                     |
| ---- | ----------------------------------------------------------------------------------------- |
| 400  | `request.malformed`; existing `session.unresolved` for an unsafe or missing route locator |
| 401  | `auth.invalid_token`                                                                      |
| 403  | `auth.insufficient_scope`, `auth.unknown_client`, `delivery.issuer_denied`                |
| 404  | `delivery.not_found`                                                                      |
| 409  | `delivery.identity_conflict`, `delivery.generation_conflict`, `delivery.gap`              |
| 410  | `delivery.expired`, `delivery.closed`, `delivery.receipt_expired`                         |
| 413  | `delivery.payload_too_large`                                                              |
| 415  | `delivery.media_type`                                                                     |
| 422  | `delivery.schema_invalid`, `delivery.cursor_invalid`, `delivery.proof_invalid`            |
| 429  | `delivery.capacity`, `rate.limited`                                                       |
| 503  | `delivery.unavailable`, `delivery.ambiguous`, `delivery.integrity`                        |

Use a finite `Retry-After` hint when supplied. Preserve the same request identity
after ambiguity. Integrity refusals retain conservative capacity and require
investigation; do not fabricate replacement history.

### Empty routing responses

These responses have **no error envelope or body** and occur before authentication
or body processing:

* An unsupported method on an exact known delivery path returns **405** with
  `Allow` naming the accepted method. There is no implicit HEAD operation.
* Unknown delivery paths, empty session segments, repeated or encoded separators,
  and trailing or extra delivery path segments return **404**. They are never
  normalized into a valid operation.

Query parameters cannot select an operation or supply ownership. Unrelated API
paths retain their existing routing behavior.