# List sealed store changes

The sync read, as a delta feed.

- **Full read.** Without `since`, the feed returns every record in the store, live and tombstoned, in change order.
- **Delta read.** With `since`, it returns every record created, updated, deleted or undeleted after that cursor. Each record appears once, in its current state; a deleted record arrives as its envelope-only tombstone.


Follow `next_cursor` while `has_more` is `true`, then keep the last `next_cursor` for the next sync. Cursors are opaque.

**`410 SEALED_CURSOR_EXPIRED` means "do a full read and reconcile".** A cursor issued more than 30 days ago is refused, and so is one from before a tombstone that has since been removed (after its 30 days, or early because the store keeps only its newest 500): the delta could no longer tell the device about that delete. Read the store again without `since` and reconcile the device's records against the full read. A synced record the device holds that the full read does not return is no longer in the store.

A page stops early once it carries 8 MiB of decoded payload, never below one record, so `has_more` can be `true` with fewer than `limit` records.

#### Path Parameters

**`store`** string required

The store: `aitronos-browser`. Up to 64 characters; an unknown store is `404 SEALED_STORE_NOT_FOUND`.

#### Query Parameters

**`since`** string optional

The `next_cursor` of an earlier page, up to 512 characters. Leave it out for a full read.

**`limit`** integer optional

The most records in this page, 1 to 500. Defaults to 100.

#### Headers

**`Authorization`** string required

`Bearer <access token>` from the person's own sign-in. API keys and service accounts are refused with `403 SEALED_STORE_PERSONAL_SESSION_REQUIRED`.

## Returns

`200 OK`:

- **`object`** string — `sealed_change_page`.
- **`full_read`** boolean — `true` when the request carried no `since`.
- **`changes`** array — the changed records in change order, each in its current state (see below). A deleted record is its tombstone.
- **`next_cursor`** string — always present. Opaque: store it and send it back, never parse it. Send it as `since` for the next page while `has_more` is `true`, and keep the last one for the next sync.
- **`has_more`** boolean — whether more changes follow this page.


Each entry of `changes`:

- **`object`** string — `sealed_record`.
- **`id`** string — the client's record id.
- **`kind`** string — `session` or `favourites`.
- **`schema_version`** integer — as the last accepted write sent it.
- **`revision`** integer — server-assigned; rises by 1 on every accepted write.
- **`created_at`** string — when the record was created (ISO 8601 UTC).
- **`last_touched_at`** string — set by the server on every accepted write. Sort by it, ties by `id`.
- **`deleted`** boolean — whether the record is a tombstone.
- **`deleted_at`** string or null — when it was deleted.
- **`device_id`** string — the device of the last accepted write.
- **`wrapped_dek`** object or null — the data key that opens `payload`: `v`, `aead`, `nonce`, `ct`, `epk`. `v` is always the integer `1`. `null` for a tombstone.
- **`payload`** string or null — the sealed bytes, standard padded base64. `null` for a tombstone.
- **`payload_bytes`** integer or null — the decoded payload size. `null` for a tombstone.


## Errors

| Status | Code | When |
|  --- | --- | --- |
| 401 | `AUTHENTICATION_REQUIRED` | No access token, or one that is not valid. |
| 403 | `SEALED_STORE_PERSONAL_SESSION_REQUIRED` | The call used an API key or a service account. Only a person's own sign-in session reaches a sealed store. |
| 404 | `SEALED_STORE_NOT_FOUND` | No store with that name (`details.store`). |
| 410 | `SEALED_CURSOR_EXPIRED` | The cursor was issued more than 30 days ago, or a tombstone after it has since been removed (`details.retention_days`). Do a full read and reconcile. |
| 422 | `SEALED_CURSOR_INVALID` | The cursor was not issued by this store. Do a full read. |
| 422 | `VALIDATION_ERROR` | `limit` is outside 1 to 500, or `since` is longer than 512 characters. |



```bash cURL
# Full read: no cursor.
curl "https://api.aitronos.com/v1/sealed-stores/aitronos-browser/changes?limit=100" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Next sync: everything after the cursor kept from the last one.
curl "https://api.aitronos.com/v1/sealed-stores/aitronos-browser/changes?since=ZjE6NTc6MTc5MTYyNDAwMA&limit=100" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```


