---
title: "The monorepo's event stream (JSON pages, or a WebSocket)"
description: "Every change a screen shows is an event, written in the same step as\nthe change itself: a `seq`, a `kind`, and the changed `row` in its REST\nshape, with the client-side `collection`, `key` and `op` (`upsert` or\n`delete`) it applies to, so a client store (TanStack DB) can apply it\ndirectly. Every event is kept; the one limit is the monorepo object's\nstorage (10 GB per Durable Object; an event is 0.3 to 1.3 KB).\n\nKinds, by collection:\n- branches: `branch.opened`, `push`, `branch.updated`, `merged`\n  (upsert), `branch.deleted` (delete). The row has the branch's stored\n  fields only (name, head, author, title, description, state and the\n  merge facts) and the event says `partial: true`: `ahead`, `behind`,\n  `base`, `mergeable` and `checks` are computed per request, so\n  refetch getBranch for them.\n- default_heads: `push` (the first push of main) and\n  `default_head.updated` (a merge). Row `{repo, default_branch,\n  default_head}`. When main moves, every open branch's ahead, behind\n  and mergeable change: refetch the branch list.\n- statuses: `status` (Status row).\n- ci_builds: `ci.build` (CiBuild row, upsert), on the build's repo.\n- ci_jobs: `ci.job` (CiJob row, upsert; key `<build>/<job>`), on the\n  build's repo.\n- ci_connections: `ci.connection` (CiConnection row; delete on\n  disconnect, row `{id}`).\n- tags: `tag.created` (a Tag row with its `repo`; also a moved tag),\n  `tag.deleted` (delete, row `{repo, name}`).\n- releases: `release.created`, `release.updated` (also a file added\n  or deleted), `release.published` (a Release row), `release.deleted`\n  (delete, row `{repo, tag}`).\n- reviews: `review` (Review row).\n- artifacts: `artifact.added`, `artifact.versioned` (an Artifact row),\n  `artifact.deleted` (delete, row `{repo, branch, id, name}`); each\n  carries `branch`.\n- artifact_comments: `artifact_comment.added`, `.edited`,\n  `.resolved`, `.reopened` (an ArtifactComment row; the thread's root\n  for a state change; a body over 16 KB is null), and\n  `artifact_comment.deleted` (delete with `{id, artifact, thread}`, or\n  an upsert with the body null for a root that keeps its replies);\n  each carries `branch`.\n- worktrees: `worktree.reported` (upsert), `worktree.removed` (delete).\n- meta_files: `meta.saved` (a meta history entry, no bytes; not sent\n  during pando init's first upload).\n- presence: `machine.connected`, `machine.disconnected`. Row\n  `{connections: [...]}`, the whole list of who is connected now (as in\n  meta/status), plus `connection`, the one that changed.\n- machines: `machine.updated` (upsert, a Machine row; key\n  `<login>/<credential_id>`). Sent at once for a new machine, a\n  connect, a disconnect or a change of its worktrees; for activity\n  alone at most once a minute per machine (every activity event\n  already names its `actor` and `machine`).\n\n- issues: `issue.opened`, `issue.commented`, `issue.closed`,\n  `issue.reopened`, `issue.updated` (everything else: title, body,\n  project, labels, assignees, links, reactions, a comment edited or\n  deleted, a merge naming a closed issue). Key `<n>`, with `number`,\n  `row` (the IssueRow), `body_version`, `items` (the new timeline\n  items; a comment's body is null over 16 KB), and when they apply\n  `removed` (item ids), `comment` (a comment edited or whose\n  reactions moved) and `reactions` (the issue's own).\n- labels: `label.updated` (upsert, a Label, `from` its name before a\n  rename; delete, row `{name}`).\n\nTopics (`?topics=`, comma-separated, at most 10): `all` (default),\n`issues` (every issue and label event), `issue:<n>` (one issue's;\nalso `repo:<repo>` for its project and its linked branches' repos),\n`main` (every default_heads event), `main:<repo>`, `repo:<repo>`,\n`meta` (meta_files and presence), `kind:<kind>`, `feed` (the\nactivity lines as the person's feed shows them, as\n`{\"type\": \"feed\", \"n\", \"event\": FeedEvent}` frames: the home uses it\nfor a monorepo of more than 100 members, and a monorepo's pages may).\n`pando worktree follow` needs only `main:<repo>`.\n\nThe `feed` topic's lines are numbered `n` in the order they go out\n(a push's line goes out when its commit count is known, so `n` is not\n`seq` order). `caught_up` then carries `feed_n`, the last number\nsent. A socket that drops reconnects with `feed_since=<the last n it\nsaw>` and first gets every line sent after it (at most 500, else one\n`{\"type\": \"feed_resync\", \"feed_n\"}`: refetch, then reconnect with that\n`feed_n`).\n\nPresence (the home's `machines_active` and `worktrees_active`): a\nfollower sends, after the socket opens and whenever a worktree is\nadded or removed on its machine,\n`{\"type\": \"hello\", \"machine\": \"<machine>\", \"worktrees\": [\"<worktree id>\", ...]}`:\nthe ids it reported with putWorktree for its branch worktrees in this\nmonorepo (not read-only mains), at most 1,000, each 1 to 64 of\n`A-Z a-z 0-9 _ -`. `machine` is optional (default: the credential's\nname). Each hello replaces the last one of that socket; presence ends\nwhen the socket closes. The hello also marks the socket's credential\nconnected in listMachines, with these worktrees. Answer: `{\"type\": \"hello_ok\", \"machine\",\n\"worktrees\", \"machines_active\", \"worktrees_active\"}`, or\n`{\"type\": \"error\", \"message\"}` (the socket stays open).\n\nWithout `Upgrade`: JSON, `?since=<seq>&limit=<n>` (at most 500), or the\nlatest `limit` with no `since`, oldest first either way. The pull also\ntakes `before=<seq>` (only events before it: page back through the\nlog with the first event's seq), `repo=<repo>` (that repo's events;\n`meta` for the meta layer's) and `kinds=<kind>,<kind>` (at most 20),\neach answered from an index on (repo, seq) or (kind, seq), and with\n`topics` (matched over at most 5,000 rows). With `Upgrade: websocket`: events after\n`?since=` (at most 500, else one `{\"type\": \"resync\", \"seq\",\n\"oldest_seq\"}` frame: refetch, then resume from `seq`), then\n`{\"type\": \"caught_up\", \"seq\"}`, then live `{\"type\": \"event\", \"seq\", ...}`\nframes. The text `ping` gets `pong`. Close code 4404: the monorepo was\ndeleted. Close code 4403: you no longer have access to it (an owner\nremoved you, you left the org that owns it, or a transfer moved it\naway from that org); do not reconnect.\n\nNumbers and shas: `seq`, `oldest_seq` are stream sequence numbers. In\nan event, `head` and `before` (push and default_head events) are commit\nshas. Set `EVENT_STREAM` false in\nworker/src/monorepo/stream.ts and it answers 501 `event_stream_on_hold`.\n"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.gitpando.com/llms.txt
> Use this file to discover all available pages before exploring further.

