Skip to content
API

API reference

The kobe HTTP API is served by the operator. All endpoints except /v1/status, /healthz, and /metrics require an Authorization header using a method the service advertises: a Bearer token for OIDC/static-token auth, or an SSH request signature.

Quick reference

MethodEndpointAuthDescription
POST/v1/leasesRequiredCreate a lease from a pool
GET/v1/leasesRequiredList your active leases
GET/v1/leases/:idRequiredGet a specific lease
DELETE/v1/leases/:idRequiredRelease a lease
PATCH/v1/leases/:idRequiredExtend a lease TTL
GET/v1/leases/:id/diagnosticsRequiredGet diagnostics bundle URL
GET/v1/poolsRequiredList available pools
GET/v1/pools/:nameRequiredGet a specific pool's status
GET/v1/pools/:name/leasesRequiredList leases for a specific pool
POST/v1/sandbox-leasesRequiredCreate a Sandbox lease from a SandboxPool
GET/v1/sandbox-leasesRequiredList your Sandbox leases
GET/v1/sandbox-leases/:idRequiredGet one Sandbox lease
PATCH/v1/sandbox-leases/:idRequiredExtend a Sandbox lease TTL
DELETE/v1/sandbox-leases/:idRequiredRequest Sandbox release
GET/v1/sandbox-leases/:id/logsRequiredRead the Sandbox log tail
POST/v1/sandbox-leases/:id/executionsRequiredRun or reserve an idempotent execution
GET/v1/sandbox-leases/:id/executions/:executionRequiredRead execution state
DELETE/v1/sandbox-leases/:id/executions/:executionRequiredCancel an execution
GET/v1/sandbox-leases/:id/executions/:execution/logsRequiredRead retained stdout/stderr windows
GET/v1/sandbox-leases/:id/attachRequiredUpgrade to an attach WebSocket
GET/v1/sandbox-leases/:id/port-forwardRequiredUpgrade to a declared-port WebSocket
GET/v1/statusOptionalEndpoint status, auth methods, pool summary
GET/healthzNoneLiveness probe
GET/metricsNonePrometheus metrics

POST /v1/leases

Creates a lease from the specified pool. Returns 202 Accepted immediately with the lease ID and Pending phase. Poll GET /v1/leases/:id until the phase is Bound and kubeconfig is populated.

Request body

{
  "profile": "ci-small",
  "ttl": "30m",
  "alias": "pr-3835",
  "metadata": {
    "github": {
      "actor": "lenij",
      "pullRequest": 3835,
      "runId": 123456789
    },
    "purpose": "e2e"
  }
}
FieldTypeRequiredDescription
profilestringYesPool name to lease from
ttlstringNoRequested TTL (e.g. "30m", "1h"). Defaults to the pool's spec.ttl. Capped by AccessPolicy maxTtl.
aliasstringNoDNS-label name unique among the requester's active leases.
metadataobjectNoOpaque caller context. Maximum 8 KiB encoded JSON, 32 top-level keys, four nesting levels, and 128 JSON values.

metadata is descriptive, untrusted, and creation-time only: the lease API does not expose an update operation. It never affects authorization, quota, priority, scheduling, or lifecycle behavior. Do not include credentials or secrets: the lease owner and cluster administrators can read it. Shared pool listings redact another tenant's metadata together with its requester identity and alias.

Response202 Accepted

{
  "id": "lease-a1b2c3d4e5f6",
  "phase": "Pending",
  "profile": "ci-small",
  "queue_position": 0,
  "effective_ttl": "30m",
  "alias": "pr-3835",
  "metadata": {
    "github": {
      "actor": "lenij",
      "pullRequest": 3835,
      "runId": 123456789
    },
    "purpose": "e2e"
  }
}

effective_ttl reports the TTL the server accepted after policy clamping.

Once bound, GET /v1/leases/:id returns:

{
  "id": "lease-a1b2c3d4e5f6",
  "phase": "Bound",
  "profile": "ci-small",
  "kubeconfig": "apiVersion: v1\nclusters:\n...",
  "expires_at": "2026-04-09T15:00:00Z"
}

Errors

StatusMeaning
400Invalid profile name, alias, TTL, or metadata
403Profile not allowed for your identity
429Concurrent lease limit reached, or server overloaded

GET /v1/leases

Lists all active leases belonging to your identity.

Response200 OK

[
  {
    "id": "lease-a1b2c3d4e5f6",
    "phase": "Bound",
    "profile": "ci-small",
    "expires_at": "2026-04-09T15:00:00Z"
  }
]

