---
title: "Sequences"
description: "Open a sequence behind the sequences_enabled gate, admit and remove its frames, and read its gaps and progress."
published: 2026-09-27T14:10:12.786416+00:00
updated: 2026-09-27T14:10:12.786416+00:00
tags: ["api", "parallax", "reference", "rest"]
url: https://xiobjects.com/docs/xio/parallax/rest-api/reference/sequences
source: XI Objects
---

<!-- xion:doctype xion+markdown -->
<!-- xion:metadata
{
  "version": "1.0",
  "content_type": "application/xion\u002Bmarkdown",
  "source_type": "xi-content/doc",
  "generator": "xio-content-publisher/1.0.0",
  "generated": "2026-09-27T14:09:20.1707213\u002B00:00",
  "encoding": "utf-8",
  "render_intent": "markdown",
  "title": "Sequences",
  "slug": "xio/parallax/rest-api/reference/sequences",
  "copyright": "\u00A9 2026 XI Objects Inc"
}
-->

# Sequences

A sequence is a chain of PX frames a per-account gate must be enabled for. Every route below `/sequences/{sequenceId}` also requires the `X-Sequence-Ticket` header POST /sequences answered - it is verified against the calling account and that sequence id before anything is read, with no expiry: a ticket dies with its sequence, not with time.

## POST /sequences

Opens a new sequence for the calling account. Refused with `403 Sequences not enabled` unless the account's `sequencesEnabled` flag is set. Accepts `multipart/form-data`.

**Parts:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `expectedSize` | Form field | No | Advisory expected frame count (S2). A positive integer; never compared with a limit. |
| `manifest[<kind>]` | Form part | No | Zero or more manifests attached to the sequence, same idiom as [Registrations](/docs/xio/parallax/rest-api/reference/registrations). |

```bash
curl -sS -X POST "$BASE/sequences" \
  -H "Authorization: Bearer $TOKEN" \
  -F "expectedSize=12"
```

**Response: 201 Created**

| Field | Type | Description |
|-------|------|--------------|
| `sequenceId` | `uuid` | The server-issued sequence id. |
| `headFrame` | `string` | The sequence's HEAD frame, base64-encoded. |
| `ticket` | `string` | The opaque ticket this account presents on every later request for this sequence. |

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 400 | `Malformed request` | Not multipart with a boundary, `expectedSize` sent twice or not a positive integer, or a field this route does not accept |
| 400 | `Invalid manifest` | A manifest part was refused |
| 403 | `Sequences not enabled` | The account's `sequencesEnabled` flag is not set |
| 413 | `Manifest too large` | A manifest part is past its cap |

---

## POST /sequences/{sequenceId}/frames

Admits one or more PX BODY frames to the sequence as one batch, all or nothing. `multipart/form-data`, one `application/octet-stream` file part per frame in the order to admit them; a single part is a batch of one. A frame carries no length field of its own (the boundary is the only framing), so the whole request is bounded by the calling account's own `maxRequestBytes`: a declared `Content-Length` past it is refused before a byte is read, and a body that runs past it while being read is refused with every byte already read cleared from memory.

No sequence limit value is read to refuse this route - not a rate, not an in-flight count, not a per-frame or per-sequence frame cap. Admitting an END frame seals the sequence in the same call: no further frame may be added, though a member may still be replaced or removed until the sequence is committed.

```bash
curl -sS -X POST "$BASE/sequences/$SEQUENCE_ID/frames" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Sequence-Ticket: $TICKET" \
  -F "frames=@frame-010.px;type=application/octet-stream" \
  -F "frames=@frame-020.px;type=application/octet-stream"
```

**Response: 201 Created**

| Field | Type | Description |
|-------|------|-------------|
| `frames` | `array` | One entry per admitted frame, in request order. |
| `frames[].frameId` | `integer` | The frame's own id. |
| `frames[].errata` | `boolean` | True when the frame matched an already-registered original and carries no artifacts of its own. |
| `verdict` | `object, nullable` | The sequence's whole current verdict - present only when this batch admitted the END frame and sealed the sequence; null otherwise. Same shape as `GET .../progress`. |