Path: Pando API › monorepos

`GET /v1/monorepos/{owner}/{monorepo}/events`

Every change a screen shows is an event, written in the same step as
the change itself: a `seq`, a `kind`, and the changed `row` in its REST
shape, with the client-side `collection`, `key` and `op` (`upsert` or
`delete`) it applies to, so a client store (TanStack DB) can apply it
directly. Every event is kept; the one limit is the monorepo object's
storage (10 GB per Durable Object; an event is 0.3 to 1.3 KB).

Kinds, by collection:
- branches: `branch.opened`, `push`, `branch.updated`, `merged`
  (upsert), `branch.deleted` (delete). The row has the branch's stored
  fields only (name, head, author, title, description, state and the
  merge facts) and the event says `partial: true`: `ahead`, `behind`,
  `base`, `mergeable` and `checks` are computed per request, so
  refetch getBranch for them.
- default_heads: `push` (the first push of main) and
  `default_head.updated` (a merge). Row `{repo, default_branch,
  default_head}`. When main moves, every open branch's ahead, behind
  and mergeable change: refetch the branch list.
- statuses: `status` (Status row).
- ci_builds: `ci.build` (CiBuild row, upsert), on the build's repo.
- ci_jobs: `ci.job` (CiJob row, upsert; key `<build>/<job>`), on the
  build's repo.
