# Read durable committed changes

`GET /api/v1/data/changes` - [human page](https://aicdapi.com/api-reference#readCivicChanges)

**Release status.** Limited availability

Replay is retained for 30 days. Save the entire page and its effects before advancing the cursor. An empty page can advance to the feed high-water mark. A resource-filtered token remains bound to that filter. Cancellation is a meeting upsert; withdrawal is a remove.

**Authentication.** Customer API key: `Authorization: Bearer aicd_...`. A browser session does not authenticate this endpoint. Required scope: `civic:read`.

### Parameters

| Field | Type | Required | Bounds | Description |
| --- | --- | --- | --- | --- |
| `query cursor` | string | no | length 1..512 |  |
| `query resource` | one of 11 values | no |  |  |
| `query limit` | integer | no | >= 1, default `100` |  |

### Example request

```bash
curl -X GET 'https://aicdapi.com/api/v1/data/changes' \
  -H "Authorization: Bearer $AICD_API_KEY"
```

### Response 200

Ordered change page.

| Field | Type | Required | Bounds | Description |
| --- | --- | --- | --- | --- |
| `data` | object | yes |  |  |
| `data.items` | object[] | yes |  |  |
| `meta` | object | yes |  |  |
| `meta.nextCursor` | string | yes | length 1..512 | Opaque continuation token. Store and replay without modification. |
| `meta.hasMore` | boolean | yes |  |  |
| `meta.replayExpiresAt` | string (date-time) | yes |  | UTC timestamp ending in Z. |

### Errors

| Status | `error.code` | Description |
| --- | --- | --- |
| **400** | `invalid_parameter` | Invalid parameter, duplicate query field or malformed JSON. |
| **401** | `unauthorized` | Missing, invalid, expired or revoked credential. |
| **403** | `billing_channel_unset`, `billing_entitlement_required`, `forbidden` | Customer lacks the required grant. |
| **404** |  | Snapshot does not exist, belongs to another customer, or expired. |
| **410** | `cursor_expired` | Cursor is older than the retained replay floor; start a new snapshot. |
| **413** | `response_too_large` | Request or response exceeds its size cap; reduce the page limit. |
| **429** | `rate_limited` | A per-account request, burst or export allowance was exhausted, or the account-wide abuse ceiling was reached. The abuse ceiling is account-wide, is not charged against the paid quota, and is charged after the identity lookup and before a revoked, expired, unpaid, or scope-denied rejection is decided. |
| **500** | `internal_error` | Unexpected operation failure; the response contains no private database details. |
| **503** | `authentication_unavailable`, `feed_unavailable` | Authentication, limit store or feed state is unavailable. |
| **504** | `timeout` | Database statement or request deadline exceeded. |

### Behaviour

- **Pagination.** Opaque cursor pagination. Send the previous response's `meta.nextCursor` back as `cursor`. A cursor minted with a resource filter stays bound to it.
- **Limits.** Counted against the per-account minute and burst allowances. A paid subscription uses its plan's own limits, which the public usage limits help lists and the account console shows. An account with no paid plan is limited by the stored account defaults, which the account console also shows. A separate account-wide abuse limit also applies to recognized subscription keys, counted separately for usable keys and for revoked or expired ones, so a compromised key you rotate does not reduce the new key's capacity. It is charged before a denial is decided and is not charged against your paid quota.
- **Retry-After.** Sent on 429 only, as whole seconds.
- **Caching.** Customer responses must not be cached.