A refused batch is refused whole - nothing in it is admitted, and the sequence's progress is unchanged. The refusal names the frame at fault; a batch frame that disagrees with a neighbour still in the pool is refused naming that **neighbour's** own frame id, not the disagreeing frame's. A request naming no frame part at all is `400 Malformed request`.

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 400 | `Malformed request` | No boundary, no frame part, or a body that ended unexpectedly |
| 400 | `Frame refused` | A frame is not a valid PX container, names another sequence, or is not BODY |
| 403 | `Sequences not enabled` | The account's `sequencesEnabled` flag is not set |
| 403 | `Sequence ticket refused` | The `X-Sequence-Ticket` header is missing, does not verify, or names a different account or sequence |
| 404 | `Sequence not found` | No sequence the caller's account holds matches this id |
| 409 | `Sequence does not accept this command` | The sequence is sealed, committed or abandoned and does not admit or replace a BODY frame |
| 409 | `Frame conflict` | The batch disagrees with the chain, repeats a frame hash already held, or the sequence changed mid-admission |
| 413 | `Request too large` | The whole request is past the account's own `maxRequestBytes` |
| 422 | `Image could not be checked` | The engine refused a frame, or the engine call itself failed |
| 503 | `Engine not configured` | No engine is wired |
| 500 | `Sequence admission failed` | An internal fault - a replaced member's pooled artifacts could not be read back, or the engine's answer broke its contract |

---

## DELETE /sequences/{sequenceId}/frames/{frameId}

Removes one BODY member from the sequence.

**Response: 204 No Content**

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 403 | `Sequences not enabled` | The account's `sequencesEnabled` flag is not set |
| 403 | `Sequence ticket refused` | The `X-Sequence-Ticket` header is missing, does not verify, or names a different account or sequence |
| 404 | `Sequence not found` | No sequence the caller's account holds matches this id |
| 404 | `Frame not found` | No member holds this frame id |
| 400 | `Frame is not a BODY frame` | The member at this frame id is HEAD, END or GAP |
| 409 | `Sequence does not accept this command` | The sequence is sealed, committed or abandoned |

---

## POST /sequences/{sequenceId}/commit

Commits a sealed sequence to the engine: its BODY frames stream to the engine in chunks, then one closing sequence record call. Takes no body and spends no grant.

**Response: 200 OK**

| Field | Type | Description |
|-------|------|-------------|
| `sequenceHash` | `string, nullable` | The sequence's own hash, once the record is written; null while it is not. |
| `outcome` | `string` | One of `registered`, `alreadyRegistered`, `incomplete`. |
| `sequenceRecordPublished` | `boolean` | True once the engine registered the sequence record. |
| `frames` | `array` | Every BODY frame's own state, in walk order. |
| `frames[].frameId` | `integer` | The frame's own id. |
| `frames[].state` | `string` | One of `published`, `alreadyPublished`, `notPublished`. |
| `frames[].originalImageHash` | `string, nullable` | The frame's own original image hash, once published; null while it is not. |
| `frames[].refusalName` | `string, nullable` | The engine's own refusal name, when the frame is not published. |

An `incomplete` answer is still a 200: at least one BODY is not yet published, or the record call was refused. Nothing is rolled back - the same call, sent again, resumes from the first unpublished frame.

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 403 | `Sequences not enabled` | The account's `sequencesEnabled` flag is not set |
| 403 | `Sequence ticket refused` | The `X-Sequence-Ticket` header is missing, does not verify, or names a different account or sequence |
| 404 | `Sequence not found` | No sequence the caller's account holds matches this id |
| 409 | `Sequence does not accept this command` | The sequence is committed or abandoned |
| 409 | `Sequence not committable` | The sequence is not sealed, not connected, carries a gap or an errata frame, holds no BODY, or a BODY holds no packet - every shortfall present is named |
| 503 | `Engine not configured` | No engine is wired |
| 500 | `Sequence commit failed` | The engine's sequence record answered a different sequence hash than the one computed here |

---

## GET /sequences/{sequenceId}/gaps

Answers one minted GAP frame per range the sequence's current chain carries - the same range and frame a member sent later at that id would fill.

**Response: 200 OK**

| Field | Type | Description |
|-------|------|-------------|
| `gaps` | `array` | One entry per gap range in the current chain. |
| `gaps[].from` | `integer` | The gap's first missing frame id. |
| `gaps[].to` | `integer` | The gap's last missing frame id. |
| `gaps[].gapFrame` | `string` | The minted GAP frame covering this range, base64-encoded. |

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 403 | `Sequences not enabled` | The account's `sequencesEnabled` flag is not set |
| 403 | `Sequence ticket refused` | The `X-Sequence-Ticket` header is missing, does not verify, or names a different account or sequence |
| 404 | `Sequence not found` | No sequence the caller's account holds matches this id |

---

## GET /sequences/{sequenceId}/progress

Answers the sequence's whole current verdict - the same one a sealing `POST .../frames` call already carries.

**Response: 200 OK**