GET /v1/leases/:id

Returns the full lease, including kubeconfig once the phase is Bound.

Response200 OK

{
  "id": "lease-a1b2c3d4e5f6",
  "phase": "Bound",
  "profile": "ci-small",
  "kubeconfig": "apiVersion: v1\n...",
  "expires_at": "2026-04-09T15:00:00Z"
}

Possible values for phase: Pending, Bound, Released, Expired, Recycling, Quarantined.


DELETE /v1/leases/:id

Releases a lease immediately. The cluster is destroyed and a fresh replacement is queued. Returns 204 No Content. A lease already in Quarantined returns 409 Conflict with reason TeardownQuarantined; release cannot downgrade or discard incomplete cleanup evidence.


PATCH /v1/leases/:id

Extends a lease TTL. Only allowed up to maxExtensions times per lease (configured in AccessPolicy, default: 2).

Request body

{
  "extend_ttl": "30m"
}

Response200 OK

{
  "expires_at": "2026-04-09T16:00:00Z"
}

GET /v1/leases/:id/diagnostics

Returns a pre-signed S3 URL to the diagnostics bundle for the lease, if diagnostics are configured on the pool and a bundle was captured.

Returns 404 if no bundle exists.


GET /v1/pools

Lists every pool accessible to your identity, regardless of resource kind. resourceKind identifies what a lease allocates and capabilities identifies the operations available to the caller.

Response200 OK

[
  {
    "name": "ci-small",
    "resourceKind": "Cluster",
    "capabilities": ["lease", "kubeconfig", "extend", "release"],
    "phase": "Healthy",
    "ready": 3,
    "leased": 1,
    "creating": 0,
    "recycling": 0,
    "unhealthy": 0,
    "quarantined": 0,
    "queueDepth": 0,
    "policy": {
      "mode": "fixed",
      "ttl": "2h",
      "warmTarget": 3
    }
  }
]

GET /v1/pools/:name

Returns status for a single pool.

Response200 OK

{
  "name": "ci-small",
  "resourceKind": "Cluster",
  "capabilities": ["lease", "kubeconfig", "extend", "release"],
  "phase": "Healthy",
  "ready": 3,
  "leased": 1,
  "creating": 0,
  "recycling": 0,
  "unhealthy": 0,
  "quarantined": 0,
  "queueDepth": 0,
  "policy": {
    "mode": "fixed",
    "ttl": "2h",
    "warmTarget": 3
  }
}

GET /v1/pools/:name/leases

Lists leases currently associated with one pool. This is useful for operational visibility when debugging queueing, stuck leases, or tenant usage.

Response - 200 OK

[
  {
    "id": "lease-a1b2c3d4e5f6",
    "phase": "Bound",
    "profile": "ci-small",
    "cluster_name": "pool-ci-small-0",
    "requester": "github:org/repo",
    "expires_at": "2026-04-09T15:00:00Z"
  }
]

Sandbox leases

SandboxPool and SandboxLease are alpha APIs. Sandbox HTTP endpoints are mounted only when agentSandbox.mode=external and the operator-installed Agent Sandbox v0.5.6 APIs pass read-only compatibility validation. Startup writes no validation Claim. Disabled mode serves no Sandbox API, so it cannot admit a lease that no controller will reconcile.

Sandbox callers select an administrator-owned SandboxPool; they cannot send a Pod spec, namespace, RuntimeClass, environment values, mounts, PVCs, or target credentials. Responses never contain a management or child-cluster kubeconfig.

Sandbox error reasons

Every Sandbox denial body is { "error": string, "detail"?: string, "reason"?: string }. reason carries a bounded machine-readable code — the same condition reports the same code on the cluster and Sandbox paths (a quarantined release is teardown_quarantined everywhere) — so an agent or script can branch on why without parsing free text. It is omitted where no bounded reason exists. The closed set:

reasonRetryable
not_foundno
not_readyyes, once Ready or placed
expiredno — request a new lease
target_unresolvedyes, after placement completes
provenance_incompleteneeds operator attention
pool_unresolvableneeds operator attention
not_declaredno
ambiguous_aliasno — disambiguate first
backend_erroryes, transient
extension_budget_exhaustedno
max_ttl_ceilingno
expiry_derivation_mismatchno
conflict_retryableyes, against current state
teardown_quarantinedno — resolve cleanup first

POST /v1/sandbox-leases

Creates caller-safe Sandbox intent and returns 202 Accepted. The response includes Location: /v1/sandbox-leases/:id; poll that URL until the lease is ready or terminal.

