# Replay durable public events

Forward publication order. Pass cursor and the same projectId, threadId, runId, and comma-separated types filters. limit defaults to 100 and is bounded to 500. Retention is seven days; an expired cursor returns 410 and requires a fresh snapshot. Platform app events are not included; read them through the Platform API events endpoints.

`GET /api/events`

Required permission: `thread:read`.

## Parameters

| Name | In | Type | Required | Description |

| --- | --- | --- | --- | --- |

| `projectId` | query | `string` | No | — |

| `threadId` | query | `string` | No | — |

| `runId` | query | `string` | No | — |

| `types` | query | `string` | No | — |

| `cursor` | query | `string` | No | — |

| `limit` | query | `integer` | No | — |

## Example request

```bash
curl --request GET \
  --url https://api.hoplite.sh/api/events \
  --header "X-Api-Key: $HOPLITE_API_KEY"
```

## 200 response

Successful response

Content type: `application/json`. Response type: `object`.

| Field | Type | Required | Constraints | Description |
| --- | --- | --- | --- | --- |
| `ok` | `boolean` | Yes | constant true | — |
| `events` | `object[]` | Yes | — | — |
| `events.id` | `string` | Yes | — | — |
| `events.orgId` | `string` | Yes | — | — |
| `events.projectId` | `string \| null` | Yes | — | — |
| `events.threadId` | `string \| null` | Yes | — | — |
| `events.runId` | `string \| null` | Yes | — | — |
| `events.sequence` | `string` | Yes | pattern ^\d+$ | — |
| `events.type` | `string` | Yes | — | — |
| `events.data` | `object` | Yes | — | — |
| `events.data.id` | `string` | Yes | — | Resource ID: thread, run, message, tool call, approval, checkpoint, preview, pull request, automation execution, operation receipt, platform source, apply or build. platform.head.updated uses the project ID and platform.app.* events the app ID. |
| `events.data.status` | `string` | No | — | Current resource status or state, when the resource has one. |
| `events.data.role` | `string` | No | — | Message role for message.persisted. |
| `events.data.sourceEventId` | `string` | No | — | Run event that requested an approval. |
| `events.data.sourceId` | `string` | No | — | Platform source version: an apply's result, a build's input or the project's new head. |
| `events.data.revision` | `integer` | No | min -9007199254740991, max 9007199254740991 | Platform project head revision for platform.head.updated. |
| `events.data.sha256` | `string` | No | — | Content hash of an imported platform source. |
| `events.data.byteSize` | `integer` | No | min -9007199254740991, max 9007199254740991 | Size of an imported platform source archive. |
| `events.data.fileCount` | `integer` | No | min -9007199254740991, max 9007199254740991 | Files in an imported platform source. |
| `events.data.artifactSha256` | `string` | No | — | Hash of a ready platform build artifact. |
| `events.data.externalId` | `string \| null` | No | — | Your identifier for the app on platform.app.* events; null when unset or cleared. |
| `events.data.createdAt` | `string<date-time>` | No | — | When the app was created, on platform.app.* events. |
| `events.data.updatedAt` | `string<date-time>` | No | — | When the app was last updated, on platform.app.* events. |
| `events.occurredAt` | `string<date-time>` | Yes | — | — |
| `events.publishedAt` | `string<date-time>` | Yes | — | — |
| `events.cursor` | `string` | Yes | — | — |
| `nextCursor` | `string` | Yes | — | — |
| `hasMore` | `boolean` | Yes | — | — |

```json
{
  "ok": true,
  "events": [
    {
      "id": "event_example",
      "orgId": "org_example",
      "projectId": "project_example",
      "threadId": "thread_example",
      "runId": "run_example",
      "sequence": "42",
      "type": "run.completed",
      "data": {
        "id": "run_example",
        "status": "completed"
      },
      "occurredAt": "2026-09-22T10:00:00.000Z",
      "publishedAt": "2026-09-22T10:00:01.000Z",
      "cursor": "opaque-cursor"
    }
  ],
  "nextCursor": "opaque-cursor",
  "hasMore": false
}
```

## Response headers

| Header | Type | Statuses | Description |

| --- | --- | --- | --- |

| `RateLimit` | `string` | `200`, `400`, `401`, `402`, `403`, `404`, `409`, `410`, `429`, `500`, `501`, `503` | Current ingress policy, remaining requests, and seconds until reset, for example `"ingress";r=599;t=60`. |

| `RateLimit-Policy` | `string` | `200`, `400`, `401`, `402`, `403`, `404`, `409`, `410`, `429`, `500`, `501`, `503` | Ingress quota and window, for example `"ingress";q=600;qu="requests";w=60`. |

| `X-RateLimit-Limit` | `integer` | `200`, `400`, `401`, `402`, `403`, `404`, `409`, `410`, `429`, `500`, `501`, `503` | Maximum requests allowed in the current rolling window. |

| `X-RateLimit-Remaining` | `integer` | `200`, `400`, `401`, `402`, `403`, `404`, `409`, `410`, `429`, `500`, `501`, `503` | Requests remaining in the current rolling window. |

| `X-RateLimit-Reset` | `integer` | `200`, `400`, `401`, `402`, `403`, `404`, `409`, `410`, `429`, `500`, `501`, `503` | Unix timestamp when the current rolling window resets. |

| `x-request-id` | `string` | `200`, `400`, `401`, `402`, `403`, `404`, `409`, `410`, `429`, `500`, `501`, `503` | Request correlation ID. |

| `Retry-After` | `integer` | `429`, `503` | Seconds until another request should be attempted. |

| `X-Retry-After` | `integer` | `429`, `503` | Legacy retry delay in seconds. |

## Status codes

- `200` — Successful response

- `400` — Invalid request. Validation responses include field paths.

- `401` — The API key is missing, invalid, or lacks the operation's permission.

- `402` — The organization does not have access to the requested product capability.

- `403` — The key's current user no longer has the required organization membership or role.

- `404` — The resource is not available in the authenticated scope.

- `409` — The request conflicts with a retry receipt or current resource state.

- `410` — The event cursor has expired; capture a fresh head and resource snapshot.

- `429` — The API-key rate limit has been exceeded.

- `500` — The API encountered an unexpected failure.

- `501` — The deployment has not configured the requested capability.

- `503` — A required API dependency, including the rate limiter, is unavailable.