Skip to main content

State (orun remote state)

The state API is the plane the orun CLI talks to when a repository is linked to Orunbase: workspace links bind a git remote to a workspace + project, and everything under …/state/ coordinates runs, stores content-addressed objects and logs, and advances the catalog. You normally do not call these routes directly — orun cloud link, orun run, and CI runners drive them. See State plane overview for the model and the orun cloud CLI docs for the client-side workflow.

note

The state plane is rolling out. The route groups below are live behind the API edge; surfaces still land incrementally (the project-scoped catalog entity read-model, for example, currently returns 501).

Contract versioning

Project-scoped …/state/… routes enforce the wire contract's major version before any work:

HeaderBehavior
Orun-Contract-Version: <major>Client's contract major. Supported majors: 12. A missing header is tolerated (treated as current); an explicit unsupported major is rejected with 409 contract_version_unsupported and the supported range in error.details.

State-plane errors ride the standard error envelope with additional codes: already_claimed, lease_lost, deps_not_ready, run_terminal, object_missing, ref_conflict, contract_version_unsupported.

Route groups

CLI links (workspace ↔ git remote binding):

MethodPathPermissionDescription
POST/v1/organizations/{orgId}/cli/linksorg.cli.linkCreate a workspace link (creates the project when absent)
GET/v1/organizations/{orgId}/cli/linksorg.cli.linkList the workspace's active links (the repo allow-list)
GET/v1/cli/links/resolve?remoteUrl=— (authenticated)Workspaces/projects the caller may link for a remote — powers orun cloud link's picker
GET/v1/organizations/{orgId}/projects/{projectId}/cli/linksList a project's links (console)
DELETE/v1/organizations/{orgId}/projects/{projectId}/cli/links/{linkId}Unlink (console)

Run coordination and the object/catalog plane, all under /v1/organizations/{orgId}/projects/{projectId}/state/… (contract-version enforced):

GroupRoutesDescription
RunsPOST/GET …/state/runs, GET …/runs/{runId}, GET …/runs/{runId}/jobs, GET …/runs/{runId}/runnableCreate (idempotent by client-minted run ULID), list, and inspect runs and their job plans
Job coordinationPOST …/runs/{runId}/jobs/{jobId}:claim / :heartbeat / :complete, POST …/runs/{runId}:cancel, GET …/runs/{runId}/log, GET …/runs/{runId}/frontierLease-based claim/heartbeat/complete verbs plus the run event-log and frontier reads (see Claim and retry)
Job logsPOST/GET …/runs/{runId}/logs/{jobId}Chunked log append and assembled read
Run secrets resolvePOST …/runs/{runId}/secrets/resolveThe lease-bound, value-returning resolve — secret.value.use plus a live lease. See Run secrets resolve
Job output secretsPOST …/runs/{runUlid}/output-secretsThe lease-bound CI write channel — publish job-produced values as environment-rung secrets. See Job output secrets
Terraform stateGET/POST/DELETE/LOCK/UNLOCK …/state/tfstate/{component}/{environment}Terraform's native HTTP backend — raw Terraform wire shape, not the platform envelope; LOCK/UNLOCK are valid only on these paths (405 elsewhere). See Terraform state backend
ObjectsPOST …/state/objects/missing, PUT/GET …/state/objects/{digest}, GET …/state/objects, POST …/objects/{digest}/uploads (+ part PUT, POST …/complete)Content-addressed object plane: digest negotiation, digest-verified single-shot PUT, and the chunked-upload sub-protocol for large blobs
Catalog headsPUT/GET …/state/catalog/head, GET …/state/catalog/heads/historyAdvance and read the published catalog head
RefsGET …/state/refs?prefix=, GET/PUT/DELETE …/state/refs/{name}Hosted RefStore — compare-and-swap ref updates (ref_conflict on a lost swap)
TriggersGET …/state/triggersInbound scm.* trigger activity feed
GCGET …/state/gc/report, POST …/state/gc/collectObject reachability report and safe-by-default (dry-run) reclamation

Workspace-scoped read models (no project segment, no contract-version gate):