```python Python
import os

import requests

URL = "https://api.aitronos.com/v1/sealed-stores/aitronos-browser/changes"
headers = {"Authorization": f"Bearer {os.environ['AITRONOS_TOKEN']}"}
local = {}     # record id -> record, as this device holds them
cursor = None   # the next_cursor kept from the last sync; None means a full read
full_read, seen = cursor is None, set()

while True:
    params = {"limit": 100} if cursor is None else {"limit": 100, "since": cursor}
    response = requests.get(URL, headers=headers, params=params, timeout=30)
    body = response.json()
    if response.status_code in (410, 422) and cursor is not None:
        # SEALED_CURSOR_EXPIRED or SEALED_CURSOR_INVALID: start again with a full read.
        cursor, full_read, seen = None, True, set()
        continue
    response.raise_for_status()
    for record in body["changes"]:
        local[record["id"]] = record  # a tombstone has deleted == True and no payload
        seen.add(record["id"])
    cursor = body["next_cursor"]
    if not body["has_more"]:
        break

if full_read:
    for record_id in set(local) - seen:
        del local[record_id]  # no longer in the store at all
print(len(local), "records; keep", cursor, "for the next sync")
```


```javascript JavaScript
const URL = "https://api.aitronos.com/v1/sealed-stores/aitronos-browser/changes";
const headers = { Authorization: `Bearer ${process.env.AITRONOS_TOKEN}` };
const local = new Map(); // record id -> record, as this device holds them
let cursor = null; // the next_cursor kept from the last sync; null means a full read
let fullRead = cursor === null;
let seen = new Set();

while (true) {
  const params = new URLSearchParams({ limit: "100" });
  if (cursor) params.set("since", cursor);
  const response = await fetch(`${URL}?${params}`, { headers });
  const body = await response.json();
  if ((response.status === 410 || response.status === 422) && cursor) {
    // SEALED_CURSOR_EXPIRED or SEALED_CURSOR_INVALID: start again with a full read.
    cursor = null;
    fullRead = true;
    seen = new Set();
    continue;
  }
  if (!response.ok) throw new Error(body.error.code);
  for (const record of body.changes) {
    local.set(record.id, record); // a tombstone has deleted === true and no payload
    seen.add(record.id);
  }
  cursor = body.next_cursor;
  if (!body.has_more) break;
}

if (fullRead) {
  for (const id of local.keys()) if (!seen.has(id)) local.delete(id); // gone from the store
}
console.log(local.size, "records; keep", cursor, "for the next sync");
```

**Response:**

200 Full read

```json
{
  "object": "sealed_change_page",
  "full_read": true,
  "changes": [
    {
      "object": "sealed_record",
      "id": "z4rSQRGzUlRnQMJmA0W1Uw",
      "kind": "session",
      "schema_version": 1,
      "revision": 2,
      "created_at": "2026-10-06T14:22:31.845570Z",
      "last_touched_at": "2026-10-09T21:14:50.093318Z",
      "deleted": true,
      "deleted_at": "2026-10-09T21:14:50.093318Z",
      "device_id": "1f783def-2aa1-4b44-9c73-3c6b9102c06b",
      "wrapped_dek": null,
      "payload": null,
      "payload_bytes": null
    },
    {
      "object": "sealed_record",
      "id": "F1PMCmFG6rsW4-HU4afL_g",
      "kind": "favourites",
      "schema_version": 1,
      "revision": 7,
      "created_at": "2026-10-08T17:20:09.381146Z",
      "last_touched_at": "2026-10-10T08:55:12.774031Z",
      "deleted": false,
      "deleted_at": null,
      "device_id": "1aee2c02-ccac-4ede-b017-1334be895e2e",
      "wrapped_dek": {
        "v": 1,
        "aead": "aes-256-gcm",
        "nonce": "K4oMLrGJ4dOy3SSa",
        "ct": "KF2aNtgNDsrl44sR1t60za/U14/38uhVtZR5x1IwVuE1ZA9O40F7m+B3Z5c4F5ff",
        "epk": "YTA186lzj639ytab4sP9at47MIW6rLw1mPH+rLf5zYk="
      },
      "payload": "KwbjgOqLohvGjo8CWi61+eqKsSS+Hr8joqdGVDKWywAlQ10opPF9w3HUCpIUC+fHif6s4NscN7iuK7ewtpt27Hams2lMd2TDStW8nnG53gk=",
      "payload_bytes": 80
    },
    {
      "object": "sealed_record",
      "id": "vSWmWbg9sDur336pnYgYzQ",
      "kind": "session",
      "schema_version": 1,
      "revision": 2,
      "created_at": "2026-10-10T08:41:17.204913Z",
      "last_touched_at": "2026-10-10T09:02:44.918207Z",
      "deleted": false,
      "deleted_at": null,
      "device_id": "1aee2c02-ccac-4ede-b017-1334be895e2e",
      "wrapped_dek": {
        "v": 1,
        "aead": "aes-256-gcm",
        "nonce": "oxwDDz+SyxUEkitE",
        "ct": "UvORUdzuxuE3FMncUQgc4qOEz2QT8a5LarNYrATM0A4Zi1J0aJj9Yu+5ySIKPQHY",
        "epk": "fRXpvCBVDmU9kJ+PSkoqi7NU2Po3FQMK9SbJiz1gOGs="
      },
      "payload": "hWbThAu7J8u8jIcvARYROTja+vbbV4hlntXsIctvvcVdqWGMyTjVt2LgISSfrFORn+gjkJcDsT9l8dsHXoFFCaDLzP06FHLuixGaBMY3WOfWFex4l7SU0G5IKY7DABr74o0l20asWcd9gADrlQl4Zg==",
      "payload_bytes": 112
    }
  ],
  "next_cursor": "ZjE6NTc6MTc5MTYyNDAwMA",
  "has_more": false
}
```

