---
title: "Pando API"
description: "Shape:\n- A monorepo is `owner/name`, like GitHub: the owner is\n  a namespace (a person's login, or an organization's), the name is\n  unique within its owner, and a permanent `id` keys everything stored.\n  A rename or a transfer changes the label only; the old `owner/name`\n  keeps working (an alias) until its owner gives that name to another\n  monorepo, which the create or rename answer reports\n  (`replaced_alias`). Owners and names match without case and are\n  stored lower case.\n- In payloads, `monorepo` is always the `owner/name` label and\n  `monorepo_id` always the id (repos, hook payloads, feed lines,\n  needs-you rows, socket frames). The Monorepo object itself has `id`,\n  `owner`, `name` and `full_name`.\n- Every route is under `/v1/monorepos/{owner}/{monorepo}`, except the\n  user's own (`/v1/user`, `/v1/monorepos`) and the web home's, which\n  span all of the person's monorepos (`/v1/events`, `/v1/branches`). A\n  monorepo holds repos. A plain git repo is a monorepo of kind `repo`\n  holding exactly one repo, at path `.`, and no meta layer; it uses the\n  same routes. The meta layer's routes\n  (`/v1/monorepos/{owner}/{monorepo}/meta/...`) are not in this reference.\n- A repo has a `path` (its folder from the root: `projects/web`, or `.` in a\n  plain repo) and a `name`, its id in URLs: the path with each `/`\n  turned into `-` (`projects-web`). A plain repo's name is the\n  monorepo's name when it was made (a rename does not change it).\n  Names are unique in a monorepo.\n- In a monorepo, the repo named `meta` (path `.`, clone_url\n  `<host>/<owner>/<monorepo>/meta.git`) is the meta layer's own git history. It\n  takes branches, diffs, reviews and merges like any repo, and it is how\n  `.pando/**` changes: after a merge into it, the merged files go to the\n  live meta layer on every machine. It is not in listRepos.\n- Git goes through the git door at the repo's `clone_url`:\n  `https://<host>/<owner>/<monorepo>/<name>.git` for a monorepo's repos,\n  `https://<host>/<owner>/<name>.git` for a plain repo (smart HTTP; see\n  the `git` tag). Pushes to the default branch are refused, except the first push\n  that creates it. Every other push goes through.\n- Every other call is JSON with `Authorization: Bearer <credential>`.\n\nArtifacts repos, in the namespace `pando-dev`, are named from the\nmonorepo's id and kept on each repo's record (Artifacts has no rename):\na project repo is `<id>--<name>` (`m_0123456789abcdef--projects-web`), a\nplain repo `<id>--<name>`, and the meta layer `<id>--meta`. New ids are\n`m_` and 16 hex characters; a monorepo made before namespaces kept its\nold name as its id. Ids and names have no `--`, so the split is never\nambiguous. These names are internal: clients use `clone_url`.\n\nRequired checks: the server reads `required_checks` from\n`.pando/pando.toml` on main. In a monorepo\nthat is the meta layer's main, table `[repos.\"<repo path>\"]`; in a plain\nrepo, the repo's own main, table `[repos.\".\"]`. No main or no file means\nno required checks. When Artifacts fails to read it, or it does not\nparse, they are unknown, and only the merge fails closed: reads (the\nrepo, the branch list, a branch, a commit's status, CI) answer as usual\nwith the list null and `checks_unknown` saying why (for a bad file,\n\"pando.toml line N: <what is wrong>\"), checks `state` is `unknown`; an\nunforced merge is refused with 503 `checks_unknown` and that reason, a\nforced one goes through as forced. Nothing is kept, so the next request\nreads it again.\n\nIdentity works like GitHub's. A person has a user account and belongs to\nmonorepos as `owner` or `member`. A machine signs in once\n(`pando login`) and holds a credential for that user; git and every agent\non the machine act as that user, the way `gh` and git credentials do. A\ncloud sandbox gets a credential its user makes for it. Credentials are\nlisted and revocable. Commits are authored as the user; Pando's commit\ntrailers name the agent.\n\nAny member may push, review, post statuses, report worktrees and merge\n(agents included, through the user's credential). Reviews are comments\nand approvals, not a gate. The merge gate is the checks, and `?force=1`\nmerges past them. Owners also manage members and hooks.\n\nOrganizations: anyone signed in creates one (`POST /v1/orgs`); its name\nis a namespace in the same space as logins. Roles are `owner` and\n`member`. Everyone in an org can open and push to every monorepo the\norg owns, and its owners are owners of all of them: a person's role in\nan org's monorepo is the higher of their own row in the monorepo and\ntheir org role. A monorepo can still add people from outside the org\nitself. People are added at once (by login, or by an email that has\nsigned in), or at that email's first sign-in. Removal from an org takes\neffect within 15 s, as for a monorepo.\n\nSigning in (`pando login`): WorkOS AuthKit's CLI Auth, the OAuth device\nflow, run through the Worker. `POST /v1/login/device` returns a code\nand a URL; the person confirms the code in the browser; the CLI polls\n`POST /v1/login/token` with the device code until it returns the user and\na new machine credential. WorkOS only proves who the person is: Pando\nkeeps its own users, credentials and membership, and keeps no WorkOS\ntoken. A user's login comes from their email (`ana@x.com` is `ana`, or\n`ana-2` if taken). Invites by email wait until that email first signs in.\n\nIn a browser: `GET /v1/login/browser?return_to=<path>` sends the person to\nAuthKit's page; WorkOS sends them back to `/v1/login/callback`, which sets\nthe session cookie `__Host-pando_session` (HttpOnly, Secure,\nSameSite=Lax, host-only) and returns them to `return_to` (a path on this\nhost, else `/`). The session is a credential of kind `browser` for the\nsame Pando user, so it sees the same monorepos. It works on `/v1` only:\nnever as a Bearer token and never at the git door. With the cookie, a\nwrite (any method but GET and HEAD) or a WebSocket upgrade must carry\n`Origin` equal to Pando's own origin, else 403 `bad_origin`. A session\nends after 30 days without use, on sign-out (`POST /v1/logout`, which also\nends the WorkOS session), or when revoked.\n\nSelf-hosted, one person: the Worker secret `PANDO_BOOTSTRAP` makes users\nwith `POST /v1/users` (it works there only). Either way, a user then makes\nmore credentials (`POST /v1/user/credentials`) and monorepos\n(`POST /v1/monorepos`).\n\nAgents: an agent acts as its user, through the user's credential. It may\nalso send `X-Pando-Agent: <name>` on API calls and pushes; Pando then\nrecords the agent on merges (`merged_agent`) and on events.\n\nBranch names in paths: encode `/` as `%2F` (`agent-3%2Foauth`). Raw\nslashes are accepted too.\n\nErrors are JSON: `{\"error\": {\"code\": \"...\", \"message\": \"...\"}}`.\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.

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
  `<host>/<owner>/<monorepo>/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://<host>/<owner>/<monorepo>/<name>.git` for a monorepo's repos,
  `https://<host>/<owner>/<name>.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 <credential>`.

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 `<id>--<name>` (`m_0123456789abcdef--projects-web`), a
plain repo `<id>--<name>`, and the meta layer `<id>--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."<repo path>"]`; 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: <what is wrong>"), 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=<path>` 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: <name>` 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": "..."}}`.

Version: 1

## Servers

- `https://app.gitpando.com`
- `https://pando-dev.{subdomain}.workers.dev`

## Sections

- [git](/api/tags/git/)
- [sessions](/api/tags/sessions/)
- [agents](/api/tags/agents/)
- [issues](/api/tags/issues/)
- [releases](/api/tags/releases/)
- [artifacts](/api/tags/artifacts/)
- [ci](/api/tags/ci/)
- [users](/api/tags/users/)
- [orgs](/api/tags/orgs/)
- [monorepos](/api/tags/monorepos/)
- [branches](/api/tags/branches/)
- [repos](/api/tags/repos/)
- [reviews](/api/tags/reviews/)
- [merges](/api/tags/merges/)
- [statuses](/api/tags/statuses/)
- [hooks](/api/tags/hooks/)
- [worktrees](/api/tags/worktrees/)


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