Docs menu

HTTP API reference

.md

The JSON API under https://oak.space/api that the CLI is built on.

oak.space has a JSON API โ€” the same one the oak CLI is built on. Use it to script repositories, branches, merges, CI, and organizations, or to wire Oak into your own tools and agents.

Basics#

Base URL: https://oak.space/api. Paths below are relative to it.

Authentication: send a token as a bearer header. Personal API keys, service-account keys, and oak login sessions all work โ€” see API keys and tokens.

curl https://oak.space/api/repos -H "Authorization: Bearer $OAK_API_KEY"

Requests without a token can read public resources only.

Format: request and response bodies are JSON (Content-Type: application/json). {owner} is an organization slug (including personal organizations, which are named after their user).

Errors use the usual status codes โ€” 400 bad request, 401 not signed in, 403 signed in but not allowed (including a key missing a scope), 404 not found (also returned for things you can't see), 409 conflict, 412 precondition failed (the CI gate), 503 temporarily unavailable โ€” with a body like:

{ "error": "human-readable message" }

Some errors add a machine-readable code and retryable: true.

Account and keys#

MethodPathDescription
GET/whoamiThe user (or service account) behind the token.
GET/versionServer version.
GET/api-keysYour personal API keys (prefixes only).
POST/api-keysCreate a key: {"name", "scope": "full"|"read", "repo"?: "owner/name"}. Returns the full key once.
DELETE/api-keys/{id}Revoke a key.

Repositories#

MethodPathDescription
GET/repos?sort=name|updated|createdRepositories you can access.
POST/reposCreate one: {"name", "description"?, "is_public"?, "organization_slug"?}.
GET/{owner}/{name}A repository: name, visibility, head, and more.
DELETE/{owner}/{name}Delete a repository.
PATCH/{owner}/{name}/visibility{"is_public": true|false}.
POST/{owner}/{name}/transferMove it to another organization: {"to_organization": "slug"}.
GET/{owner}/{name}/sizeStorage the repository uses.

Branches#

MethodPathDescription
GET/{owner}/{name}/branchesList branches, with descriptions, heads, and status.
POST/{owner}/{name}/branchesCreate one: {"name", "description"?, "parent_branch"?}.
GET/{owner}/{name}/branches/{branch}One branch. Add ?record_read=1 to record that you've read it; the response includes who else has.
POST/{owner}/{name}/branches/{branch}/mergeSquash-merge onto the parent. See below.
POST/{owner}/{name}/branches/{branch}/syncMerge the parent into the branch. 409 with code: "branch_moved" (retryable) if it received new commits meanwhile.
POST/{owner}/{name}/branches/{branch}/closeClose a branch. Idempotent.
POST/{owner}/{name}/branches/{branch}/reopenReopen a closed branch.
POST/{owner}/{name}/branches/{branch}/renameTemporarily disabled โ€” returns 503.

Branch descriptions are set by pushing (oak desc then oak push); there's no separate endpoint.

Merge responses#

POST โ€ฆ/merge lands one squash commit on main whose message is the branch description.

StatusMeaning
200Merged. The body has commit_hash (the new commit on main) and merge_parent_hash (the branch tip).
412The CI gate: CI on the branch head is failing or running. Add ?force=1 to override (recorded in the audit log).
409 with conflict_pathsThe branch conflicts with main. Sync, resolve, and push.
409 with code: "main_moved" or "branch_moved"main or the branch changed during the merge. Nothing was merged; retry.
400 "already closed"The branch was already merged or closed.

Commits and content#

MethodPathDescription
POST/{owner}/{name}/commits/infoMetadata for a list of commit hashes.
POST/{owner}/{name}/commits/{hash}/revertLand a commit that undoes {hash} on its branch. 409 if the affected files have changed since.
GET/{owner}/{name}/tree/{commit}The root directory listing at a commit.
GET/{owner}/{name}/tree/{commit}/{path}A subdirectory listing.
GET/{owner}/{name}/raw/{commit}/{path}A file's bytes at a commit.

Outside the JSON API, GET https://oak.space/{owner}/{name}/download returns a zip of main, and git clone https://oak.space/{owner}/{name}.git a Git snapshot.

CI#

Reading needs read access to the repo; triggering, cancelling, and secrets need write access.

MethodPathDescription
GET/{owner}/{name}/ci/runs?limit=NRecent runs (default 50, max 200).
GET/{owner}/{name}/ci/runs/{id}One run, with its jobs, steps, exit codes, and logs.
POST/{owner}/{name}/ci/runsRun workflows at a branch head: {"workflow"?: "ci", "branch"?: "main"}. Returns {"run_ids": [...]}. Without workflow, every workflow runs.
POST/{owner}/{name}/ci/triggerExact-head, idempotent trigger (what oak ci trigger uses) โ€” see below.
POST/{owner}/{name}/ci/runs/{id}/cancelCancel a queued or running run.
GET/{owner}/{name}/ci/secretsSecret names and update times (never values).
PUT/{owner}/{name}/ci/secretsCreate or replace one: {"name", "value"}.
DELETE/{owner}/{name}/ci/secrets/{secret}Delete one.

The exact-head trigger only runs if the branch head is still the commit you name, and replays instead of duplicating when you retry with the same key:

{
  "protocol": "ordinary_trigger_v1",
  "expected_commit": "<full 64-character hash>",
  "idempotency_key": "deploy-check-1",
  "branch": "main",
  "workflow": "ci"
}

It returns {"run_ids", "replayed"}; 412 if the head moved, 409 if the key was used with a different request, and 503 with retryable: true when busy.

Run statuses go queued โ†’ running โ†’ completed, and a completed run's conclusion is success, failure, cancelled, timed_out, or skipped.

Path permissions#

MethodPathDescription
GET/{owner}/{name}/path-permissionsThe server's parse of .oak/PERMISSIONS on main: entries, principals, warnings, and whether enforcement is on.
POST/{owner}/{name}/path-permissions/proposePropose a new file as a branch: {"content": "...", "description"?: "..."}, or {"delete": true}. 403 if you may not change it.

Organizations#

MethodPathDescription
GET/orgsYour organizations.
POST/orgsCreate one: {"slug", "name", "description"?}.
GET/orgs/{slug}One organization.
PATCH/orgs/{slug}Update its name or description.
DELETE/orgs/{slug}Delete it.
GET/orgs/{slug}/membersMembers and roles.
POST/orgs/{slug}/membersAdd someone: {"username", "role"?: "member"|"admin"|"owner"}.
PATCH/orgs/{slug}/members/{username}Change a role: {"role"}.
DELETE/orgs/{slug}/members/{username}Remove a member.
GET/orgs/{slug}/groupsGroups.
POST/orgs/{slug}/groupsCreate a group: {"slug", "description"?}.
DELETE/orgs/{slug}/groups/{group}Delete a group.
POST/orgs/{slug}/groups/{group}/membersAdd a member: {"username"}.
DELETE/orgs/{slug}/groups/{group}/members/{username}Remove a member.
GET/orgs/{slug}/storageStorage used against the quota.
GET/orgs/{slug}/auditThe audit log.

Managing members, groups, and service accounts needs owner or admin.

Service accounts#

MethodPathDescription
GET/orgs/{slug}/service-accountsList them.
POST/orgs/{slug}/service-accountsCreate one: {"name", "display_name"?}.
PATCH/orgs/{slug}/service-accounts/{name}{"disabled": true|false}.
DELETE/orgs/{slug}/service-accounts/{name}Delete it.
GET/orgs/{slug}/service-accounts/{name}/keysIts keys.
POST/orgs/{slug}/service-accounts/{name}/keysCreate a key: {"name", "scopes": "read,write"}. Returns the key once.
DELETE/orgs/{slug}/service-accounts/{name}/keys/{key_id}Revoke a key.

Scopes are explained in API keys and tokens.

Webhooks#

MethodPathDescription
GET/{owner}/{name}/webhooksA repo's webhooks.
POST/{owner}/{name}/webhooksCreate one: {"url", "secret"?, "events"?: ["push", "branch.created", "merge"]}.
DELETE/{owner}/{name}/webhooks/{id}Delete one.

Payloads and signatures: Webhooks.

Sites#

MethodPathDescription
GET/orgs/{slug}/siteThe organization's site settings.
PUT/orgs/{slug}/siteEnable or update: {"repo", "source_dir"?}.
DELETE/orgs/{slug}/siteDisable it.
GET/sitesSites you can see.

CLI releases#

Public endpoints the installer and oak upgrade use to fetch and verify binaries.

MethodPathDescription
GET/releasesPublished CLI versions.
GET/releases/latestThe latest version.
GET/releases/{version}/{platform}Download a binary.
GET/releases/{version}/{platform}/sha256Its SHA-256.
GET/releases/{version}/{platform}/minisigIts minisign signature.

Sync protocol#

Push, pull, clone, and mount use content-addressed endpoints under /{owner}/{name}/ โ€” push, pull, blobs/*, and chunks/*. They're built for the CLI, which verifies every hash, and may change between versions; use the CLI rather than calling them directly.

Something here wrong or missing? Run oak feedback -m "โ€ฆ" or email [email protected].