Skip to content

API Reference

All endpoints (except GET /healthz) require authentication. In production, the server validates the GCP IAP JWT from the X-Goog-IAP-JWT-Assertion header. In dev/test, set the DEV_IDENTITY env var on the server to bypass JWT validation.

Repo-scoped endpoints use the /-/ separator to allow org/repo names to contain slashes without ambiguity:

GET /repos/acme/platform/-/tree
POST /repos/myorg/subrepo/-/commit

Health

GET /healthz

Returns server status. No authentication required.

Response 200:

{"status": "ok"}

Orgs

POST /orgs

Create an org.

Body:

{"name": "acme"}

Response 201: Org object.

GET /orgs

List all orgs.

Response 200:

{"orgs": [...]}

GET /orgs/{org}

Get a single org.

Response 200: Org object. 404 if not found.

DELETE /orgs/{org}

Delete an org. Fails with 409 if the org has repos.

Response 204.

GET /orgs/{org}/repos

List repos owned by the org.

Response 200:

{"repos": [...]}

Org membership

GET /orgs/{org}/members

List org members.

POST /orgs/{org}/members/{identity}

Add a member. Body: {"role": "owner|member"}.

DELETE /orgs/{org}/members/{identity}

Remove a member.

Org invitations

GET /orgs/{org}/invites

List pending invitations.

POST /orgs/{org}/invites

Create an invitation. Body: {"email": "...", "role": "owner|member"}. Returns a token.

POST /orgs/{org}/invites/{token}/accept

Accept an invitation by token.

DELETE /orgs/{org}/invites/{id}

Revoke a pending invitation.

Repos

POST /repos

Create a repo.

Body:

{"owner": "acme", "name": "platform"}

Response 201: Repo object.

GET /repos

List all repos.

GET /repos/{name}

Get a repo. {name} is the full path (acme/platform).

DELETE /repos/{name}

Delete a repo.

Branches

GET /repos/{name}/-/branches

List branches.

Query parameters: - statusactive (default), merged, or abandoned. - drafttrue to show only draft branches. - include_drafttrue to include draft branches.

Response 200:

{"branches": [{"name": "...", "head_sequence": 42, "base_sequence": 10, "status": "active", "draft": false, "created_by": "...", "created_at": "..."}]}

POST /repos/{name}/-/branch

Create a branch rooted at the current main head.

Body:

{"name": "feature/x"}

Response 201.

GET /repos/{name}/-/branch/{bname}

Get branch metadata (head sequence, base sequence, status, draft flag).

PATCH /repos/{name}/-/branch/{bname}

Update branch flags. Currently supports promoting draft to ready.

Body:

{"draft": false}

DELETE /repos/{name}/-/branch/{bname}

Delete a branch. Cannot delete main. Maintainer-only.

GET /repos/{name}/-/branch/{bname}/status

Evaluate merge policies without merging. Returns mergeability and per-policy results.

Response 200:

{
  "mergeable": true,
  "policies": [
    {"name": "require_review", "pass": true, "reason": ""}
  ]
}

Commits

POST /repos/{name}/-/commit

Create an atomic commit containing one or more file changes.

Body:

{
  "branch": "feature/x",
  "message": "update config",
  "files": [
    {"path": "config.yaml", "content": "<base64>", "content_type": "text/yaml"},
    {"path": "old.txt"}
  ]
}
  • files[].content — Base64-encoded file content. Omit or set to null to delete the file.
  • files[].content_type — Optional MIME type.

The commit author is always the authenticated identity; clients cannot override it.

Response 201:

{"sequence": 43}

GET /repos/{name}/-/commit/{sequence}

Get commit metadata for a specific sequence number.

Response 200:

{
  "sequence": 43,
  "branch": "feature/x",
  "message": "update config",
  "author": "alice@example.com",
  "created_at": "...",
  "files": [{"path": "config.yaml", "version_id": "..."}]
}

Tree and files

GET /repos/{name}/-/tree

Materialize the directory tree.

Query parameters: - branch — Branch name (default main). - at — Sequence number to materialize at. - limit — Maximum number of entries (default 100). - after — Pagination cursor (path of the last entry from the previous page).

Response 200:

{
  "entries": [
    {"path": "config.yaml", "version_id": "...", "content_type": "text/yaml", "binary": false}
  ]
}

GET /repos/{name}/-/file/{path}

Get file content.