{
  "pool": "agent-small",
  "ttl": "30m",
  "alias": "review-142",
  "idempotencyKey": "one-random-key-per-deliberate-create"
}

ttl defaults to the pool's defaultTtl and is capped by both the pool and the caller's Sandbox policy. alias is optional and must be unique among that identity's active Sandbox leases. Current clients should generate one random idempotencyKey (1–253 bytes) for each deliberate create and reuse it only while recovering that request. It remains optional for older clients.

{
  "id": "sandbox-a1b2c3d4e5f60718293a4b5c",
  "phase": "Pending",
  "pool": "agent-small",
  "ttl": "30m",
  "alias": "review-142",
  "provisioning_deadline": "2026-08-20T12:10:00Z"
}

When a requested TTL is reduced, effective_ttl reports the accepted value. provisioning_deadline is anchored to the API-server creation time and already includes queueing or controller downtime; retrying reconciliation never resets it.

If Kobe is shutting down or cannot safely classify a lost Kubernetes response, it preserves the same legacy fields and adds an explicit non-retry handoff:

{
  "id": "sandbox-a1b2c3d4e5f60718293a4b5c",
  "phase": "Pending",
  "pool": "agent-small",
  "ttl": "30m",
  "provisioning_deadline": "2026-08-20T12:10:00Z",
  "status": "admission_pending",
  "retry": false,
  "statusUrl": "/v1/sandbox-leases/sandbox-a1b2c3d4e5f60718293a4b5c"
}

This response is not confirmation that placement was accepted. Do not repeat the create: retain id and poll Location/statusUrl. The admission arbiter will either admit that exact lease or cancel it; a later 404 means cancellation won. phase: "Pending" remains present so older clients also keep polling the handle instead of treating it as placeable.

A keyed retry returns the same durable lease and Location. Reusing a key with different pool, TTL, or alias intent returns 409. If response headers arrived but the body was lost, recover with GET Location; do not repeat the POST. If the connection failed before headers, the same keyed POST may be repeated.

StatusMeaning
400Invalid pool name, alias, or TTL
403Missing Sandbox lease permission or resource ceiling exceeded
404An allowed SandboxPool does not exist
409Alias is already active, or an idempotency key is bound to different intent
429Sandbox concurrency limit reached, or the per-principal admission rate limit was hit
503Pool configuration or Kubernetes admission cannot be verified

Admission is rate limited per principal, independently of the concurrency limit. Each attempt that passes the limiter spends one token before admission work starts. An already-throttled request is refused without another charge or admission mutation. A keyed retry may perform one exact-name GET solely to recover an already committed lease; a miss remains 429. Rate-limit 429 includes Retry-After in seconds, while concurrency 429 does not. The limit is per API replica, and kobe_sandbox_admission_rate_limited_total counts every refusal.

GET /v1/sandbox-leases

Lists only leases owned by the authenticated identity and still permitted by its Sandbox pool scope. Requester identity and target credentials are omitted.

GET /v1/sandbox-leases/:id

Returns the typed lifecycle state for an owned lease. A lease belonging to another identity returns 404.

Possible phases are Pending, Provisioning, Ready, Releasing, Released, Expired, and Quarantined. Quarantined means cleanup is uncertain and the capacity remains unavailable; it is not a successful terminal state.

When teardown starts, release_cause is recorded once as Requested, RuntimeTtl, or ProvisioningDeadline. The first cause is immutable: a later release request cannot turn an already-expired lease into a caller-requested Released outcome. After cleanup is verified, both automatic causes finish as Expired and Requested finishes as Released; unverifiable cleanup finishes as Quarantined without returning capacity.

When resolved, the response may include non-secret placement and target object references with exact Kubernetes UIDs. It never includes bearer tokens or kubeconfigs.

PATCH /v1/sandbox-leases/:id

Extends the runtime TTL of a Ready lease. The body takes extendTtl (or extend_ttl, so one body shape works against the cluster endpoint too):

{ "extendTtl": "30m" }

The response reports the new expiry and the remaining budget:

{ "expiresAt": "2026-08-10T01:32:00Z", "extensionsCount": 1, "maxExtensions": 2 }

Authorized by the lease verb rather than one of its own: an extension can never move expiry past maxTtl measured from readiness, which is the same ceiling the caller could have requested at creation. Set maxExtensions: 0 on the Sandbox grant to forbid extension and require callers to commit up front.