| Field | Type | Description |
|-------|------|-------------|
| `state` | `string` | One of `open`, `sealed`, `committed`, `abandoned`. |
| `framesReceived` | `integer` | How many BODY members the sequence currently holds. |
| `expectedSize` | `integer, nullable` | The advisory expected frame count, as given at open or last amended. |
| `reach` | `integer` | The highest frame id the chain's walk from HEAD arrives at, contiguously. |
| `gaps` | `array` | The current chain's own gap ranges (`from`/`to`), with no frame bytes attached. |
| `errata` | `array` | Every BODY admitted as errata - its frame id and the registered original it matched. |
| `connected` | `boolean` | True when the chain's walk from HEAD reaches END with no gap. |

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 403 | `Sequences not enabled` | The account's `sequencesEnabled` flag is not set |
| 403 | `Sequence ticket refused` | The `X-Sequence-Ticket` header is missing, does not verify, or names a different account or sequence |
| 404 | `Sequence not found` | No sequence the caller's account holds matches this id |

---

## GET /sequences/{sequenceId}/results

Reads a committed sequence's own record.

**Response: 200 OK**

| Field | Type | Description |
|-------|------|-------------|
| `sequenceHash` | `string` | The committed sequence's own hash. |
| `outcome` | `string` | One of `registered`, `alreadyRegistered`. |
| `finalSize` | `integer` | How many BODY frames the committed sequence holds. |
| `committedAt` | `string` | When the sequence committed, ISO 8601. |

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 403 | `Sequences not enabled` | The account's `sequencesEnabled` flag is not set |
| 403 | `Sequence ticket refused` | The `X-Sequence-Ticket` header is missing, does not verify, or names a different account or sequence |
| 404 | `Sequence not found` | No sequence the caller's account holds matches this id |
| 409 | `Sequence does not accept this command` | The sequence has not committed yet - the detail names its current state |

---

## PUT /sequences/{sequenceId}/expected-size

Amends the sequence's advisory expected size (S2). Never compared with any limit - a value above the account's own `maxFramesPerSequence` is stored exactly as given.

**Request body:**

| Field | Type | Required | Description |
|-------|------|----------|--------------|
| `expectedSize` | `integer` | Yes | A positive integer. |

**Response: 204 No Content**

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 400 | `Malformed request` | `expectedSize` is missing or not a positive integer |
| 403 | `Sequences not enabled` | The account's `sequencesEnabled` flag is not set |
| 403 | `Sequence ticket refused` | The `X-Sequence-Ticket` header is missing, does not verify, or names a different account or sequence |
| 404 | `Sequence not found` | No sequence the caller's account holds matches this id |
| 409 | `Sequence does not accept this command` | The sequence is committed or abandoned |

---

## DELETE /sequences/{sequenceId}

Abandons the sequence at once: it moves to `abandoned`, every non-HEAD member is purged, and one custody `deleted` row is written per purged packet.

**Response: 200 OK**