MethodPathDescription
GET/v1/organizations/{orgId}/catalog/entitiesMerged org-wide catalog graph (filters: project, environment, kind, owner, q)
GET/v1/organizations/{orgId}/repo-facetsRepo self-description read model, org list
GET/v1/organizations/{orgId}/repo-facets/{projectId}One project's repo facet
GET/v1/organizations/{orgId}/state/usageCurrent state-plane storage footprint
GET/v1/organizations/{orgId}/state/runsOrg-global run feed across all projects (the console's Activities view)

Project-scoped state routes authorize with the state.run.read/state.run.write, state.object.read/state.object.write, secret.value.use, and catalog.read/catalog.publish policy actions (deny-by-default; workflow actors minted via OIDC exchange are granted within their token-bound workspace + project — see CI workflow grants).

Claim and retry

The claim body is { "runnerId": …, "retry": …, "hermetic": …, "jobInputHash": … } — everything but runnerId optional. hermetic + jobInputHash drive memoization; retry: true is the explicit re-attempt shape (orun run --job X --retry — what a CI rerun uses to resume one execution):

  • retry: true re-opens a failed or timed_out job only.
  • succeeded, memoized, and canceled stay terminal regardless — the claim answers { "ok": false, "reason": "terminal" } (cancellation is an operator decision, not a flake).
  • Dependencies still gate: a retry whose deps are not satisfied answers deps_not_ready.
  • A granted retry appends state.job.retried then state.job.claimed to the run event-log, with attempt incremented and leaseEpoch equal to the new attempt.
curl "https://api.orunbase.com/v1/cli/links/resolve?remoteUrl=github.com/acme/storefront" \
-H "Authorization: Bearer $ORUN_CLOUD_TOKEN"
{
"data": {
"candidates": [
{
"id": "wsl_8c7d6e5f4a3b20191817161514131211",
"orgId": "org_7c1f4b2a9d3e48f0a6b5c4d3e2f1a0b9",
"orgSlug": "acme",
"projectId": "prj_3e2d1c0b4a5968f7a6b5c4d3e2f1a0b9",
"projectSlug": "storefront",
"remoteUrl": "github.com/acme/storefront",
"provider": "github",
"providerRepoId": "456789123",
"providerOwnerId": "9876543",
"providerOwnerLogin": "acme",
"ciSettings": {
"oidcEnabled": true,
"apiKeyEnabled": true,
"allowedRefPattern": null,
"allowedEnvironments": null
},
"createdBy": { "id": "usr_3c2b1a0f9e8d7c6b5a49382716050403", "kind": "user" },
"createdAt": "2026-06-20T12:00:00.000Z",
"lastSeenAt": "2026-07-02T08:00:00.000Z"
}
],
"links": [ { "…": "same contents as candidates" } ]
},
"meta": { "requestId": "req_5f2d1c0b9a8e7f6d5c4b3a33", "cursor": null }
}

Creating a link (what orun cloud link does after the picker) takes the normalized remote and an optional projectSlug — the project is created on demand when absent:

curl -X POST https://api.orunbase.com/v1/organizations/org_7c1f4b2a9d3e48f0a6b5c4d3e2f1a0b9/cli/links \
-H "Authorization: Bearer $ORUN_CLOUD_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "remoteUrl": "github.com/acme/storefront", "projectSlug": "storefront" }'

With the SDK:

const { candidates } = await client.state.resolve("github.com/acme/storefront");

Read the storage footprint

GET …/state/usage returns the live storage stock for quota displays — distinct from metered flow metrics:

curl https://api.orunbase.com/v1/organizations/org_7c1f4b2a9d3e48f0a6b5c4d3e2f1a0b9/state/usage \
-H "Authorization: Bearer $ORUN_CLOUD_TOKEN"
{
"data": {
"usage": {
"objects": { "count": 1284, "bytes": 73400320 },
"logs": { "count": 412, "bytes": 9437184 }
}
},
"meta": { "requestId": "req_5f2d1c0b9a8e7f6d5c4b3a34", "cursor": null }
}