Refusals are all 409 Conflict except a malformed or non-positive duration, which is 400 Bad Request: a lease that is not Ready, an exhausted extension budget, an extension that would exceed the ceiling, or a lease that changed concurrently — the last is safe to retry against the current lease.

DELETE /v1/sandbox-leases/:id

Requires the independent Sandbox release verb. For an admitted active lease, the API records server-owned release intent and returns 204 No Content; it does not write lifecycle status. The placement controller observes that intent, moves the lease to Releasing, revokes access, and verifies cleanup.

Repeated release of an already requested, Releasing, Released, or Expired lease returns 204 No Content. A Quarantined lease returns 409 Conflict: cleanup is still uncertain, so its capacity remains unavailable rather than being reported as released.

POST /v1/sandbox-leases/:id/executions

Reserves the execution before starting its process, so retrying the same request cannot run a command twice. idempotencyKey is required, contains 1–253 bytes, and must be new for each deliberate execution. Reusing it with different command, working directory, timeout, container, or detach intent returns 409 Conflict.

{
  "command": ["/agent", "analyse", "./repo"],
  "cwd": "/workspace",
  "timeout": "5m",
  "container": "agent",
  "idempotencyKey": "execution-018f8f4f",
  "detach": false
}

command is an argv array and is never interpreted by a shell. cwd and container are optional. timeout defaults to 60 seconds, is capped at one hour, and can never extend past the lease expiry.

Wait mode (detach: false) returns 200 OK after the supervised command ends:

{
  "id": "sbxe-a1b2c3d4e5f6",
  "state": "Succeeded",
  "exitCode": 0,
  "startedAt": "2026-08-20T12:01:00Z",
  "finishedAt": "2026-08-20T12:01:12Z",
  "stdout": "analysis complete\n",
  "stderr": "",
  "truncated": false
}

Detached mode returns 202 Accepted once the durable reservation exists. Poll the execution URL; a keyed retry returns the same execution rather than starting another process.

Execution states are Queued, Running, Succeeded, Failed, Cancelled, TimedOut, and Unknown. exitCode is present only when the remote process reported one. Unknown means Kobe could not establish the outcome and must not be treated as permission to rerun effects automatically.

Common failures are 400 for an invalid command, timeout, cwd, or idempotency key; 403/404 for unavailable lease access; 409 for key reuse with changed intent and for the per-lifetime execution bound (reason: execution_limit_exhausted — retrying cannot succeed, request a new lease); 501 when the pool has no Kobe runner; and 503 when the execution store or target cannot be verified.

GET /v1/sandbox-leases/:id/executions/:execution

Returns the current execution object in the same response shape, without inline stdout/stderr. It returns 404 unless both the lease and execution belong to the authenticated caller and exact lease instance.

GET /v1/sandbox-leases/:id/executions/:execution/logs

Reads retained output with independent resume positions:

?stdoutOffset=0&stderrOffset=0
{
  "id": "sbxe-a1b2c3d4e5f6",
  "state": "Running",
  "stdout": {
    "data": "building\n",
    "nextOffset": 9,
    "more": true,
    "truncated": false
  },
  "stderr": {
    "data": "",
    "nextOffset": 0,
    "more": false,
    "truncated": false
  }
}

Pass each returned nextOffset unchanged on the next request. Offsets count the runner's retained raw bytes and are opaque resume tokens; do not derive them from the length of data. The API represents each returned window as a string, replacing invalid UTF-8 when necessary. more means another window is already available. truncated means earlier or excess output was irreversibly dropped at the retention cap.

A follower is drained only when the execution state is terminal and both windows report more: false. Transport/body retry uses the same offsets so it cannot duplicate an already acknowledged window. 401 and 403 are terminal authorization results, not retry signals.

DELETE /v1/sandbox-leases/:id/executions/:execution

Requests cancellation and returns 200 OK with the state that actually applies. Runner-managed execution cancellation signals the whole process group. An execution already terminal remains in that terminal state; the response does not claim it was cancelled after the fact. Repeating cancellation is safe.


GET /v1/status

Public endpoint — no auth required, but providing a token enriches the response with accessible pools.

Response200 OK

{
  "version": "0.1.0",
  "auth": {
    "methods": ["oidc", "ssh"],
    "sessions": []
  },
  "pools": [
    { "name": "ci-small", "ready": 3, "leased": 1, "creating": 0 }
  ]
}

This is the endpoint kobe status queries.

Available for:
Apple macOS logomacOSMicrosoft Windows logoWindowsLinux logoLinux
Download Kunobi