200 Since a cursor

```json
{
  "object": "sealed_change_page",
  "full_read": false,
  "changes": [
    {
      "object": "sealed_record",
      "id": "vSWmWbg9sDur336pnYgYzQ",
      "kind": "session",
      "schema_version": 1,
      "revision": 3,
      "created_at": "2026-10-10T08:41:17.204913Z",
      "last_touched_at": "2026-10-10T09:30:05.551390Z",
      "deleted": true,
      "deleted_at": "2026-10-10T09:30:05.551390Z",
      "device_id": "1f783def-2aa1-4b44-9c73-3c6b9102c06b",
      "wrapped_dek": null,
      "payload": null,
      "payload_bytes": null
    },
    {
      "object": "sealed_record",
      "id": "kzhPW9s59kDjN4KgsqDo8g",
      "kind": "session",
      "schema_version": 1,
      "revision": 1,
      "created_at": "2026-10-10T09:58:40.207733Z",
      "last_touched_at": "2026-10-10T09:58:40.207733Z",
      "deleted": false,
      "deleted_at": null,
      "device_id": "1aee2c02-ccac-4ede-b017-1334be895e2e",
      "wrapped_dek": {
        "v": 1,
        "aead": "aes-256-gcm",
        "nonce": "CbsPu3yjtX0WfjiR",
        "ct": "OstmROyv3oyMewk3pmThll7VuC/LMwdptAkkpaaDq0yLCK9YetMUPud+vBPfdIbR",
        "epk": "+5hqSqzsvkTA3oKtoXE9Q8a5QQ3vTgv3VfDaB1rI7YY="
      },
      "payload": "WwJa6HYrSprDW7CSy0gXmpZ/Rnrlr+/JedudlqwoITqebUB01KbKYCN4xXL4nT+BHZIYWC+4vRxVOKH23RB9votU7iS+cX6E",
      "payload_bytes": 72
    }
  ],
  "next_cursor": "ZjE6NTk6MTc5MTYyNjcxMg",
  "has_more": false
}
```

410 Cursor expired

```json
{
  "success": false,
  "error": {
    "code": "SEALED_CURSOR_EXPIRED",
    "message": "This device was away too long to catch up. Syncing everything.",
    "system_message": "the cursor is older than the 30-day tombstone retention; do a full read (no cursor) and reconcile",
    "type": "client_error",
    "status": 410,
    "details": {
      "retention_days": 30
    },
    "trace_id": "343c080a-8696-4a8c-a26d-8bd7cdc63a17",
    "timestamp": "2026-10-10T10:05:30.882104Z"
  }
}
```

## Related Resources

- [Sealed stores](/docs/api-reference/sealed-stores/introduction)
- [Get a sealed record](/docs/api-reference/sealed-stores/get-record)
- [List deleted sealed records](/docs/api-reference/sealed-stores/list-deleted)
- [Create a sealed record](/docs/api-reference/sealed-stores/create-record)
- [Get a sealed store's limits](/docs/api-reference/sealed-stores/get-store)