- ci_connections: `ci.connection` (CiConnection row; delete on
  disconnect, row `{id}`).
- tags: `tag.created` (a Tag row with its `repo`; also a moved tag),
  `tag.deleted` (delete, row `{repo, name}`).
- releases: `release.created`, `release.updated` (also a file added
  or deleted), `release.published` (a Release row), `release.deleted`
  (delete, row `{repo, tag}`).
- reviews: `review` (Review row).
- artifacts: `artifact.added`, `artifact.versioned` (an Artifact row),
  `artifact.deleted` (delete, row `{repo, branch, id, name}`); each
  carries `branch`.
- artifact_comments: `artifact_comment.added`, `.edited`,
  `.resolved`, `.reopened` (an ArtifactComment row; the thread's root
  for a state change; a body over 16 KB is null), and
  `artifact_comment.deleted` (delete with `{id, artifact, thread}`, or
  an upsert with the body null for a root that keeps its replies);
  each carries `branch`.
- worktrees: `worktree.reported` (upsert), `worktree.removed` (delete).
- meta_files: `meta.saved` (a meta history entry, no bytes; not sent
  during pando init's first upload).
- presence: `machine.connected`, `machine.disconnected`. Row
  `{connections: [...]}`, the whole list of who is connected now (as in
  meta/status), plus `connection`, the one that changed.
- machines: `machine.updated` (upsert, a Machine row; key
  `<login>/<credential_id>`). Sent at once for a new machine, a
  connect, a disconnect or a change of its worktrees; for activity
  alone at most once a minute per machine (every activity event
  already names its `actor` and `machine`).

- issues: `issue.opened`, `issue.commented`, `issue.closed`,
  `issue.reopened`, `issue.updated` (everything else: title, body,
  project, labels, assignees, links, reactions, a comment edited or
  deleted, a merge naming a closed issue). Key `<n>`, with `number`,
  `row` (the IssueRow), `body_version`, `items` (the new timeline
  items; a comment's body is null over 16 KB), and when they apply
  `removed` (item ids), `comment` (a comment edited or whose
  reactions moved) and `reactions` (the issue's own).
- labels: `label.updated` (upsert, a Label, `from` its name before a
  rename; delete, row `{name}`).

Topics (`?topics=`, comma-separated, at most 10): `all` (default),
`issues` (every issue and label event), `issue:<n>` (one issue's;
also `repo:<repo>` for its project and its linked branches' repos),
`main` (every default_heads event), `main:<repo>`, `repo:<repo>`,
`meta` (meta_files and presence), `kind:<kind>`, `feed` (the
activity lines as the person's feed shows them, as
`{"type": "feed", "n", "event": FeedEvent}` frames: the home uses it
for a monorepo of more than 100 members, and a monorepo's pages may).
`pando worktree follow` needs only `main:<repo>`.

The `feed` topic's lines are numbered `n` in the order they go out
(a push's line goes out when its commit count is known, so `n` is not
`seq` order). `caught_up` then carries `feed_n`, the last number
sent. A socket that drops reconnects with `feed_since=<the last n it
saw>` and first gets every line sent after it (at most 500, else one
`{"type": "feed_resync", "feed_n"}`: refetch, then reconnect with that
`feed_n`).

Presence (the home's `machines_active` and `worktrees_active`): a
follower sends, after the socket opens and whenever a worktree is
added or removed on its machine,
`{"type": "hello", "machine": "<machine>", "worktrees": ["<worktree id>", ...]}`:
the ids it reported with putWorktree for its branch worktrees in this
monorepo (not read-only mains), at most 1,000, each 1 to 64 of
`A-Z a-z 0-9 _ -`. `machine` is optional (default: the credential's
name). Each hello replaces the last one of that socket; presence ends
when the socket closes. The hello also marks the socket's credential
connected in listMachines, with these worktrees. Answer: `{"type": "hello_ok", "machine",
"worktrees", "machines_active", "worktrees_active"}`, or
`{"type": "error", "message"}` (the socket stays open).

Without `Upgrade`: JSON, `?since=<seq>&limit=<n>` (at most 500), or the
latest `limit` with no `since`, oldest first either way. The pull also
takes `before=<seq>` (only events before it: page back through the
log with the first event's seq), `repo=<repo>` (that repo's events;
`meta` for the meta layer's) and `kinds=<kind>,<kind>` (at most 20),
each answered from an index on (repo, seq) or (kind, seq), and with
`topics` (matched over at most 5,000 rows). With `Upgrade: websocket`: events after
`?since=` (at most 500, else one `{"type": "resync", "seq",
"oldest_seq"}` frame: refetch, then resume from `seq`), then
`{"type": "caught_up", "seq"}`, then live `{"type": "event", "seq", ...}`
frames. The text `ping` gets `pong`. Close code 4404: the monorepo was
deleted. Close code 4403: you no longer have access to it (an owner
removed you, you left the org that owns it, or a transfer moved it
away from that org); do not reconnect.

Numbers and shas: `seq`, `oldest_seq` are stream sequence numbers. In
an event, `head` and `before` (push and default_head events) are commit
shas. Set `EVENT_STREAM` false in
worker/src/monorepo/stream.ts and it answers 501 `event_stream_on_hold`.

## Authentication

Requires one of the following:

- `credential`, http, header `Authorization`
- `session`, apiKey, in cookie

## Path parameters

- `events.path.owner` (string, required) — The monorepo's owner, a namespace (a person's login or an organization). Matched without case.
  - pattern `^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,38}$`
- `events.path.monorepo` (string, required) — The monorepo's name within its owner (or an old name). Matched without case.
  - pattern `^[a-z0-9](?:[a-z0-9._]|-(?!-)){0,38}$`

## Query parameters

- `events.query.since` (integer, optional)
  - min 0
- `events.query.limit` (integer, optional)
  - min 1; max 500; default `100`
- `events.query.topics` (string, optional)
  - default `"all"`
- `events.query.feed_since` (integer, optional) — With `topics` including `feed`, on a WebSocket: replay the `feed` lines numbered after this.
  - min 0
- `events.query.before` (integer, optional) — JSON only: events before this seq (the latest `limit` of them, oldest first).
  - min 1
- `events.query.repo` (string, optional) — JSON only: one repo's events (its name as in its URL; `meta` for the meta layer).
- `events.query.kinds` (string, optional) — JSON only: these kinds, comma-separated, at most 20.

## Code samples

### cURL

```curl
curl --request GET \
  --url https://app.gitpando.com/v1/monorepos/string/string/events \
  --header 'Authorization: Bearer <token>'
```

### TypeScript

```typescript
const url = 'https://app.gitpando.com/v1/monorepos/string/string/events';
const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));
```

### Python

```python
import requests

url = "https://app.gitpando.com/v1/monorepos/string/string/events"

headers = {"Authorization": "Bearer <token>"}

response = requests.get(url, headers=headers)

print(response.text)
```

## Responses

### 101

The WebSocket.

### 200

A page of events

#### Example

```json
{
  "events": [
    {
      "seq": 0,
      "at": "2019-08-24T14:15:22Z",
      "kind": "string",
      "collection": "string",
      "key": "string",
      "op": "upsert",
      "repo": "string",
      "actor": "string",
      "machine": "string",
      "agent": "string",
      "row": null,
      "partial": true
    }
  ],
  "seq": 0,
  "oldest_seq": 0,
  "resync": true
}
```

- `events.response.200.events` (array<object>, required)
  - `events.response.200.events.seq` (integer, required)
  - `events.response.200.events.at` (string, required)
    - format `date-time`
  - `events.response.200.events.kind` (string, required)
  - `events.response.200.events.collection` (string | null, required)
  - `events.response.200.events.key` (string | null, required)
  - `events.response.200.events.op` (string | null, required)
    - one of `"upsert"`, `"delete"`, `null`
  - `events.response.200.events.row` (unknown, required) — The changed row in its REST shape; on a delete, the last row (or its key fields).
  - `events.response.200.events.repo` (string | null, optional)
  - `events.response.200.events.actor` (string | null, optional) — The user's login.
  - `events.response.200.events.machine` (string | null, optional) — The credential's name.
  - `events.response.200.events.agent` (string | null, optional)
  - `events.response.200.events.partial` (boolean, optional) — True on branch events: the row has the branch's stored fields only.
- `events.response.200.seq` (integer, required) — The newest event's seq (0 when none).
- `events.response.200.oldest_seq` (integer, required) — The oldest kept.
- `events.response.200.resync` (boolean, required) — True when `since` is not in the stream; refetch everything.

### 501

`event_stream_on_hold`

#### Example

```json
{
  "error": {
    "code": "string",
    "message": "string",
    "paths": [
      "string"
    ],
    "checks": {
      "sha": "string",
      "state": "success",
      "required": [
        "string"
      ],
      "checks_unknown": "string",
      "missing": [
        "string"
      ],
      "statuses": [
        {
          "sha": "string",
          "context": "string",
          "state": "pending",
          "target_url": "string",
          "description": "string",
          "creator": "string",
          "app": "string",
          "created_at": "2019-08-24T14:15:22Z"
        }
      ]
    }
  }
}
```

- `events.response.501.error` (object, required)
  - `events.response.501.error.code` (string, required)
  - `events.response.501.error.message` (string, required)
  - `events.response.501.error.paths` (array<string>, optional)
  - `events.response.501.error.checks` (object, optional) — GitHub's combined status, plus the required contexts and which are missing.
    - `events.response.501.error.checks.sha` (string | null, required)
    - `events.response.501.error.checks.state` (string, required) — `success` when the merge gate passes; `missing` when a required context has not reported; `unknown` while the required checks are (an unforced merge is refused).
      - one of `"success"`, `"pending"`, `"failure"`, `"error"`, `"missing"`, `"unknown"`
    - `events.response.501.error.checks.required` (array | null, required) — Null while unknown.
    - `events.response.501.error.checks.checks_unknown` (string | null, required) — Why the required checks are unknown, in plain words: "Pando couldn't read this monorepo's required checks; try again", or "pando.toml line 3: expected comma or end of structure; fix it on main, or merge with force". Null otherwise.
    - `events.response.501.error.checks.statuses` (array<object>, required) — The latest status per context.
      - `events.response.501.error.checks.statuses.sha` (string, required)
      - `events.response.501.error.checks.statuses.context` (string, required)
      - `events.response.501.error.checks.statuses.state` (string, required)
        - one of `"pending"`, `"success"`, `"failure"`, `"error"`
      - `events.response.501.error.checks.statuses.created_at` (string, required)
        - format `date-time`
      - `events.response.501.error.checks.statuses.target_url` (string | null, optional)
      - `events.response.501.error.checks.statuses.description` (string | null, optional)
      - `events.response.501.error.checks.statuses.creator` (string, optional) — The member who posted it (a login); `app:<slug>` for a status posted with a status token or by a CI integration; `hook:<hook id>` for a status token from a web hook with no app.
      - `events.response.501.error.checks.statuses.app` (string | null, optional) — The CI service that reported it (`buildkite`, `github-actions`, ...), stamped by Pando from a status token or a CI integration, never from the body or `target_url`. Null when a member posted it with their own credential. The web shows the service's mark for a slug it knows.
    - `events.response.501.error.checks.missing` (array<string>, optional)


Source: https://docs.gitpando.com/api/monorepos/events/index.md