Query parameters: - branch — Branch name (default main). - at — Sequence number.

Response 200:

{"path": "config.yaml", "content": "<base64>", "content_type": "text/yaml", "version_id": "..."}

404 if the file does not exist on the branch at the requested sequence.

GET /repos/{name}/-/file/{path}/history

Get the commit history for a file.

Query parameters: - branch — Branch name (default main). - limit — Maximum entries (default 100). - after — Pagination cursor (sequence number of the last entry).

Diff

GET /repos/{name}/-/diff

Show files changed on a branch relative to its base on main.

Query parameters: - branch — Branch name (required).

Response 200:

{
  "changes": [
    {"path": "config.yaml", "status": "modified", "binary": false, "diff": "..."}
  ],
  "conflicts": []
}

Merge and rebase

POST /repos/{name}/-/merge

Merge a branch into main. Policy evaluation runs before the merge.

Body:

{"branch": "feature/x"}

Response 200: Merge result including new main head sequence.

Response 409: Merge conflicts (body includes conflict details).

Response 403: Policy denied (body includes policy results).

POST /repos/{name}/-/rebase

Rebase a branch onto the current main head.

Body:

{"branch": "feature/x"}

Response 200: Rebase result.

Response 409: Rebase conflicts.

Reviews

POST /repos/{name}/-/review

Submit a review.

Body:

{"branch": "feature/x", "status": "approved", "body": "LGTM"}

status is approved or rejected.

Response 201: Review object including ID, reviewer, sequence, and created_at.

GET /repos/{name}/-/branch/{branch}/reviews

List reviews for a branch.

Query parameters: - at — Show only reviews at or before this sequence.

Response 200: Array of review objects.

Check runs

POST /repos/{name}/-/check

Report a CI check result.

Auth: IAP JWT (human users) or job OIDC JWT (Authorization: Bearer {jwt}, issued by oidc.docstore.dev).

Body:

{
  "branch": "feature/x",
  "check_name": "ci/build",
  "status": "passed",
  "log_url": "https://...",
  "sequence": 43
}

status is passed, failed, or pending.

Response 201: Check run object.

