---
title: "Register a Sequence"
description: "Open a sequence behind the sequences_enabled gate, admit PX frames as batches, replace and fill by frame id, seal with END, then commit in chunks."
published: 2026-09-27T14:10:03.436351+00:00
updated: 2026-09-27T14:10:03.436351+00:00
tags: ["parallax", "rest", "sequences"]
url: https://xiobjects.com/docs/xio/parallax/rest-api/register-a-sequence
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:09.9536498\u002B00:00",
  "encoding": "utf-8",
  "render_intent": "markdown",
  "title": "Register a Sequence",
  "slug": "xio/parallax/rest-api/register-a-sequence",
  "copyright": "\u00A9 2026 XI Objects Inc"
}
-->

# Register a Sequence

A sequence is a different shape of registration from a slot: not many independent images, but one
ordered chain of PX frames - client-encoded units that are never a source video's frames one for
one - whose unit of upload is the frame and whose unit of meaning is the whole chain. Sequences are
a private-beta surface of their own: your account's `sequencesEnabled` flag must be on before any
`/sequences` route answers anything but a refusal, and only an operator can turn it on.

Everything below `/sequences/{sequenceId}` also requires the ticket the open call gave you. There
is no separate sequence guide for look-up; registering a sequence is the only conversation this
release ships.

## The gate

Every `/sequences` route, open included, is refused for an account whose `sequencesEnabled` flag is
not set:

```json
{ "title": "Sequences not enabled", "status": 403, "detail": "Sequences are not enabled for this account." }
```

Ask your operator to turn the flag on for your account. Nothing else about the API changes for
you until it is.

## Open a sequence

`POST /sequences` mints the sequence and its HEAD frame, and answers a ticket:

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

```json
{
  "sequenceId": "0195b6e0-1a2b-7c3d-9e4f-5a6b7c8d9e0f",
  "headFrame": "eF7cn9uKAjEQhF+lyPUuJJPJ4b7cVXAF...",
  "ticket": "eyJhbGciOiJFUzI1NiJ9.eyJhY2NvdW50SWQ..."
}
```

`expectedSize` is an optional, advisory frame count - it drives progress reporting, never
integrity, and is never compared against a limit. Manifest parts are optional too, in the same
`manifest[<kind>]` idiom [Registrations](/docs/xio/parallax/rest-api/reference/registrations) uses,
and attach to the sequence as a whole rather than to any one frame.

`headFrame` is the sequence's own HEAD frame, minted by the server and base64-encoded: the chain's
starting point, carrying no bucket of its own.

**The ticket has no expiry.** It is the signed pair of your account id and this sequence id. It
dies when the sequence does - on commit or on abandon - never on a clock. Present it as
`X-Sequence-Ticket` on every later request for this sequence:

```bash
export SEQUENCE_ID="0195b6e0-1a2b-7c3d-9e4f-5a6b7c8d9e0f"
export TICKET="eyJhbGciOiJFUzI1NiJ9.eyJhY2NvdW50SWQ..."
```

A ticket that does not verify, or that names a different account or a different sequence than the
route you called, is refused `403 Sequence ticket refused` before anything is read - the frame
batch below included.

## Admit frames, as one batch

`POST /sequences/{sequenceId}/frames` takes one or more PX frames, each its own
`application/octet-stream` file part, and admits them **as one batch, all or nothing**:

```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"
```

A refused batch changes nothing: not one frame in it is admitted, and the sequence's progress is
exactly what it was before you sent the request. Send the same batch again once you have fixed
whatever it named.

### Only a BODY frame arrives from you

A PX frame carries a type: HEAD, BODY, END or GAP. **HEAD and GAP are minted by the server and
refused from a client outright** - you never construct one, and the server refuses a container that
claims to be either with `400 Frame refused`. Only BODY and END arrive from you, one call at a
time or many in a batch. A frame whose header names a sequence other than the one in the URL is
refused the same way, and a container that does not parse, whose bucket table disagrees with its
own frame hash, or that fails any other structural check is refused before it is ever compared
against the chain.

Two BODY frames in the same sequence can never both stand as members under the same frame hash:
a batch that repeats one, against itself or against a member the batch keeps, is refused `409
Frame conflict` naming the repeat, before either frame ever reaches the engine. A frame hash is
unique across your whole account too, not just within one sequence: a frame whose hash a BODY
member of a different sequence of yours already holds is refused the same way. A repeated image
that reaches the engine under a different frame hash is the engine's own `FrameImageRepeated`
check's job - the backstop for whatever the chain's own hash check does not catch - and surfaces as
`422 Image could not be checked`.

### Gaps and reach

The chain is the walk from HEAD to END through each frame's own `prev`/`next` links. Two numbers
describe how much of it you have proven so far:

- **Reach** is the highest frame id the walk reaches from HEAD without meeting a missing link.
- **Gaps** are the ranges still missing - never single ids, always a `from`/`to` span, because the
  frame that will eventually fill one might cover several ids at once.

`GET /sequences/{sequenceId}/gaps` answers one minted GAP frame per range still open, so a client
that lost track of what it has sent can ask the server rather than replay everything:

