# api ## Pages - [Pando API](https://docs.gitpando.com/api/index.md) — Shape: - A monorepo is `owner/name`, like GitHub: the owner is a namespace (a person's login, or an organization's), the name is unique within its owner, and a permanent `id` keys everything stored. A rename or a transfer changes the label only; the old `owner/name` keeps working (an alias) until its owner gives that name to another monorepo, which the create or rename answer reports (`replaced_alias`). Owners and names match without case and are stored lower case. - In payloads, `monorepo` is always the `owner/name` label and `monorepo_id` always the id (repos, hook payloads, feed lines, needs-you rows, socket frames). The Monorepo object itself has `id`, `owner`, `name` and `full_name`. - Every route is under `/v1/monorepos/{owner}/{monorepo}`, except the user's own (`/v1/user`, `/v1/monorepos`) and the web home's, which span all of the person's monorepos (`/v1/events`, `/v1/branches`). A monorepo holds repos. A plain git repo is a monorepo of kind `repo` holding exactly one repo, at path `.`, and no meta layer; it uses the same routes. The meta layer's routes (`/v1/monorepos/{owner}/{monorepo}/meta/...`) are not in this reference. - A repo has a `path` (its folder from the root: `projects/web`, or `.` in a plain repo) and a `name`, its id in URLs: the path with each `/` turned into `-` (`projects-web`). A plain repo's name is the monorepo's name when it was made (a rename does not change it). Names are unique in a monorepo. - In a monorepo, the repo named `meta` (path `.`, clone_url `///meta.git`) is the meta layer's own git history. It takes branches, diffs, reviews and merges like any repo, and it is how `.pando/**` changes: after a merge into it, the merged files go to the live meta layer on every machine. It is not in listRepos. - Git goes through the git door at the repo's `clone_url`: `https://///.git` for a monorepo's repos, `https:////.git` for a plain repo (smart HTTP; see the `git` tag). Pushes to the default branch are refused, except the first push that creates it. Every other push goes through. - Every other call is JSON with `Authorization: Bearer `. Artifacts repos, in the namespace `pando-dev`, are named from the monorepo's id and kept on each repo's record (Artifacts has no rename): a project repo is `--` (`m_0123456789abcdef--projects-web`), a plain repo `--`, and the meta layer `--meta`. New ids are `m_` and 16 hex characters; a monorepo made before namespaces kept its old name as its id. Ids and names have no `--`, so the split is never ambiguous. These names are internal: clients use `clone_url`. Required checks: the server reads `required_checks` from `.pando/pando.toml` on main. In a monorepo that is the meta layer's main, table `[repos.""]`; in a plain repo, the repo's own main, table `[repos."."]`. No main or no file means no required checks. When Artifacts fails to read it, or it does not parse, they are unknown, and only the merge fails closed: reads (the repo, the branch list, a branch, a commit's status, CI) answer as usual with the list null and `checks_unknown` saying why (for a bad file, "pando.toml line N: "), checks `state` is `unknown`; an unforced merge is refused with 503 `checks_unknown` and that reason, a forced one goes through as forced. Nothing is kept, so the next request reads it again. Identity works like GitHub's. A person has a user account and belongs to monorepos as `owner` or `member`. A machine signs in once (`pando login`) and holds a credential for that user; git and every agent on the machine act as that user, the way `gh` and git credentials do. A cloud sandbox gets a credential its user makes for it. Credentials are listed and revocable. Commits are authored as the user; Pando's commit trailers name the agent. Any member may push, review, post statuses, report worktrees and merge (agents included, through the user's credential). Reviews are comments and approvals, not a gate. The merge gate is the checks, and `?force=1` merges past them. Owners also manage members and hooks. Organizations: anyone signed in creates one (`POST /v1/orgs`); its name is a namespace in the same space as logins. Roles are `owner` and `member`. Everyone in an org can open and push to every monorepo the org owns, and its owners are owners of all of them: a person's role in an org's monorepo is the higher of their own row in the monorepo and their org role. A monorepo can still add people from outside the org itself. People are added at once (by login, or by an email that has signed in), or at that email's first sign-in. Removal from an org takes effect within 15 s, as for a monorepo. Signing in (`pando login`): WorkOS AuthKit's CLI Auth, the OAuth device flow, run through the Worker. `POST /v1/login/device` returns a code and a URL; the person confirms the code in the browser; the CLI polls `POST /v1/login/token` with the device code until it returns the user and a new machine credential. WorkOS only proves who the person is: Pando keeps its own users, credentials and membership, and keeps no WorkOS token. A user's login comes from their email (`ana@x.com` is `ana`, or `ana-2` if taken). Invites by email wait until that email first signs in. In a browser: `GET /v1/login/browser?return_to=` sends the person to AuthKit's page; WorkOS sends them back to `/v1/login/callback`, which sets the session cookie `__Host-pando_session` (HttpOnly, Secure, SameSite=Lax, host-only) and returns them to `return_to` (a path on this host, else `/`). The session is a credential of kind `browser` for the same Pando user, so it sees the same monorepos. It works on `/v1` only: never as a Bearer token and never at the git door. With the cookie, a write (any method but GET and HEAD) or a WebSocket upgrade must carry `Origin` equal to Pando's own origin, else 403 `bad_origin`. A session ends after 30 days without use, on sign-out (`POST /v1/logout`, which also ends the WorkOS session), or when revoked. Self-hosted, one person: the Worker secret `PANDO_BOOTSTRAP` makes users with `POST /v1/users` (it works there only). Either way, a user then makes more credentials (`POST /v1/user/credentials`) and monorepos (`POST /v1/monorepos`). Agents: an agent acts as its user, through the user's credential. It may also send `X-Pando-Agent: ` on API calls and pushes; Pando then records the agent on merges (`merged_agent`) and on events. Branch names in paths: encode `/` as `%2F` (`agent-3%2Foauth`). Raw slashes are accepted too. Errors are JSON: `{"error": {"code": "...", "message": "..."}}`. - [Address with agent](https://docs.gitpando.com/api/agents/addressIssueWithAgent/index.md) — Makes the branch `-` (or `branch` when given) off main, or uses it as it is when it exists, titled as the issue and linked to it, so merging it closes the issue; then launches a session acting as the caller, told the issue's title, body and comments and `prompt` when given. 409 `issue_closed` for a closed issue. - [The live view's socket (Upgrade, websocket)](https://docs.gitpando.com/api/agents/agentLive/index.md) — Frames are `{type: "messages", from, messages}`: the transcript from message `from` on. The first starts at 0; later ones append. The session's state comes as agent.updated on the event socket. - [The models a session may use here](https://docs.gitpando.com/api/agents/agentModels/index.md) - [Its transcript, oldest first](https://docs.gitpando.com/api/agents/agentTranscript/index.md) - [One agent session](https://docs.gitpando.com/api/agents/getAgent/index.md) - [Launch an agent session acting as the caller](https://docs.gitpando.com/api/agents/launchAgent/index.md) — Mints a sandbox credential for the caller named after the session (`cloud-`), records the session, and starts it with the prompt on the branch named: made off main when it is new, used as it is when it exists. Pando never invents a name: without `branch`, 400 `branch_required`. `repo` may be left out in a plain repository. A session that cannot start is answered `failed` with its reason, and its credential is revoked. - [The repository's agent sessions, newest first](https://docs.gitpando.com/api/agents/listAgents/index.md) - [The caller's own chats with the repository assistant, newest first](https://docs.gitpando.com/api/agents/listChats/index.md) - [A new chat with the repository assistant](https://docs.gitpando.com/api/agents/newChat/index.md) — One session (kind chat) per conversation, per person. It answers from Pando's read tools with the caller's access and no sandbox; asked for a change, it starts its own branch `assistant/` and works as a task does. A chat is its person's alone: not in GET .../agents, and 404 to anyone else. Follow-ups, the transcript, the socket and Stop are the agents routes'. - [Review with agent](https://docs.gitpando.com/api/agents/reviewWithAgent/index.md) — Launches a session (kind review) acting as the caller on an existing branch: it reads the branch, may run its tests in its sandbox, and posts one review (comment or request_changes, never approve). It has no tool that writes a file, merges or comments on an issue. - [A follow-up, from the person who launched it](https://docs.gitpando.com/api/agents/sendAgent/index.md) — Taken after the current run when one is going. 403 `not_launcher` for anyone else. - [Abort the run and stop the sandbox (its launcher or an owner)](https://docs.gitpando.com/api/agents/stopAgent/index.md) — The transcript and the branch stay; a later message starts it again. - [Upload an artifact, or the next version of one with the same name](https://docs.gitpando.com/api/artifacts/addArtifact/index.md) — Multipart, with Content-Length (411 without it): one `file` field (a .md, .markdown, .html, .htm, .png, .jpg, .jpeg, .gif, .webp or .svg file; its name is the artifact's) and an `image` field for each image a document uses, the file's name its path from the document (`img/hero.png`). Refused: 413 too_large (over 10 MiB in all, or a document over 1.5 MiB), 400 too_many_files (over 20), 409 too_many_artifacts (100 on the branch), 400 bad_request (a name, path or kind Pando does not take). Each file's SHA-256 is computed and checked by R2. - [A thread, or a reply](https://docs.gitpando.com/api/artifacts/addArtifactComment/index.md) — `{body, version?, anchor?, quote?, in_reply_to?}`. A thread takes `anchor` (Plannotator's fields, as written) or `quote` (plain words, stored as Plannotator's external annotation: blockId `external`, offsets 0); neither: the whole artifact. `version` defaults to the newest. A reply names `in_reply_to` and takes neither; it joins the thread's root. Members named with `@login` are recorded. Send an Idempotency-Key to make a retry safe (200 with `pando-replayed`). Refused: 409 too_many_threads, too_many_replies; 400 too_many_mentions; 413 too_large (body or anchor). - [Delete it (any member), every version, its bytes and comments](https://docs.gitpando.com/api/artifacts/deleteArtifact/index.md) - [Delete your own](https://docs.gitpando.com/api/artifacts/deleteArtifactComment/index.md) — A root with replies keeps its place with its body null; it goes with its last reply. - [One artifact with every version](https://docs.gitpando.com/api/artifacts/getArtifact/index.md) - [One comment, whole](https://docs.gitpando.com/api/artifacts/getArtifactComment/index.md) - [The version's document, its images written in as data addresses](https://docs.gitpando.com/api/artifacts/getArtifactDocument/index.md) — The Markdown or HTML with each image it names from its own files (`img/a.png` or `./img/a.png`) replaced by a `data:` address, for the viewer's frame, which cannot fetch from Pando. Same sandbox policy as a file. An image artifact answers its image. - [One file's bytes](https://docs.gitpando.com/api/artifacts/getArtifactFile/index.md) — Its type from its extension, with `Content-Security-Policy: sandbox; default-src 'none'` so opening it never runs it on Pando's address, and X-Pando-Sha256. - [Its threads on every version](https://docs.gitpando.com/api/artifacts/listArtifactThreads/index.md) - [The branch's artifacts, newest first](https://docs.gitpando.com/api/artifacts/listArtifacts/index.md) - [Every artifact's threads on the branch (the panel's All files)](https://docs.gitpando.com/api/artifacts/listBranchArtifactThreads/index.md) - [Edit your own, or resolve or reopen its thread](https://docs.gitpando.com/api/artifacts/updateArtifactComment/index.md) — `{body, base_version?}`: your own (403 not_author; 409 edited with `current` when base_version is stale). `{state: open | resolved}`: any member; answers the thread's root. - [The branch's own commits, newest first, with Pando's trailers parsed](https://docs.gitpando.com/api/branches/branchCommits/index.md) — The commits on the branch that are not on the default branch (the ones `ahead` counts), in git log's order: each commit before its parents. `trailers` holds the who-made-it lines (`Pando-Agent`, `Pando-Worktree`, `Pando-Machine`), read from the message's last paragraph. - [The branch diff (GitHub's three-dot diff, merge base to head)](https://docs.gitpando.com/api/branches/branchDiff/index.md) — Computed in the Worker from Artifacts reads, with no clone: the merge base, a walk of the two trees that skips equal subtrees, the changed blobs only, and a line diff (patience, then bounded Myers between its anchors, as `git diff --patience`). Exact renames (the same blob moved) show as `renamed`; there is no similarity rename detection. `max_bytes` bounds the bytes of hunk lines returned. Files past the budget keep their entry with `patch_omitted: budget`. A text file over 8 MiB on either side has `patch_omitted: too_large` and null counts. **Files changed** (`patches=1`): the answer is a `BranchDiff`, each file with its own unified patch in git's format (`patch`, ready for a diff view such as @pierre/diffs' `processFile`), or null when left out: a binary (a NUL in the first 8,000 bytes), a text file over 8 MiB on either side, or past 2 MiB of patches in all. At most 1,000 files are listed (`too_many_files`, with `files_total`). Main and the head come from Pando's stored heads; the answer is kept by (main, head), which never changes, so a repeat costs no Artifacts read, and `ETag` is the two shas (a repeat with `If-None-Match` is a 304). `format`, `hunks`, `max_bytes` and `paths` do not apply. **Files changed, streamed** (`patches=1&stream=1`, the web's branch page, for a branch of any size): `application/x-ndjson`, one `BranchDiffLine` a line, each sent as soon as it is known: `start` (the ends, the merge base, `files_total`, `too_many_files`), then one `file` per changed file (a `DiffFile` with its `patch`) in a file tree's order (folders before files at each level, by name), then `end` (`stats`, `truncated`); `error` if the diff fails after its start. At most 5,000 files, and 32 MiB of patches in all. Each pair of blobs diffed is kept in R2, so the next head of a branch reads only the pairs a push changed; the whole answer is kept by (main, head) once streamed, and streamed from there after with the same `ETag` and 304. An answer being computed has no `ETag` (`Cache-Control: no-store`): it may stop part way, with an `error` line, and the next request goes on from the pairs already kept. - [Create a branch on the server, off main by default](https://docs.gitpando.com/api/branches/createBranch/index.md) — Points a new branch at `from` (default: main's head) with no new commit. The caller becomes the branch's author. 409 `branch_exists` if it exists. The answer has `stale: true` until the branch's facts are computed (a `branch.updated` event follows). - [Delete a branch (not the default one)](https://docs.gitpando.com/api/branches/deleteBranch/index.md) - [One branch, as in the list](https://docs.gitpando.com/api/branches/getBranch/index.md) — Answers from stored facts at once, like the list. It computes them first only when they have been stale for over 30 s, or with `?fresh=1`. - [List branches with head, base, author, ahead and behind, mergeable and checks](https://docs.gitpando.com/api/branches/listBranches/index.md) — Answered at once from stored facts. `ahead`, `behind`, `base` and `mergeable` are computed on change, by a queue: when a branch is pushed, and in a sweep when main moves (the 100 open branches pushed most recently in the last 7 days). Until a row's facts are for the current main and head it is `stale: true`, with `mergeable: "pending"` and `last_mergeable`; a `branch.updated` event brings the fresh row. A list computes at most 25 rows itself, those stale for over 30 s (a branch no sweep reaches). The default branch is not in the list; its name and head are at the top. Newest first, as GitHub lists active branches: an open branch by its last push, a merged one by its merge time; branches with the same time by name. Pages stay stable when two share a time: `next_cursor` holds the last row's time and name. - ["Needs you": open branches that are ready, conflicting or failing, and issues assigned to the person, across their monorepos](https://docs.gitpando.com/api/branches/listNeeds/index.md) — `issues`: per monorepo, the open issues assigned to the person, read from each monorepo's summary line (up to 10 s behind; the person's socket's `summary` frame moves it, its `assigned`; a large monorepo's is read from it, current), most recently updated first, with the newest one. Branches: from stored branch facts and statuses (no Artifacts call), asked only of the monorepos whose counts in their summary line are not zero (up to 10 s behind), most recently active first, at most 50 monorepos (the rest in `skipped`). One state per branch, in this order: - `conflicts`: the stored dry run for the branch's current head conflicts with main; - `failing`: a check on the head failed or errored; - `ready`: facts for the current main and head say clean, and the merge gate's checks pass. Facts are computed for branches pushed in the last 7 days; an older branch that is not re-pushed drops out of `ready` when main moves. Rows are newest push first. - [Set the branch's title and description](https://docs.gitpando.com/api/branches/updateBranch/index.md) — The merge commit's message is the title and description. - [Buildkite's webhooks (the URL a connection shows)](https://docs.gitpando.com/api/ci/buildkiteWebhook/index.md) — Buildkite's own request: `X-Buildkite-Event`, and either `X-Buildkite-Signature: timestamp=,signature=` (HMAC-SHA256 of `.` keyed by the connection's webhook token; refused when over 5 minutes off) or `X-Buildkite-Token`. Events used: `build.*` and `job.*` (and `ping`). Bodies up to 5 MiB. The build, its job and its check are written with their events before the answer. Events are applied in the order Buildkite signed them (in whole seconds; with a plain token, the order they arrive); a job's state never moves back, and a job event's copy of a build never moves a finished build back. - [Cancel a running build (passed through)](https://docs.gitpando.com/api/ci/cancelCiBuild/index.md) — 409 `ci_finished` when it has finished. Needs `write_builds`. - [Connect a CI service (owners)](https://docs.gitpando.com/api/ci/createCiConnection/index.md) — Answers the webhook URL to paste into the service, and its token (`webhook_token`), shown this once. For Buildkite: Organization Settings, Notification Services, Add, Webhook; the URL; the token, sent as `X-Buildkite-Token` or (better) signed as `X-Buildkite-Signature`; events `build.*` and `job.*`; the pipelines to send. One connection per organization per monorepo. REST calls use the Worker secret `BUILDKITE_API_TOKEN` at launch (the connection says `token: worker_secret`); per-connection tokens are not built yet. - [Disconnect a CI service (owners)](https://docs.gitpando.com/api/ci/deleteCiConnection/index.md) — Its pipelines, builds, jobs and cached details go; its checks stay (they are commit statuses). Its webhook URL then answers 404. - [Download an artifact of this build](https://docs.gitpando.com/api/ci/downloadCiArtifact/index.md) — A 302 to the service's own download URL, which Buildkite keeps good for about 60 seconds. Only an artifact of this build. - [The CI tab's top - services, connections, pipelines, required checks](https://docs.gitpando.com/api/ci/getCi/index.md) - [A build and its jobs](https://docs.gitpando.com/api/ci/getCiBuild/index.md) - [A job's log, from the service (its last 1 MiB)](https://docs.gitpando.com/api/ci/getCiJobLog/index.md) — Raw, with the service's escape codes (ANSI colours, Buildkite's timestamps). `Cache-Control` is immutable once `complete`. - [A pipeline - its configuration (pipeline.yml) and its builds](https://docs.gitpando.com/api/ci/getCiPipeline/index.md) — The configuration is the service's (Buildkite's pipeline YAML, as set in Buildkite), read at most every 5 minutes. A pipeline that uploads its steps from the repo shows the upload step here; the repo's own file is on the Code tab. When the read fails, the last copy is shown with `configuration_error`. - [A build's annotations, from the service (first 100)](https://docs.gitpando.com/api/ci/listCiAnnotations/index.md) - [A build's artifacts, from the service (first 100)](https://docs.gitpando.com/api/ci/listCiArtifacts/index.md) - [Stored builds, newest first](https://docs.gitpando.com/api/ci/listCiBuilds/index.md) - [Build the same commit again, with fresh tokens](https://docs.gitpando.com/api/ci/rebuildCiBuild/index.md) — A new build in the same pipeline, for the same commit and branch, with the env a hook delivery gives (a fresh `PANDO_READ_TOKEN` and `PANDO_STATUS_TOKEN`) and `meta_data.pando_rebuild_of`. Recorded at once. Needs `write_builds` on the token. - [Retry a job (passed through)](https://docs.gitpando.com/api/ci/retryCiJob/index.md) — The service reruns the job with the build's original env, so it can clone for as long as the build's read token lives: at least 24 hours after Pando started the build. The new job is recorded at once and its check goes back to pending. Needs `write_builds`. - [New build - the pipeline builds a branch's head as Pando has it](https://docs.gitpando.com/api/ci/startCiBuild/index.md) — Any member. The branch's head comes from Pando's stored heads (no ref read); its commit's title (one Artifacts read) is the build's message. Started as a push delivery starts one: a read token for the repo and a status token for the commit, both fresh. The build is recorded at once; its webhooks update it. `?connection=` picks the pipeline when two connections share its slug. - [Register a hook (owners)](https://docs.gitpando.com/api/hooks/createHook/index.md) — `web`: a signed JSON POST to `url` (see `webhooks` below). `buildkite`: Pando calls Buildkite's create-build API, `POST https://api.buildkite.com/v2/organizations/{organization}/pipelines/{pipeline}/builds`, with the Worker secret `BUILDKITE_API_TOKEN`, for `push` events (by default) on branches that are not deleted. The build gets `commit`, `branch`, `message`, and `env`: `PANDO_CLONE_URL`, `PANDO_READ_TOKEN` (read only, this repo, at least 24 hours: Buildkite's Retry reruns a job with this env), `PANDO_STATUS_URL`, `PANDO_STATUS_TOKEN` (posts checks on this commit for 24 hours, stamped `buildkite`; see createStatus; `pando status ` uses it), `PANDO_MONOREPO`, `PANDO_REPO`, `PANDO_BRANCH`, `PANDO_HEAD`, `PANDO_DELIVERY`, `PANDO_SEQ`. Its `meta_data` has `pando_delivery`, `pando_monorepo` (the id) and `pando_repo`, which Pando's Buildkite webhook uses to map the build back (the `ci` tag). - [Remove a hook (owners)](https://docs.gitpando.com/api/hooks/deleteHook/index.md) - [The hook's deliveries, newest first (last 100)](https://docs.gitpando.com/api/hooks/listDeliveries/index.md) - [List hooks](https://docs.gitpando.com/api/hooks/listHooks/index.md) - [Send a delivery again (owners)](https://docs.gitpando.com/api/hooks/redeliver/index.md) — The same delivery (same id and seq) is sent again now, with a fresh read token and a fresh round of five tries; `attempts` keeps counting. - [Replay a hook from a seq, after an outage (owners)](https://docs.gitpando.com/api/hooks/replayHook/index.md) — Every delivery of this hook with a seq above `since` is sent again, oldest first, at most 1,000, each with a fresh round of five tries. Only changes made while the hook existed have deliveries. - [React to a comment](https://docs.gitpando.com/api/issues/addCommentReaction/index.md) - [Comment on an issue](https://docs.gitpando.com/api/issues/addIssueComment/index.md) - [React to an issue (once per person and reaction)](https://docs.gitpando.com/api/issues/addIssueReaction/index.md) - [Open an issue](https://docs.gitpando.com/api/issues/createIssue/index.md) — `link`: branches to link at once (`pando worktree add --issue`). Send an `Idempotency-Key`: a retry with the same key answers the first issue (200, `Pando-Replayed: true`). - [Make a label (owners)](https://docs.gitpando.com/api/issues/createLabel/index.md) - [Delete a comment (yours, or anyone's as an owner); it leaves the timeline](https://docs.gitpando.com/api/issues/deleteIssueComment/index.md) - [Delete a label (owners); it leaves every issue, with no timeline items](https://docs.gitpando.com/api/issues/deleteLabel/index.md) - [One issue, with its body, links, participants and reactions](https://docs.gitpando.com/api/issues/getIssue/index.md) - [One comment, whole (a live event leaves a body over 16 KB out)](https://docs.gitpando.com/api/issues/getIssueComment/index.md) - [Comments and events together, oldest first](https://docs.gitpando.com/api/issues/issueTimeline/index.md) — `cursor` continues after a page; `last=N` (instead) is the newest N, oldest first. `total` counts every item. - [A page of issues, and the open and closed counts for the same filters](https://docs.gitpando.com/api/issues/listIssues/index.md) — Keyset paging: pass `next_cursor` back as `cursor`. `q` matches words in titles and bodies (SQLite FTS5): each word must appear, the last as a prefix, up to 10 words; comments are not searched. Results keep the chosen sort. - [The monorepo's labels, by name, each with its open issues](https://docs.gitpando.com/api/issues/listLabels/index.md) — A new monorepo starts with bug (orange), enhancement (blue), documentation (teal) and question (purple). At most 500. - [Take your reaction back](https://docs.gitpando.com/api/issues/removeCommentReaction/index.md) - [Take your reaction back](https://docs.gitpando.com/api/issues/removeIssueReaction/index.md) - [Edit, label, assign, link, close or reopen an issue](https://docs.gitpando.com/api/issues/updateIssue/index.md) — Any of the fields, in one call; each change is its own timeline item and the call is one event. Add and remove lists are set operations. `state: closed` takes `state_reason` (default `completed`); setting the state it already has changes nothing. `body` with `base_version` is refused with 409 `edited` (and `current`) when the body moved; without it the last writer wins. - [Edit your comment](https://docs.gitpando.com/api/issues/updateIssueComment/index.md) - [Rename, recolour or describe a label (owners); a rename moves it on every issue in one step](https://docs.gitpando.com/api/issues/updateLabel/index.md) - [Land the branch on the default branch with a merge commit (any member)](https://docs.gitpando.com/api/merges/mergeBranch/index.md) — Any member may merge, agents included (like `gh pr merge`). Reviews are not a gate. Merges in one repo run one at a time. The merge commit is authored as the user. The gate: every required check (the repo's `required_checks`; with none set, every reported context) is `success` on the branch head. `force=1` merges past red or missing checks, and the merge is recorded as forced. - [Create a monorepo; the caller becomes its owner](https://docs.gitpando.com/api/monorepos/createMonorepo/index.md) - [Delete a monorepo, its repos and their history (owners)](https://docs.gitpando.com/api/monorepos/deleteMonorepo/index.md) — Deletes every Artifacts repo it owns (`--*`, the meta layer's too), the meta layer's large files in R2 (`meta//`), its records, invites, memberships and old names. No soft delete. Needs `?confirm=/`, typed out in full. - [The monorepo's event stream (JSON pages, or a WebSocket)](https://docs.gitpando.com/api/monorepos/events/index.md) — 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 `/`), 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 `/`). 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 ``, 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:` (one issue's; also `repo:` for its project and its linked branches' repos), `main` (every default_heads event), `main:`, `repo:`, `meta` (meta_files and presence), `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:`. 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=` 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": "", "worktrees": ["", ...]}`: 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=&limit=` (at most 500), or the latest `limit` with no `since`, oldest first either way. The pull also takes `before=` (only events before it: page back through the log with the first event's seq), `repo=` (that repo's events; `meta` for the meta layer's) and `kinds=,` (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`. - [Get a monorepo (members); an old name answers with the current one](https://docs.gitpando.com/api/monorepos/getMonorepo/index.md) - [Invite a person by email (owners)](https://docs.gitpando.com/api/monorepos/invite/index.md) — If someone has already signed in with that email, they are added now (200, `status: added`). Otherwise the invite waits (201, `status: invited`) and is accepted when that email first signs in with `pando login` (a verified email only). - [The activity log across the person's monorepos (JSON pages, or the person's WebSocket)](https://docs.gitpando.com/api/monorepos/listActivity/index.md) — The web home's log: what happened in every monorepo the person belongs to, newest first, each line with its `monorepo`. People, machines and worktrees only: no agent field. Lines are the activity kinds: `push`, `branch.opened`, `branch.deleted`, `merged`, `review`, `status` (only when a check's state changes), `worktree.reported` (a new worktree only), `worktree.removed`, `meta.saved` (not files a merge brought in). The cache-sync kinds stay on the per-monorepo stream. Order and cursor: every line has the display key `(at, monorepo, seq)`. `next` is an opaque cursor, "older than this line"; pass it back as `cursor`. It is stable while lines arrive: new lines are newer, and a late or repeated delivery never moves a line already paged past. Where lines come from: each person has a feed (a Durable Object). A monorepo of 100 members or fewer copies its lines into its members' feeds; a larger one is read from its own log on every page. A feed keeps 7 days (at most 1,000,000 lines); older pages, and a monorepo the feed joined with a partial history, are read from the monorepos' own logs, which keep everything. At most 20 logs are read for one page, most recently active first; the rest are named in `skipped` (narrow with `in` to read one of them). With `Upgrade: websocket`: one socket per person, for every small monorepo. Send `{"type": "hello", "since": }` (since optional): the feed replays the lines that landed after it (at most 500, else one `resync` frame: refetch the first page), then `{"type": "caught_up", "landed"}`. `landed` is the feed's own arrival number, not a time: keep the highest seen and send it on reconnect. Live frames: - `{"type": "event", "landed", "event": FeedEvent}`: place it by its display key. The same line can arrive again with a fuller `summary` (a push's commit count); replace by `(monorepo_id, seq)`. - `{"type": "summary", "monorepo", "monorepo_id", "summary": FeedSummary}`. - `{"type": "needs", "monorepo", "monorepo_id", "changed": [{repo, branch, state}], "more"}`: branches that entered or left "needs you" (`state` null: left); `more`: over 50 changed, refetch listNeeds. A large monorepo sends `summary` and `needs` only when it crosses 100 members (either way); its line is read again with the list. - `{"type": "joined", "monorepo", "monorepo_id", "large"}`, `{"type": "left", "monorepo", "monorepo_id"}`: refetch the first page and the list. - `{"type": "contributed", "monorepo", "monorepo_id", "hour", "kind", "n"}`: your own work was counted (`kind` commit, branch or review; `hour` the UTC hour, `2026-10-03T17`), for work of the last two days; refetch getContributions. Large monorepos send it here too. - `{"type": "error", "message"}`: the frame sent was not understood (anything but `hello` or the text `ping`). Nothing changed; the socket stays open. Every frame's `monorepo` is the `owner/name` label (as the index last heard it; a rename shows within a minute) and `monorepo_id` the id. The text `ping` gets `pong`. Lines of large monorepos do not come here: open their own event socket with `topics=feed` (the home opens at most 5; it refreshes the others every 60 s). Cookie or Bearer. With the cookie, the upgrade must carry Pando's own `Origin` (403 `bad_origin`). - [Invites still waiting](https://docs.gitpando.com/api/monorepos/listInvites/index.md) - [The monorepo's members](https://docs.gitpando.com/api/monorepos/listMembers/index.md) — Everyone who can open it, by login. In an org's monorepo that is its own members and the org's people; `via` says which, and `role` is the higher of the two. - [The monorepos this user belongs to (their own, by membership, and by organization)](https://docs.gitpando.com/api/monorepos/listMonorepos/index.md) — By owner (the person's own first), then name. With `?owner=`: only that owner's (an org's page). With `?name=`: only those named so, under any owner (`pando clone `: the cli prefers your own namespace, else the only match, else it asks). With `?summary=1` (the web home): most recently active first, each with its one line (`MonorepoSummary`), answered from the person's feed in one call, plus one call per monorepo of more than 100 members. A small monorepo's line is up to 10 s behind: it sends it to its members' feeds only when it changed, at most that often. A large one sends none (2,000 members would be 2,000 feed writes per change); its line is read from it here, current. A monorepo the feed has not joined yet (seconds after it was made or joined) has nulls. - [Add a user to the monorepo, or change their role (owners)](https://docs.gitpando.com/api/monorepos/putMember/index.md) — Their own row; in an org's monorepo it adds to what the org gives them. 409 `last_owner` when it would leave a person's monorepo with no owner. - [Remove a user's own row from the monorepo (owners; a person's monorepo keeps one owner)](https://docs.gitpando.com/api/monorepos/removeMember/index.md) — Removes their own row only (404 when they have none); someone in the owning org keeps the org's access. An org's monorepo needs no owner row of its own (its org's owners own it). - [Rename or transfer a monorepo (owners)](https://docs.gitpando.com/api/monorepos/renameMonorepo/index.md) — The label moves; the id, repos, history, members, Artifacts repos and feeds stay. The old `owner/name` keeps working for the API and the git door (an alias) until that owner gives the name to another monorepo. A transfer (`owner`) goes to an organization the caller is in (else 403 `not_your_namespace`), or to a person who already has their own owner row in it (else 400 `bad_owner`). Its own members stay; moved into an org, the org's people gain it, and moved out, they lose it. 409 `monorepo_exists` when the new name is taken. `replaced_alias` reports an old name of another monorepo that this one took over. - [Withdraw an invite (owners)](https://docs.gitpando.com/api/monorepos/revokeInvite/index.md) - [Create an organization; the caller is its first owner](https://docs.gitpando.com/api/orgs/createOrg/index.md) — The login follows the namespace rule (`Login`): one space with people's logins, so a taken name (a person's or an org's) is 409 `namespace_taken` and a reserved one 400 `reserved_name`. The namespace, the org and the caller's owner row are written in one batch, so of two creates of one name at once, one wins. - [An organization's header (anyone signed in)](https://docs.gitpando.com/api/orgs/getOrg/index.md) — `role` is the caller's (null when not in it); `members` is the count of its people, shown to its people only (null otherwise). - [The organizations this user is in, with their role, by login](https://docs.gitpando.com/api/orgs/listMyOrgs/index.md) - [Invites still waiting (its people)](https://docs.gitpando.com/api/orgs/listOrgInvites/index.md) - [An organization's people, by login (its people only)](https://docs.gitpando.com/api/orgs/listOrgMembers/index.md) - [Add a person to the organization by email (owners)](https://docs.gitpando.com/api/orgs/orgInvite/index.md) — As a monorepo's invite: someone who has already signed in with that email is added now (200, `status: added`); otherwise the invite waits (201, `status: invited`) and becomes a membership at that email's first sign-in (a verified email only). - [Add a person to the organization by login, or change their role (owners)](https://docs.gitpando.com/api/orgs/putOrgMember/index.md) — They are in at once, and gain every monorepo the org owns. 409 `last_owner` when it would leave the org with no owner. - [Remove a person from the organization (owners), or leave it (yourself)](https://docs.gitpando.com/api/orgs/removeOrgMember/index.md) — Their access to the org's monorepos ends within 15 s (plus replica lag); rows of their own in a monorepo stay. 409 `last_owner` for the org's only owner. - [Withdraw an invite (owners)](https://docs.gitpando.com/api/orgs/revokeOrgInvite/index.md) - [Change an organization's details (owners)](https://docs.gitpando.com/api/orgs/updateOrg/index.md) — Fields left out are kept; null clears one. - [Record a deployment (the CI or a deploy script deploys; Pando records)](https://docs.gitpando.com/api/releases/createDeployment/index.md) — `release`: a published release's tag; left out, the newest published release at `sha` is named, if any. Pando never deploys. - [Make a release (published at once unless draft)](https://docs.gitpando.com/api/releases/createRelease/index.md) — Its commit: the tag's when the tag exists, else `target` (a commit on main, or main), else main's head. A commit not on main is refused (422 not_on_main). Unless `draft`, it is published at once by the publish rule, and refused (412 checks_not_green) before anything is made. `generate_notes` appends notes written from the merges since the last published release. The tag `latest` is refused. - [Delete a release and its files; its tag stays, and git can move or delete it again](https://docs.gitpando.com/api/releases/deleteRelease/index.md) - [Delete a file](https://docs.gitpando.com/api/releases/deleteReleaseFile/index.md) - [Download a file](https://docs.gitpando.com/api/releases/downloadReleaseFile/index.md) — Members, or anyone (no credential needed) when the project's switch `releases_public` is on. `latest` names the newest published release. Headers: Content-Disposition (attachment), X-Pando-Sha256. - [One release, with its commit's checks](https://docs.gitpando.com/api/releases/getRelease/index.md) - [A release's notes, or (generate=1) notes written from the merges since the last published release](https://docs.gitpando.com/api/releases/getReleaseNotes/index.md) — With `generate`, for a release, a tag, or a release still to be made (`target`, else main). - [The project's main (its commit, checks or not built, the newest build on main) and its latest release](https://docs.gitpando.com/api/releases/getRepoSummary/index.md) - [Deployments, newest first](https://docs.gitpando.com/api/releases/listDeployments/index.md) - [Releases, newest first (a draft by when it was made, a published one by when it was published), 30 a page](https://docs.gitpando.com/api/releases/listReleases/index.md) - [The project's tags, newest first](https://docs.gitpando.com/api/releases/listTags/index.md) - [Publish a draft](https://docs.gitpando.com/api/releases/publishRelease/index.md) — Refused (412 checks_not_green, with `checks`) when the commit's required checks are red or missing, unless `force`; the same rule as merge, and anyone who may merge may publish. The tag is made at the release's commit when it does not exist (409 tag_exists when it names another commit), and from then on git can't move or delete it. - [Edit its title or notes](https://docs.gitpando.com/api/releases/updateRelease/index.md) - [Upload a file (a name sent again replaces it)](https://docs.gitpando.com/api/releases/uploadReleaseFile/index.md) — The body is the file's bytes, with Content-Length (411 without it): at most 100,000,000 bytes (413), 20 files a release (409 too_many_files). Pando computes its SHA-256; send X-Pando-Sha256 to have a mismatch refused (422 sha256_mismatch). - [Create a repo in a monorepo](https://docs.gitpando.com/api/repos/createRepo/index.md) — Makes the Artifacts repo and records it. It starts empty; the first push to `main` through the door (`pando init`) creates main. Refused in a monorepo of kind `repo` (it already has its one repo). - [One file's bytes at a ref](https://docs.gitpando.com/api/repos/getFile/index.md) - [Commits that changed one path, newest first](https://docs.gitpando.com/api/repos/getHistory/index.md) — `git log --first-parent -- `, paged. A page may stop short of `limit` when its walk spent its budget (`complete_page: false`); ask again with `next`. Renames are not followed. The walk stops at 10,000 commits from the ref (`capped`). - [The last commit that changed each entry of a folder](https://docs.gitpando.com/api/repos/getLastCommits/index.md) — First parents (`git log -1 --first-parent -- `): on main, a change merged from a branch is the merge, with `merged_branch`. A complete answer is stored (it never changes) and reused by later commits' answers. A walk stops after about 40 Artifacts reads: the answer then has `complete: false`, the entries found so far (null for the rest), and `retry_after_ms`; a job finishes it. Entries unchanged within 10,000 commits stay null with `older.before`. - [Get a repo](https://docs.gitpando.com/api/repos/getRepo/index.md) - [A folder at any ref](https://docs.gitpando.com/api/repos/getTree/index.md) — Folders first, then by name; 1,000 entries a page. Read by hash through the edge cache: a repeat view reaches no Artifacts call. A branch name resolves from Pando's stored heads; a sha that is not in this repo is a 404. No sizes (git trees have none). - [Every code repo's main head (for followers; no Artifacts read)](https://docs.gitpando.com/api/repos/heads/index.md) — Main as Pando last moved it: by the first push through the door, or by a merge. Answered from the monorepo object's records. A repo made before Pando kept heads is read once from Artifacts and kept. `head` is null for an empty repo. The meta layer's repo is not listed. A follower (`pando worktree follow`) calls this on connect, then opens `GET /v1/monorepos/{owner}/{monorepo}/events?topics=main&since=` (WebSocket) to hear each move, and calls it again every few minutes as a safety net. - [List repos](https://docs.gitpando.com/api/repos/listRepos/index.md) - [A project's settings (owners)](https://docs.gitpando.com/api/repos/updateRepo/index.md) — `releases_public`: the switch "Anyone can download releases" (release files only, never code). - [Review a branch (a comment or an approval; not a gate)](https://docs.gitpando.com/api/reviews/createReview/index.md) - [The branch's reviews, oldest first](https://docs.gitpando.com/api/reviews/listReviews/index.md) - [AgentSession](https://docs.gitpando.com/api/schemas/AgentSession/index.md) — packages/contract/src/agent.ts is the exact shape. - [AgentSessionList](https://docs.gitpando.com/api/schemas/AgentSessionList/index.md) - [AgentTranscript](https://docs.gitpando.com/api/schemas/AgentTranscript/index.md) — Messages with parts (text, thinking, tool-call, tool-result); packages/contract/src/agent.ts. - [Artifact](https://docs.gitpando.com/api/schemas/Artifact/index.md) - [ArtifactComment](https://docs.gitpando.com/api/schemas/ArtifactComment/index.md) - [ArtifactDetail](https://docs.gitpando.com/api/schemas/ArtifactDetail/index.md) - [ArtifactList](https://docs.gitpando.com/api/schemas/ArtifactList/index.md) - [ArtifactThreads](https://docs.gitpando.com/api/schemas/ArtifactThreads/index.md) - [ArtifactVersion](https://docs.gitpando.com/api/schemas/ArtifactVersion/index.md) - [AssignedIssues](https://docs.gitpando.com/api/schemas/AssignedIssues/index.md) — Open issues assigned to one person in one monorepo. - [Branch](https://docs.gitpando.com/api/schemas/Branch/index.md) - [BranchDiff](https://docs.gitpando.com/api/schemas/BranchDiff/index.md) - [BranchDiffLine](https://docs.gitpando.com/api/schemas/BranchDiffLine/index.md) — One line of the streamed Files changed (`patches=1&stream=1`), by `type`. - [BranchRef](https://docs.gitpando.com/api/schemas/BranchRef/index.md) - [BranchSessions](https://docs.gitpando.com/api/schemas/BranchSessions/index.md) - [BuildState](https://docs.gitpando.com/api/schemas/BuildState/index.md) — A commit's checks as a build state: Checks' state, or `not_built` when nothing reported on it and nothing is required (never green). - [Checks](https://docs.gitpando.com/api/schemas/Checks/index.md) — GitHub's combined status, plus the required contexts and which are missing. - [CiAnnotation](https://docs.gitpando.com/api/schemas/CiAnnotation/index.md) - [CiArtifact](https://docs.gitpando.com/api/schemas/CiArtifact/index.md) - [CiBuild](https://docs.gitpando.com/api/schemas/CiBuild/index.md) - [CiConnection](https://docs.gitpando.com/api/schemas/CiConnection/index.md) - [CiJob](https://docs.gitpando.com/api/schemas/CiJob/index.md) - [CiPipeline](https://docs.gitpando.com/api/schemas/CiPipeline/index.md) - [CiState](https://docs.gitpando.com/api/schemas/CiState/index.md) — One vocabulary for every service. Buildkite's scheduled and waiting are queued, failing and canceling are running, not_run and broken are skipped; `service_state` keeps the service's own word. - [Commit](https://docs.gitpando.com/api/schemas/Commit/index.md) - [CommitBranch](https://docs.gitpando.com/api/schemas/CommitBranch/index.md) - [CommitDiffLine](https://docs.gitpando.com/api/schemas/CommitDiffLine/index.md) — One line of a commit's streamed diff, by `type`: `file`, `end` and `error` as in BranchDiffLine. - [CommitLine](https://docs.gitpando.com/api/schemas/CommitLine/index.md) - [CommitPage](https://docs.gitpando.com/api/schemas/CommitPage/index.md) - [ContributionPeriod](https://docs.gitpando.com/api/schemas/ContributionPeriod/index.md) — A month of the timeline (`2026-10`), or one day (`2026-10-03`). Each list is most first. - [Contributions](https://docs.gitpando.com/api/schemas/Contributions/index.md) - [Credential](https://docs.gitpando.com/api/schemas/Credential/index.md) - [Delivery](https://docs.gitpando.com/api/schemas/Delivery/index.md) - [Deployment](https://docs.gitpando.com/api/schemas/Deployment/index.md) - [Diff](https://docs.gitpando.com/api/schemas/Diff/index.md) - [DiffFile](https://docs.gitpando.com/api/schemas/DiffFile/index.md) - [Error](https://docs.gitpando.com/api/schemas/Error/index.md) - [Event](https://docs.gitpando.com/api/schemas/Event/index.md) - [FeedEvent](https://docs.gitpando.com/api/schemas/FeedEvent/index.md) — One line of the activity log (listActivity, the person's socket, the `feed` topic). - [FeedSummary](https://docs.gitpando.com/api/schemas/FeedSummary/index.md) — A monorepo's one line on the home. - [Hook](https://docs.gitpando.com/api/schemas/Hook/index.md) - [HookEvent](https://docs.gitpando.com/api/schemas/HookEvent/index.md) — `branch.opened`: the first push of a branch. `push`: any push through the door that moves or deletes a branch. `merged`: a Pando merge landed a branch; a buildkite hook builds main's merge commit (`merge_commit`) on main (`main_branch`). `tag`: a tag pushed, moved or deleted (or made by publishing a release): `tag` is its name, `head` its commit (zeros when deleted). - [HookPayload](https://docs.gitpando.com/api/schemas/HookPayload/index.md) - [Hunk](https://docs.gitpando.com/api/schemas/Hunk/index.md) - [Invite](https://docs.gitpando.com/api/schemas/Invite/index.md) - [Issue](https://docs.gitpando.com/api/schemas/Issue/index.md) - [IssueComment](https://docs.gitpando.com/api/schemas/IssueComment/index.md) - [IssueLink](https://docs.gitpando.com/api/schemas/IssueLink/index.md) - [IssueList](https://docs.gitpando.com/api/schemas/IssueList/index.md) - [IssueMerge](https://docs.gitpando.com/api/schemas/IssueMerge/index.md) - [IssueRow](https://docs.gitpando.com/api/schemas/IssueRow/index.md) — A row of the list (no body). - [Label](https://docs.gitpando.com/api/schemas/Label/index.md) - [LabelColor](https://docs.gitpando.com/api/schemas/LabelColor/index.md) — Each theme sets the eight from its own tokens (no red, so a label never reads as a failure). - [LabelList](https://docs.gitpando.com/api/schemas/LabelList/index.md) - [Login](https://docs.gitpando.com/api/schemas/Login/index.md) — A user's login, which is their namespace, or an organization's (one space for both): 1 to 39 lower-case letters, digits and single hyphens, no hyphen at either end (input in any case is lowered). Reserved, because a namespace is the first URL segment: v1, healthz, login, logout, settings, assets, fonts, new, api, app, static, orgs, users, docs, install. A login made at sign-in comes from the email's local part, skipping reserved and taken names (`base`, `base-2`, ... `base-999`, then a random tail). - [Machine](https://docs.gitpando.com/api/schemas/Machine/index.md) - [Member](https://docs.gitpando.com/api/schemas/Member/index.md) - [MemberRole](https://docs.gitpando.com/api/schemas/MemberRole/index.md) - [MergeResult](https://docs.gitpando.com/api/schemas/MergeResult/index.md) - [Monorepo](https://docs.gitpando.com/api/schemas/Monorepo/index.md) - [MonorepoCount](https://docs.gitpando.com/api/schemas/MonorepoCount/index.md) - [MonorepoEntry](https://docs.gitpando.com/api/schemas/MonorepoEntry/index.md) - [MonorepoSummary](https://docs.gitpando.com/api/schemas/MonorepoSummary/index.md) - [Name](https://docs.gitpando.com/api/schemas/Name/index.md) — A monorepo name, unique within its owner: lower-case letters, digits, `.`, `_`, `-`; starts with a letter or digit; no `--`; at most 39 characters. Input in any case is lowered. Reserved, because /{owner}/{name} would be one of Pando's own pages: sessions (a person's recorded agent sessions). Creating or renaming to one answers 400 `reserved_name`. - [NeedRow](https://docs.gitpando.com/api/schemas/NeedRow/index.md) - [NewCredential](https://docs.gitpando.com/api/schemas/NewCredential/index.md) - [Org](https://docs.gitpando.com/api/schemas/Org/index.md) - [OrgMember](https://docs.gitpando.com/api/schemas/OrgMember/index.md) - [Profile](https://docs.gitpando.com/api/schemas/Profile/index.md) — A person's profile header, as getOwner and updateProfile answer. Never their email. - [ReactionContent](https://docs.gitpando.com/api/schemas/ReactionContent/index.md) - [Reactions](https://docs.gitpando.com/api/schemas/Reactions/index.md) — Those given at least once, in GitHub's order. - [ReactionsAnswer](https://docs.gitpando.com/api/schemas/ReactionsAnswer/index.md) - [Release](https://docs.gitpando.com/api/schemas/Release/index.md) - [ReleaseFile](https://docs.gitpando.com/api/schemas/ReleaseFile/index.md) - [ReleaseNotes](https://docs.gitpando.com/api/schemas/ReleaseNotes/index.md) - [ReplacedAlias](https://docs.gitpando.com/api/schemas/ReplacedAlias/index.md) — An old `owner/name` (from a rename or transfer) that led to another monorepo and now leads to this one. Null when nothing was taken over. Never silent. - [Repo](https://docs.gitpando.com/api/schemas/Repo/index.md) - [RepoSummary](https://docs.gitpando.com/api/schemas/RepoSummary/index.md) - [Review](https://docs.gitpando.com/api/schemas/Review/index.md) - [ReviewComment](https://docs.gitpando.com/api/schemas/ReviewComment/index.md) - [Session](https://docs.gitpando.com/api/schemas/Session/index.md) — No title: who, which agent and model, where, when. Active while it has a record from the last 30 minutes. - [SessionCommit](https://docs.gitpando.com/api/schemas/SessionCommit/index.md) - [SessionDeleted](https://docs.gitpando.com/api/schemas/SessionDeleted/index.md) - [SessionEntries](https://docs.gitpando.com/api/schemas/SessionEntries/index.md) - [SessionEntry](https://docs.gitpando.com/api/schemas/SessionEntry/index.md) - [SessionExclusions](https://docs.gitpando.com/api/schemas/SessionExclusions/index.md) - [SessionList](https://docs.gitpando.com/api/schemas/SessionList/index.md) - [SessionMachines](https://docs.gitpando.com/api/schemas/SessionMachines/index.md) - [SessionStore](https://docs.gitpando.com/api/schemas/SessionStore/index.md) - [Status](https://docs.gitpando.com/api/schemas/Status/index.md) - [StatusState](https://docs.gitpando.com/api/schemas/StatusState/index.md) - [Tag](https://docs.gitpando.com/api/schemas/Tag/index.md) - [Timeline](https://docs.gitpando.com/api/schemas/Timeline/index.md) - [TimelineItem](https://docs.gitpando.com/api/schemas/TimelineItem/index.md) — By `kind`, with its own fields: `comment` (`comment`), `labeled` and `unlabeled` (`label`, as it was then), `assigned` and `unassigned` (`assignee`), `renamed` (`from`, `to`), `project_changed` (`from`, `from_path`, `to`, `to_path`), `linked` (`repo`, `repo_path`, `branch`, `source`, `sha`), `unlinked` (`repo`, `repo_path`, `branch`, `source`), `closed` (`reason`, `merge`: the merge that closed it, or null), `reopened`, `merge_referenced` (`merge`: a merge that named it after it was closed). - [TreeEntry](https://docs.gitpando.com/api/schemas/TreeEntry/index.md) - [User](https://docs.gitpando.com/api/schemas/User/index.md) - [Worktree](https://docs.gitpando.com/api/schemas/Worktree/index.md) - [Append whole lines at the session's end (the first piece makes it)](https://docs.gitpando.com/api/sessions/appendSessionPiece/index.md) — Header `Pando-Session`: JSON `{program, agent_id, repo, branch, parent}` (`repo` and `branch` null in the repository's own folder; `parent` a session id or null). `Content-Encoding: gzip` is optional. Only the session's own person adds to it. - [Delete at once and for good (its person or an owner); its pieces leave R2 now](https://docs.gitpando.com/api/sessions/deleteSession/index.md) - [A branch's sessions, numbered 1, 2, 3 by their earliest commit there, and each commit's session numbers](https://docs.gitpando.com/api/sessions/getBranchSessions/index.md) — A commit links to a session by a `Pando-Session:` line anywhere in its message, read from the branch's own commits (at most 250) with its facts after each push, and from the commits that reach main. A rewritten commit drops out. A commit's model is the one in force in the session at its commit time, and null when its sessions disagree. Sessions that ran in the branch's worktree with no commit there yet follow, with no number. - [One session, or the stub a deleted one leaves (who deleted it, when)](https://docs.gitpando.com/api/sessions/getSession/index.md) - [A page of the conversation, parsed on read, about 2 MB of lines at a time](https://docs.gitpando.com/api/sessions/getSessionEntries/index.md) - [The repositories and projects you do not record, on every machine you sign in on](https://docs.gitpando.com/api/sessions/getSessionExclusions/index.md) - [Each of your machines, as it last reported its recording (Settings)](https://docs.gitpando.com/api/sessions/listSessionMachines/index.md) - [Sessions by last activity, newest first (deleted ones are not listed)](https://docs.gitpando.com/api/sessions/listSessions/index.md) - [Read every stored piece again and rebuild the session's numbers (how a parser fix reaches stored sessions)](https://docs.gitpando.com/api/sessions/reindexSession/index.md) - [Remove prompt n's turn (its person or an owner); a mark keeps its number, who and when](https://docs.gitpando.com/api/sessions/removeSessionTurn/index.md) — Every line from the prompt up to the next prompt (or up to now, for the last) is cut from the stored pieces by its own time. - [This machine (the calling credential) says whether it records sessions and which stores it reads](https://docs.gitpando.com/api/sessions/reportSessionMachine/index.md) - [Replace that list (at most 200)](https://docs.gitpando.com/api/sessions/setSessionExclusions/index.md) - [The combined status of a commit (latest per context)](https://docs.gitpando.com/api/statuses/combinedStatus/index.md) - [What a commit changed against its first parent, streamed as Files changed is](https://docs.gitpando.com/api/statuses/commitDiffStream/index.md) — `application/x-ndjson`, one `CommitDiffLine` a line: `start` (the commit, its first parent, null for a first commit, `files_total`, `too_many_files`), then one `file` per changed file in a file tree's order, then `end`; `error` if the diff fails after its start. The limits are Files changed's: at most 5,000 files, 32 MiB of patches in all, a text file over 8 MiB on either side not diffed. The whole answer is kept by the sha once streamed, with the sha as its `ETag` (a 304 with `If-None-Match`). - [Report a commit status (GitHub's body)](https://docs.gitpando.com/api/statuses/createStatus/index.md) — With a member's credential, the status shows that member as `creator` and has `app` null, whatever the body says. With a status token (`PANDO_STATUS_TOKEN` in a Buildkite build's env, `status_token` in a web hook's payload), Pando stamps `app` from the hook that delivered it (`buildkite`, a web hook's own `app`, or null) and `creator` `app:` (`hook:` when null). A status token posts statuses for its one commit in its one repo, for 24 hours, on this route only: no other route and no git. - [One commit, the branch it came from, and its checks (the web's commit page)](https://docs.gitpando.com/api/statuses/getCommit/index.md) — `branch` is the branch it was pushed on, when Pando knows it: the earliest branch that listed it among its own commits (the newest 250 of each branch are recorded when Pando compares the branch with main), else a branch whose head it is or was, else the branch a session saw it on. `merge_of` is set when the commit is Pando's merge of a branch into the default branch. `author_person` is the person whose sign-in email (or `@users.pando.invalid`) is the author's, when they can open the monorepo. - [agents](https://docs.gitpando.com/api/tags/agents/index.md) — Cloudflare-run agents: a session launched from Pando runs in its own Durable Object in the Worker pando-agents, with a Linux sandbox whose workspace is the session's branch. It acts as the person who launched it, through a sandbox credential minted for it (`cloud-`, the session's machine); revoking that credential cuts it off. Its state is only what is known exactly (working, waiting, failed, stopped), and every change is an `agent.updated` event. Without pando-agents the routes answer 503 `agents_unavailable`. - [artifacts](https://docs.gitpando.com/api/tags/artifacts/index.md) — Documents uploaded to a branch's page (the pull request), never in git: a Markdown or HTML file with the images it uses, or an image (research/plans/artifacts-2026-10-12/SPEC.md). Bytes in R2, rows in the monorepo object. The same name again is the next version; every version is kept. Comments are Plannotator's annotations (the anchor is stored as written, never read by the server) with Pando's thread: replies one level deep, open or resolved. Members only; any member may delete an artifact (every version, its bytes and comments go, with no undo). Limits: 10 MiB an artifact in all, its document at most 1.5 MiB, 20 files; 100 artifacts on a branch; 500 threads on one version, 100 replies in a thread; a comment 65,536 characters, its anchor 16,384 bytes and 16 extra HTML elements; 10 people named in one comment. - [branches](https://docs.gitpando.com/api/tags/branches/index.md) - [ci](https://docs.gitpando.com/api/tags/ci/index.md) — CI on one model for every service: a **service** (`buildkite` today), a **connection** (one account of it: a Buildkite organization), its **pipelines** (each builds one repo), **builds**, **jobs**, and each job's **check**. Pando runs no CI. - **Stored by Pando, pushed by the service.** A connection gets a webhook URL; the service's webhooks keep builds and jobs (state, times, links) in the monorepo's records, written in one step with their events on the monorepo's stream (`ci.build`, `ci.job`) before the webhook is answered. Viewers cost the service nothing. - **A check is a job named by its step key**: a job keyed `ci` writes the status `ci` (`app` `buildkite`, `creator` `app:buildkite`), so `required_checks = ["ci"]` in `.pando/pando.toml` gates merges on it. Commands and triggered builds report; waits and blocks do not. Only the newest job with that key, in the newest build of that pipeline for that commit, reports (a retry or a rebuild takes over). States: passed is success; failed is failure (soft failed: success); canceled, not run after a failure (`broken`) or not taken by an agent (`expired`) are error; a skipped step is success. - **Fetched from the service when opened**: a job's log (its last 1 MiB), a build's annotations and artifacts (first 100 of each), a pipeline's configuration (read at most every 5 minutes). Cached once settled (finished over 30 s ago), else for 10 s. - **Actions**, any member: Rebuild (a new build of the same commit with fresh tokens; the service's own rebuild would reuse the old build's env), Cancel and Retry (passed through). - **Mapping a build to a repo**: Pando's own builds carry `meta_data.pando_delivery`, `pando_monorepo` and `pando_repo`; a build started in the service maps through the buildkite hook that names its pipeline. Other builds (another monorepo's, another organization's, an unmapped pipeline's) are answered 200 and ignored. - **Who pushed** comes from Pando's own records: the delivery that started the build, else the push or merge that made the commit. - **The service's rate limit**: Buildkite allows 200 REST calls a minute per organization and 50 per user token. A connection makes at most 40 a minute (10 are left for starting builds); past that, or after Buildkite answers 429, calls answer 429 `ci_rate_limited` ("try again in N s", with `retry_after_ms`). A reader of a log, annotations or artifacts gets the last copy instead, when there is one: 200 with `cached: true`, `complete: false` and `retry_after_ms`. pipeline.yml shows its stored copy with `configuration_error`. Never a 5xx for the hold. Errors from the service: `ci_unauthorized` (502: the token lacks a scope), `ci_refused` (409: Buildkite refused the action), `ci_service` (502), `ci_rate_limited` (429), `ci_token_missing` (503: the Worker secret BUILDKITE_API_TOKEN is not set). - [git](https://docs.gitpando.com/api/tags/git/index.md) — The git door. Plain git clients use it; no Pando client is needed. ``` GET /{owner}/{monorepo}/{repo}.git/info/refs?service=git-upload-pack GET /{owner}/{monorepo}/{repo}.git/info/refs?service=git-receive-pack POST /{owner}/{monorepo}/{repo}.git/git-upload-pack POST /{owner}/{monorepo}/{repo}.git/git-receive-pack ``` A plain repo drops the repo segment: `/{owner}/{name}.git/...`. The other shape answers 404 saying which one fits. An old `owner/name` (after a rename or a transfer) still works. Credentials: HTTP Basic with the user's credential as the password (any username; this is what `pando credential`, the git credential helper, supplies), or `Authorization: Bearer `. Without credentials the door answers 401 with `WWW-Authenticate: Basic`, so a credential helper is asked. Protocol v2 `object-info` (sizes without downloading): Artifacts does not offer it, so the door adds `object-info` to the v2 advertisement and answers `command=object-info` itself, in git's own reply format (`size`, then ` ` per object; an unknown object gets ` ` with no size). Sizes come from a per-repo index, filled on first ask and kept forever. Every other v2 command goes to Artifacts unchanged. Clone and fetch also accept the read token from a hook delivery (`read_token` for a web hook, 30 minutes; `PANDO_READ_TOKEN` for a Buildkite build, at least 24 hours so Buildkite's Retry can still clone), so CI clones from `clone_url` with no credential of its own. Only members of the monorepo get through. Writes per repo: an Artifacts repo takes about two writes at once, so every write (a push, a branch create or delete, a merge, a meta layer commit) waits for one of the repo's two write slots, first come first served. git just waits. A push that waits 120 s gets `remote error: Pando: is busy with other writes; try the push again`. The ceiling is about 5 to 10 small pushes a second per repo. Push rule (receive-pack). The door reads the command list before the pack. If any command updates or deletes the repo's default branch (`refs/heads/main`), it refuses the whole push with git's usual `! [remote rejected] main (main is protected; ...)` line. Creating main when it does not exist yet is allowed: that is the first push of `pando init`. Main moves only by a merge. Busy repos. Artifacts runs about 2 receive-packs at once per repo and answers others "busy". The door holds a push of up to 1 MiB in memory and sends it again once, after 150 to 400 ms. A push that is still refused, or is larger, is rejected with `Pando: is busy with other pushes right now; push again` (every ref `busy; push again`), and nothing is written: push again. A call to Artifacts that fails outright is handled the same way: retried once for a held push, else `Pando: couldn't take this push right now; push again`, never a bare HTTP 500. Push size. One push carries at most 64 MiB of receive-pack request (67,108,864 bytes), Artifacts' limit. A push whose `Content-Length` is over it is refused before anything goes to Artifacts; a streamed one is cut off before its pack ends, so it cannot land. Either way git gets `Pando: this push is over 64 MiB, the most one push can carry (Artifacts' limit); nothing landed.`, how to push in steps (for a first push of main: `pando init`, which pushes the history in steps on `refs/pando/import` and `refs/pando/import-files`, then deletes them), and every ref `over 64 MiB; push in steps`. Nothing is recorded. Raw files, GET or HEAD, beside the git door: `/{owner}/{monorepo}/projects/{project}/raw/{path}?ref=` for a monorepo's project (the repo at `projects/{project}`, named `projects-{project}`), `/{owner}/{name}/raw/{path}?ref=` for a plain repo, and `/{owner}/{monorepo}/raw/{path}?v=` for a monorepo's own files (the meta layer; `v` is a meta layer version). Members only, by the session cookie or a Bearer credential. Served so nothing runs: `Content-Security-Policy: sandbox`, nosniff, images as images, everything else as `text/plain` (HTML and SVG included) or, with a NUL in its first 8,000 bytes, `application/octet-stream`. `ETag` is the content's hash (the git blob's; a large meta file's SHA-256). By branch, or a meta file now: `private, no-cache` (a repeat is a 304); by a 40-character sha or a `v`: `private, max-age=31536000, immutable`. The door records each pushed branch's head and, on its first push, its author (the user). Then it delivers the `branch.opened` and `push` hooks. - [hooks](https://docs.gitpando.com/api/tags/hooks/index.md) - [issues](https://docs.gitpando.com/api/tags/issues/index.md) — Issues, as on GitHub: one list per monorepo, numbered `#1`, `#2`, ... in arrival order with no gaps (one counter, taken in one step), with an optional `project` (the repo it is about). Members open, comment, edit, label, assign, close and reopen any issue; owners manage labels and may delete anyone's comment. Bodies and comments are Markdown, up to 65,536 characters; titles 1 to 256. **Branches and merges.** A branch is linked to an issue three ways: a closing keyword in its title or description ("Fixes #12"), one in a commit on the branch (read with the branch's facts), or an explicit `link`. Keywords are GitHub's: close, closes, closed, fix, fixes, fixed, resolve, resolves, resolved, any case, an optional colon, then `#12` or `owner/name#12` of this monorepo; one keyword per number; text in backticks is skipped; a `git revert` commit never closes. A merge into main decides, as it lands, the open issues it closes: the branch's links and the keywords in its merge message and merged commits (MergeResult `closes`, also on its `merged` line). Each closes about a second later, in its own step, as completed, naming the merge (`issue.closed`); one a person closed meanwhile is left as it is. Issues never refuse a merge. A revert never reopens. **Writes are safe to retry.** Create and comment take an `Idempotency-Key` header (up to 64 characters, kept for good, per person): a retry answers the first one (200, `Pando-Replayed: true`). Labels, assignees, links, reactions and state are set operations. A body edit with `base_version` never overwrites someone else's: a stale one is 409 `edited` with `current`, the issue or comment as it is now. **Live.** Every change is one event on the monorepo's stream (topics `issues` and `issue:`); `issue.opened`, `issue.commented`, `issue.closed` (by a person) and `issue.reopened` are activity lines. Open issues assigned to a person show in the home's "Needs you". - [merges](https://docs.gitpando.com/api/tags/merges/index.md) - [monorepos](https://docs.gitpando.com/api/tags/monorepos/index.md) - [orgs](https://docs.gitpando.com/api/tags/orgs/index.md) - [releases](https://docs.gitpando.com/api/tags/releases/index.md) — Builds of main, tags and releases, per project. Pando records them; the CI runs the builds (research/plans/releases-2026-10-04). - **Main is built at merge.** A Buildkite hook taking `merged` builds main's new merge commit on main (its status token is for that commit), never the branch head again. - **Not built.** The summary reads `not_built` for a commit nothing reported on and nothing requires, never green. The merge gate keeps reading such a commit as `success`. - **Tags** are recorded by the git door from `refs/tags/*` as pushes land, an annotated tag peeled to its commit; events `tag.created` and `tag.deleted`; the hook event `tag`. - **A release** is a tag on its project's main with a title, notes and files. Anyone who may merge may publish one; the commit's required checks must be green unless forced (the merge rule). A published release's tag can't be moved or deleted with git until the release is deleted; deleting a release keeps its tag. - **Notes** can be written from the merges since the last published release: each branch's title, who merged it, the issues it closed. At most 500 merges, then "and N more"; notes at most 100,000 characters. - **Files** are kept in R2: at most 100 MB (100,000,000 bytes) each and 20 a release, SHA-256 kept. Downloads are for members unless the project's switch "Anyone can download releases" (`releases_public`) is on; it opens release files only, never code. - [repos](https://docs.gitpando.com/api/tags/repos/index.md) - [reviews](https://docs.gitpando.com/api/tags/reviews/index.md) - [sessions](https://docs.gitpando.com/api/tags/sessions/index.md) — Agent sessions: one conversation between a person and a coding agent (Claude Code, Codex, Pi, OpenCode V2), recorded by `pando sync` on the person's machine after secrets are removed there, and stored as sent, in pieces in R2. Every member reads them as they are recorded; nothing on them is written by a model. The server parses on read with letta-ai/trajectory 0.4.3, plus a pass of its own for the model of each answer. **Appending.** A piece is whole lines, at most 8,000,000 bytes, sent with where it starts (`at`) and where the next starts (`next`) in the sender's own cursor: a byte offset in the agent's file, or the last row read for OpenCode. It is taken only when `at` is the session's end; any other start is 409 `not_at_end` with `end`, which the sender resumes from. A deleted session answers 410 `session_deleted`: send nothing more. - [statuses](https://docs.gitpando.com/api/tags/statuses/index.md) - [users](https://docs.gitpando.com/api/tags/users/index.md) - [worktrees](https://docs.gitpando.com/api/tags/worktrees/index.md) - [Make a credential for this user (another machine, a cloud sandbox)](https://docs.gitpando.com/api/users/createCredential/index.md) — The new credential acts as the same user. Its secret is shown once. - [Make a user and a first credential (bootstrap secret only; self-hosted, one person)](https://docs.gitpando.com/api/users/createUser/index.md) - [A person's contribution graph and timeline for the last year](https://docs.gitpando.com/api/users/getContributions/index.md) — A contribution is a commit that reached a repository's main (credited to its author and to every Co-authored-by person whose sign-in email it names, if they can open that repository; merge commits never), a branch opened (by the person whose sign-in pushed it), a review, or a repository made. Pushes, merges as such, checks and saves of the shared files themselves do not count. A repository's past commits count when its main first arrives (pando init in an existing repo, a first push of main), read in the background a page at a time. A viewer sees only the work in repositories they can open now, on their own profile too. Counts are kept per UTC hour in the person's feed; a credit shows about half a second after a merge. With `day`, the answer is that day's ContributionPeriod. 404 for an org or no such person. - [Whose namespace this is, a person or an organization (what `/{name}` shows)](https://docs.gitpando.com/api/users/getOwner/index.md) - [This user's credentials (no secrets)](https://docs.gitpando.com/api/users/listCredentials/index.md) - [Sign in a browser (redirects to AuthKit)](https://docs.gitpando.com/api/users/loginBrowser/index.md) - [Where AuthKit sends the browser back (sets the session cookie)](https://docs.gitpando.com/api/users/loginCallback/index.md) - [pando login, step 2 (polled): the user and a new machine credential](https://docs.gitpando.com/api/users/loginPoll/index.md) — Poll every `interval` seconds. Until the person confirms, the answer is 400 with `error.code` `authorization_pending` (keep polling) or `slow_down` (add 5 seconds to the interval). `access_denied`, `expired_token` and `invalid_grant` end the attempt. On success, `joined` lists the monorepos (`owner/name`) whose email invites were accepted, and the CLI stores `credential.secret` in `~/.pando/credentials/` (mode 600). - [pando login, step 1: a code to confirm in the browser](https://docs.gitpando.com/api/users/loginStart/index.md) — Starts WorkOS CLI Auth. Show `user_code` and `verification_uri` (or open `verification_uri_complete`). Keep `device_code` to yourself: it is what the CLI polls with. No credential needed. - [Sign out this browser (and end its WorkOS session)](https://docs.gitpando.com/api/users/logout/index.md) — Revokes the session in the cookie, clears the cookie, and sends the browser to WorkOS's logout URL, which returns to `/`. Needs `Origin` equal to Pando's own. - [Opened in a browser, goes to the app](https://docs.gitpando.com/api/users/logoutPage/index.md) — A GET never ends a session. It sends the browser to the app (`/`), where the name menu signs out (POST). - [The user behind the credential, and the credential itself](https://docs.gitpando.com/api/users/me/index.md) - [Revoke one of this user's credentials](https://docs.gitpando.com/api/users/revokeCredential/index.md) - [Change your profile's header (Edit profile)](https://docs.gitpando.com/api/users/updateProfile/index.md) — A field left out is kept; null or "" clears it. Once a name is saved here, signing in no longer refreshes it from WorkOS. The picture is always the sign-in's; the email is never shown on a profile. - [What a `web` hook receives](https://docs.gitpando.com/api/webhooks/hookDelivery/index.md) — Headers: `X-Pando-Event` (the event), `X-Pando-Delivery` (the delivery id), `X-Pando-Signature-256: sha256=` (HMAC-SHA256 of the raw body with the hook's secret; GitHub's scheme under another name). Deliveries leave as soon as the change is recorded (no queue): at least once and not in order, like GitHub's. The body's `seq` is the event stream's sequence number: drop a seq you have seen, and sort by it. A try that does not get a 2xx within 10 seconds is retried after 10 s, 1 min, 5 min and 30 min (5 tries over about 36 minutes); then the delivery is dead until an owner redelivers it or replays the hook. Delivery records are kept for the latest 1,000 per hook, with each one's last 10 tries. - [One worktree](https://docs.gitpando.com/api/worktrees/getWorktree/index.md) - [The Machines tab, one row per machine (a credential), latest activity first](https://docs.gitpando.com/api/worktrees/listMachines/index.md) — A machine is a credential: the row's key is (login, credential_id), and it is shown by the credential's name, so two people's `laptop`s stay apart. Kept by the monorepo in the same step as each change: - every activity event made through the credential sets `last_activity` (the line's kind, repo, branch and summary, as in FeedEvent; a push's commit count and title arrive a moment later); - a follower's hello on the event socket sets it `connected`, with its worktrees; its last such socket closing sets it disconnected. Machines with no activity yet (only connected) come after the others. Browser sessions (kind `browser`) appear only once they act, never as connected; show them as the web. Live: `machine.updated` on the event stream. - [Every worktree in the monorepo, across machines](https://docs.gitpando.com/api/worktrees/listWorktrees/index.md) - [Report a worktree (creates it, or replaces the one with this id)](https://docs.gitpando.com/api/worktrees/putWorktree/index.md) — Each call also refreshes `last_seen_at`. - [Remove a worktree from the registry](https://docs.gitpando.com/api/worktrees/removeWorktree/index.md)