When submitted via a job OIDC JWT, the reporter field on the check run is set to ci-job:{job_id} (the job UUID from the JWT's job_id claim). This identifies which CI job produced the result.

GET /repos/{name}/-/branch/{branch}/checks

List check runs for a branch.

Query parameters: - at — Show only check runs at or before this sequence.

Review comments

POST /repos/{name}/-/comment

Add an inline comment on a file in a branch.

Body:

{"branch": "feature/x", "path": "config.yaml", "body": "Needs update"}

GET /repos/{name}/-/branch/{branch}/comments

List review comments. Optional path query parameter to filter by file.

DELETE /repos/{name}/-/comment/{id}

Delete a review comment. Writers can delete their own comments; maintainers can delete any.

Roles

GET /repos/{name}/-/roles

List role assignments. Admin-only.

PUT /repos/{name}/-/roles/{identity}

Set a role. Admin-only.

Body:

{"role": "writer"}

DELETE /repos/{name}/-/roles/{identity}

Remove a role assignment. Admin-only.

Archive

GET /repos/{name}/-/archive

Download the repo tree as a .tar.gz.

Query parameters: - branch — Branch name (default main). - at — Sequence number.

Requires authentication (IAP JWT or job OIDC JWT). For CI workers, use the presigned URL variant instead.

GET /repos/{name}/-/archive (presigned — no auth required)

When the sig and expires query parameters are present, the server validates the HMAC signature and serves the archive without authentication. This form is intended for BuildKit llb.HTTP sources.

Query parameters: - branch — Branch name (required). - at — Sequence number (required). - expires — Unix timestamp after which the URL is invalid (required). - sig — Hex-encoded HMAC-SHA256 over repo\nbranch\nsequence\nexpires (required).

Response 403 if the signature is invalid or the URL has expired.

CI Job Authentication

POST /repos/{name}/-/archive/presign

Obtain a time-limited presigned URL for a CI job's source archive. The presigned URL can be fetched by BuildKit without any credentials.

Auth: Authorization: Bearer {request_token} (the one-time token returned by ci-scheduler's POST /claim).

The request_token is bound to a specific job; the repo in the token must match the {name} path parameter.

Response 200:

{"url": "https://docstore.dev/repos/{repo}/-/archive?branch={branch}&at={seq}&expires={unix}&sig={hex}"}

The URL is valid for 1 hour. Pass it directly to BuildKit as the llb.HTTP source — no credentials needed.

Response 401 if the token is missing, invalid, or expired.

Chain

GET /repos/{name}/-/chain

Get commits in a sequence range for local hash-chain verification.

Query parameters: - from — Start sequence (inclusive). - to — End sequence (inclusive).

Releases

POST /repos/{name}/-/releases

Create a release. Maintainer-only.

Body:

{"name": "v1.0.0", "sequence": 43, "notes": "First release"}

GET /repos/{name}/-/releases

List releases in reverse chronological order.

GET /repos/{name}/-/releases/{name}

Get a release.

DELETE /repos/{name}/-/releases/{name}

Delete a release. Admin-only.

Purge

POST /repos/{name}/-/purge

Delete merged/abandoned branches and their commits older than a cutoff. Admin-only.

Body:

{"older_than": "720h"}

Events

GET /repos/{name}/-/events

Server-Sent Events stream for a repo. Emits events on commits, merges, reviews, and check runs.

GET /events

Global SSE stream. Admin-only.

Event subscriptions (webhooks)

See docs/events.md for a full guide covering event types, SSE streaming, HMAC signing, and the retry/suspend policy.

POST /subscriptions

Create a webhook subscription.

Authorization: - repo field set → Reader+ on the named repo. - repo field omitted → Global admin only.

Body:

{
  "repo": "acme/platform",
  "event_types": ["com.docstore.commit.created"],
  "backend": "webhook",
  "config": {"url": "https://...", "secret": "hmac-secret"}
}
  • repo — Optional. Omit to create a global subscription that receives events from all repos.
  • event_types — Optional. Omit to receive all event types.
  • config.secret — Optional. When set, each delivery includes an X-DocStore-Signature: sha256=<hmac> header.

Response 201: EventSubscription object.

GET /subscriptions

List subscriptions. Global admins see all subscriptions; non-admin authenticated users see only their own.

Response 200:

{"subscriptions": [...]}

A non-null suspended_at field means the subscription was automatically suspended after 10 failed deliveries and must be resumed manually.

DELETE /subscriptions/{id}

Delete a subscription. Global admin or the subscription creator.

Response 204.

POST /subscriptions/{id}/resume

Resume a suspended subscription. Global admin or the subscription creator. Clears suspended_at and resets failure_count to 0.

Response 204.

OIDC Identity Endpoints (oidc.docstore.dev)

These endpoints are served by the ci-oidc-public Cloud Run service at oidc.docstore.dev. They require no authentication.

GET /.well-known/openid-configuration

Returns the OIDC discovery document for the https://oidc.docstore.dev issuer.

Response 200:

{
  "issuer": "https://oidc.docstore.dev",
  "jwks_uri": "https://oidc.docstore.dev/.well-known/jwks.json",
  "response_types_supported": ["id_token"],
  "subject_types_supported": ["public"],
  "id_token_signing_alg_values_supported": ["RS256"]
}

GET /.well-known/jwks.json

Returns the JSON Web Key Set containing the RSA public key used to sign CI job JWTs. Use this to verify job JWT signatures without trusting DocStore's internal state.

External systems (GCP Workload Identity Federation, AWS STS, HashiCorp Vault) can use this endpoint to exchange job JWTs for cloud provider credentials.

CI OIDC Token Endpoint (internal)

This endpoint is served by the ci-oidc Cloud Run service. It is accessible from GKE worker pods via VPC (--ingress=internal) but not from the public internet.

POST /ci/token

Exchange a request_token for a short-lived job OIDC JWT.

Auth: Authorization: Bearer {request_token} (the one-time token returned by ci-scheduler's POST /claim).

Body:

{"audience": "docstore", "check_name": "ci/build"}
  • audience — Required. The JWT aud claim. Use "docstore" for docstore API access.
  • check_name — Optional. Included in the JWT sub claim as repo:{repo}:branch:{branch}:check:{check_name}.

Response 200:

{"token": "eyJ..."}

The returned JWT has a 1-hour lifetime and is signed with RS256 via Cloud KMS. Each call produces a unique JWT (unique jti) recorded in the ci_oidc_tokens audit table.

Response 401 if the request_token is missing, invalid, or expired.


Raw markdown — machine-readable source for this page.