| Field | Type | Description |
|-------|------|-------------|
| `sequenceId` | `uuid` | The sequence's id. |
| `state` | `string` | Always `abandoned`. |
| `packetsPurged` | `integer` | How many packets the abandon's purge deleted a custody row for. |

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 403 | `Sequences not enabled` | The account's `sequencesEnabled` flag is not set |
| 403 | `Sequence ticket refused` | The `X-Sequence-Ticket` header is missing, does not verify, or names a different account or sequence |
| 404 | `Sequence not found` | No sequence with this id exists for the calling account |
| 409 | `Sequence does not accept this command` | The sequence is committed or already abandoned |
<!-- xion:trust
{
  "v": 1,
  "canon_v": 1,
  "ctx": "xiobjects.com/content",
  "hash_blake3_hex": "cb776b1a3550f8235abfb0cfd9df876ec0a5fd528d746adc2f97e4c80c6609a9",
  "hash_sha256_hex": null,
  "sig_alg": "ed25519",
  "sig_b64": "0aM559qXqT-mnbZTgoyKo3vuV70Stkw8V9HT5mspU_CEw1apqnr8gAsMQyLQia4huie-QxeB6TSjCvG1c1oCCA",
  "pubkey_b64": "qvdEfSxeAWQCSmFhYZ6YX5kI935su0PASlrB7Yi2nJ8",
  "x509_chain_pem": [
    "-----BEGIN CERTIFICATE-----\r\nMIIB9TCCAaegAwIBAgIRAMAcad\u002BzF5t\u002B/s4nONSc6aAwBQYDK2VwMC4xLDAqBgNV\r\nBAMMI1hJIE9iamVjdHMgSW5jIENvbnRyb2wgSW50ZXJtZWRpYXRlMB4XDTI2MDky\r\nMzA0MTAzMFoXDTI2MTAyMzA0MTAzMFowSzEeMBwGA1UEAwwVeGlvLWNvbnRlbnQt\r\ncHVibGlzaGVyMRcwFQYDVQQKDA5YSSBPYmplY3RzIEluYzEQMA4GA1UECwwHQ29u\r\ndGVudDAqMAUGAytlcAMhAKr3RH0sXgFkAkphYWGemF\u002BZCPd\u002BbLtDwEpawe2Itpyf\r\no4G8MIG5MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG\r\nCCsGAQUFBwMkMGUGA1UdIwReMFyAFDspt5hZsP6rNX4Cq7owpMYa05OyoS6kLDAq\r\nMSgwJgYDVQQDDB9JbnN0aXR1dGUgb2YgUHJvdmVuYW5jZSBSb290IENBghRSYDf4\r\nsUJ\u002B9h\u002Bod0\u002BZRK/X/JSUBTAdBgNVHQ4EFgQUg7Gut2vWupuiLVcKgZt1GwdYmggw\r\nBQYDK2VwA0EA1cI0DTLhDQyTflrGrMlnMT/3Iw2c1OXVYphjr0uXnCmX1Dt5sNYT\r\niTgydyG3BPQqqiZ253V1ltTxT68ZA2gNAg==\r\n-----END CERTIFICATE-----\r\n",
    "-----BEGIN CERTIFICATE-----\r\nMIIByDCCAXqgAwIBAgIUUmA3\u002BLFCfvYfqHdPmUSv1/yUlAUwBQYDK2VwMCoxKDAm\r\nBgNVBAMMH0luc3RpdHV0ZSBvZiBQcm92ZW5hbmNlIFJvb3QgQ0EwHhcNMjUxMTAy\r\nMDMxNzEyWhcNMzAxMTAxMDMxNzEyWjAuMSwwKgYDVQQDDCNYSSBPYmplY3RzIElu\r\nYyBDb250cm9sIEludGVybWVkaWF0ZTAqMAUGAytlcAMhAFSS/pggSRmTcAMko7uc\r\nATH8OHgxVymd5mBFlPXbJkgio4GtMIGqMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD\r\nVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQ7KbeYWbD\u002BqzV\u002BAqu6MKTGGtOTsjBlBgNV\r\nHSMEXjBcgBQAZRTDswSVORu\u002BkUOKX6WvrOvmQKEupCwwKjEoMCYGA1UEAwwfSW5z\r\ndGl0dXRlIG9mIFByb3ZlbmFuY2UgUm9vdCBDQYIUJqoJlpiSFg\u002B7W5IJLMrLttgR\r\nQp4wBQYDK2VwA0EA5FOht7YOsVRPp/FOKMQ\u002B3Mo9JxrvGR3ylKWAWNm6OUV7N3DB\r\nI9cD62wU5I0d0EKDBy0CX9DnoqUyxv5yguraAA==\r\n-----END CERTIFICATE-----\r\n",
    "-----BEGIN CERTIFICATE-----\r\nMIIBaTCCARugAwIBAgIUJqoJlpiSFg\u002B7W5IJLMrLttgRQp4wBQYDK2VwMCoxKDAm\r\nBgNVBAMMH0luc3RpdHV0ZSBvZiBQcm92ZW5hbmNlIFJvb3QgQ0EwHhcNMjUxMTAy\r\nMDMwNTEyWhcNMzUxMDMxMDMwNTEyWjAqMSgwJgYDVQQDDB9JbnN0aXR1dGUgb2Yg\r\nUHJvdmVuYW5jZSBSb290IENBMCowBQYDK2VwAyEAEWNZl\u002Br3IC7\u002BgBh90Yo1kWk1\r\npZCVzVuFdFT7qBBU8W2jUzBRMB0GA1UdDgQWBBQAZRTDswSVORu\u002BkUOKX6WvrOvm\r\nQDAfBgNVHSMEGDAWgBQAZRTDswSVORu\u002BkUOKX6WvrOvmQDAPBgNVHRMBAf8EBTAD\r\nAQH/MAUGAytlcANBAO6QeydOFNrN75qNyftggYudsxMyl4w9qWkSdZ6hlhrRcbSr\r\niG9Si0kbrIJOwYB/LTBU0RM4Rl\u002Bo9PM3Qp0mPwo=\r\n-----END CERTIFICATE-----\r\n"
  ],
  "key_id": "i1xUjBgnfprOkR49BjDnH_u3g5aYtfteENcAGMjJGlA",
  "created_at": "2026-09-27T14:09:20Z"
}
-->