```bash
curl -sS "$BASE/sequences/$SEQUENCE_ID/gaps" \
  -H "Authorization: Bearer $TOKEN" -H "X-Sequence-Ticket: $TICKET"
```

`GET /sequences/{sequenceId}/progress` answers the same reach and gaps alongside the sequence's
whole state, at any point in the conversation, not only once sealed.

### Replacing a frame, or a whole range

A frame's id is its identity; its `prev`/`next` links are its position, not a fixed slot - the
client owns the numbering and the server does not care what order requests arrive in.

- **Frame for frame:** send a frame under an id the sequence already holds, and it replaces the
  existing member. Its links must still agree with its neighbours, or the batch is refused naming
  that neighbour. Sending the exact same id and the exact same frame hash again is a no-op: nothing
  is re-admitted, nothing is re-charged, and the answer reads exactly as it did the first time.
- **Range replacement:** a frame whose `prev`/`next` span strictly encloses ids the pool currently
  holds evicts every one of them in one move - that is how one frame can stand in for a whole range
  a client decides to re-encode. The chain is rebuilt once over the result; it is never a function
  of the order batches arrived in.

Once an END frame has sealed the sequence (below), only a fill for a still-open gap or an exact
retransmit is accepted; any other change to a sealed shape is refused.

### Remove a frame

`DELETE /sequences/{sequenceId}/frames/{frameId}` removes one BODY member outright. Removing a
frame that has neighbours reopens the chain at that point - it becomes a gap by the same rule as
one that never arrived.

## END seals the sequence, and answers the verdict

Admitting an END frame - naming its own `prev` - seals the sequence in the same call that admits
it: no further frame may be added, though a member may still be replaced or removed until commit.
The response to a sealing batch carries the sequence's whole verdict, the same shape
`GET .../progress` answers at any time:

```json
{
  "frames": [ { "frameId": 40, "errata": false } ],
  "verdict": {
    "state": "sealed",
    "framesReceived": 4,
    "expectedSize": 12,
    "reach": 40,
    "gaps": [],
    "errata": [],
    "connected": true
  }
}
```

`connected: true` means the walk from HEAD reaches END with no gap left - the sequence is ready to
commit. A sealed sequence that still carries a gap, an errata frame, or fewer than three frames is
not yet committable; fill or replace what the verdict names and check again.

## Commit, in chunks, resumable

`POST /sequences/{sequenceId}/commit` takes no body:

```bash
curl -sS -X POST "$BASE/sequences/$SEQUENCE_ID/commit" \
  -H "Authorization: Bearer $TOKEN" -H "X-Sequence-Ticket: $TICKET"
```

Commit never hands every frame to the engine in one call. It streams the sealed chain's BODY
members in bounded chunks, publishing each chunk's frame records as it lands, then makes one
closing call that publishes the sequence record itself over the ordered frame list.

**`incomplete` is still a `200`, not a failure:**

```json
{
  "sequenceHash": null,
  "outcome": "incomplete",
  "sequenceRecordPublished": false,
  "frames": [
    { "frameId": 10, "state": "published", "originalImageHash": "3b8f1a...9e4c", "refusalName": null },
    { "frameId": 20, "state": "notPublished", "originalImageHash": null, "refusalName": null }
  ]
}
```

Nothing already published is rolled back. **Send the exact same commit call again** - it resumes
from the first unpublished frame rather than repeating what already landed, and once every BODY
frame is published it makes the closing sequence-record call and answers `outcome: "registered"`
(or `"alreadyRegistered"` if a retry finds the record already there). A sequence that is not sealed,
not connected, still carries a gap or an errata frame, or holds no packet for some BODY member is
refused `409 Sequence not committable` naming every shortfall present, never one at a time.

## Read the result back

`GET /sequences/{sequenceId}/results` reads a committed sequence's own record: the sequence hash,
its outcome, how many BODY frames it holds, and when it committed. It is a 404 like anything else
under `/sequences/{sequenceId}` if the sequence is not yours, and a 409 naming the current state if
you ask before it has committed.

## Abandon

`DELETE /sequences/{sequenceId}` throws the sequence away at once: it moves to `abandoned`, every
non-HEAD member is purged, and a custody `deleted` row is written for every purged packet. Nothing
was registered, so nothing is undone and nothing is charged. A committed or already-abandoned
sequence cannot be abandoned again; the call answers `409 Sequence does not accept this command`.

See [Sequences](/docs/xio/parallax/rest-api/reference/sequences) for every field, every status and
every refusal, and [Errors and refusals](/docs/xio/parallax/rest-api/errors-and-refusals) for what
each one means and what to do about it.
<!-- xion:trust
{
  "v": 1,
  "canon_v": 1,
  "ctx": "xiobjects.com/content",
  "hash_blake3_hex": "f9f550bf384525119c86c33cc54f46c71f074625155d2950d21e6334c6f07533",
  "hash_sha256_hex": null,
  "sig_alg": "ed25519",
  "sig_b64": "HPY4UKigxrITjQIo9FgW3GqtFzvm5yQR6C4KbsR8R1tz2ecsWbwUIzCklV7NVYa9PmD9gti1dw_qdUw47fUoBQ",
  "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:09Z"
}
-->