fluidbox Control Plane API (0.3.0)

Download OpenAPI specification:

Run AI coding agents in governed, disposable sandboxes.

fluidbox is a control plane that runs AI coding agents in governed, disposable sandboxes. You register a versioned agent definition; each run freezes an immutable RunSpec, provisions a fresh sandbox, streams a live event timeline, pauses for human approval (or auto-decides in autonomous mode), and ends with a diff and a cost report.

The four planes

This description covers every HTTP surface the control plane exposes. They are separate audiences with separate credentials, and mixing them up is the most common integration mistake:

Plane Base path Who calls it Credential
Public API /v1 Your code, the CLI, the dashboard Admin token, PAT, or browser session
Runner contract /internal Only the in-sandbox runner Audience-scoped session token
Operator /v1/admin Break-glass operator tooling Admin token only
Ingress /v1/ingress GitHub and other connected services Webhook signature

On Kubernetes the runner contract is served on a separate listener (:8788) and the /internal routes do not exist on the public listener at all — route absence is a stronger boundary than bearer auth. On the single-host Docker path both planes ride :8787 and the bearer token separates them.

Errors

Every failure across every plane returns the same body: a single error string. There are no per-endpoint error envelopes.

{ "error": "agent not found" }

Immutability

Two invariants shape most of this API and will surprise you if you miss them:

  • A RunSpec is frozen at run creation, including a full policy snapshot. Editing an agent or a policy affects only future runs. This is what makes the audit trail trustworthy.
  • Agents are append-only. "Editing" an agent means appending an agent revision; revisions are never mutated. A run uses the current revision's model and system prompt unless the trigger pins one.

Runs

A run is one governed execution of an agent. Creating a run freezes an immutable RunSpec — agent revision, policy snapshot, workspace, budgets, and the frozen capability surface — and everything downstream is judged against that snapshot rather than against current configuration.

List runs

Authorizations:
adminToken
query Parameters
limit
integer <int64>
Default: 50

Maximum items to return.

Responses

Response Schema: application/json
Array of objects (Session)

Response samples

Content type
application/json
{
  • "sessions": [
    ]
}

Start a run

Freezes an immutable RunSpec and begins provisioning. The response returns immediately with the new run in created; watch /v1/sessions/{id}/events/stream for progress.

What gets frozen: the agent revision's model and system prompt, a full policy snapshot, the resolved workspace, the effective budgets, and the capability surface after any per-run narrowing. Nothing you change afterwards affects this run.

Authorizations:
adminToken
header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
required
agent
required
string

The agent's name or id.

task
required
string

What to do this time. Distinct from the agent's system prompt.

Scratch (object) or Local copy (object) or Git repository (object) (WorkspaceInput)
autonomous
boolean
Default: false

Auto-decides approvals using the policy fallback. The permission gate stays wired either way — this is not a bypass, and both the original and the rewritten verdict are recorded.

object (Budgets)

Spending limits. Anything omitted falls back to the policy's value. Budgets can be tightened per run but never loosened past the policy.

capabilities
Array of strings

A keep-list of bundle names, intersected with the revision's attachments. Remove-only — it can never add a capability.

object

Explicit requirement-slot to connection-id overrides. Each entry is verified for tenant, caller permission, connector match, and snapshot before the run starts. Unknown slots are rejected.

Responses

Response Schema: application/json
object (Session)

A run. The run_spec is frozen at creation and is what actually governs the run — current agent and policy configuration is irrelevant to it.

Request samples

Content type
application/json
Example
{
  • "agent": "reviewer",
  • "task": "Summarize the OWASP top ten for a Rust web service."
}

Response samples

Content type
application/json
{
  • "session": {
    }
}

Get a run

Returns the run alongside its accumulated usage totals.

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The run (session) identifier.

Responses

Response Schema: application/json
object (Session)

A run. The run_spec is frozen at creation and is what actually governs the run — current agent and policy configuration is irrelevant to it.

object (UsageTotals)

Response samples

Content type
application/json
{
  • "session": {
    },
  • "usage": {
    }
}

Cancel a run

Records the intent to cancel and starts finalization. cancelled is false when the run was already terminal — that is a success, not an error.

Cancellation is deliberately stricter than run visibility: being able to see a run does not mean being able to stop it.

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The run (session) identifier.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Responses

Response Schema: application/json
cancelled
boolean

False when the run had already reached a terminal state.

Response samples

Content type
application/json
{
  • "cancelled": true
}

Get run cost

The metered cost of the run. Usage is teed off the streaming LLM response by the facade, so this is measured rather than estimated.

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The run (session) identifier.

Responses

Response Schema: application/json
input_tokens
integer <int64>
output_tokens
integer <int64>
cost_usd
number <double>
tool_calls
integer <int64>

Response samples

Content type
application/json
{
  • "input_tokens": 0,
  • "output_tokens": 0,
  • "cost_usd": 0.1,
  • "tool_calls": 0
}

List run artifacts

The outputs a finished run produced — most usefully, the diff.

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The run (session) identifier.

Responses

Response Schema: application/json
Array of objects (Artifact)

Response samples

Content type
application/json
{
  • "artifacts": [
    ]
}

Get one artifact

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The run (session) identifier.

aid
required
string

The artifact identifier.

Responses

Response Schema:
id
string
kind
string
size_bytes
integer <int64>
digest
string

Response samples

Content type
{
  • "id": "string",
  • "kind": "diff",
  • "size_bytes": 0,
  • "digest": "string"
}

List result deliveries for a run

Result delivery is decoupled from the run lifecycle: a failing webhook or a dead GitHub can never mutate a run. Deliveries are at-least-once and are retried with backoff from 5 seconds to 1 hour over 6 attempts — receivers must deduplicate on the x-fluidbox-delivery header.

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The run (session) identifier.

Responses

Response Schema: application/json
Array of objects (Delivery)

Response samples

Content type
application/json
{
  • "deliveries": [
    ]
}

Events

The append-only run timeline. Events carry a gapless per-session seq assigned under a row lock, which is what makes both catch-up polling and Last-Event-ID stream resume exact. Model prompts never reach the ledger — only digests, usage, and cost.

Read the run timeline

Returns events after seq. This same query backs the live stream, so polling and streaming are exact about each other — you can switch between them without gaps or duplicates.

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The run (session) identifier.

query Parameters
after
integer <int64>
Default: 0

Return events with seq strictly greater than this.

limit
integer <int64>
Default: 200

Responses

Response Schema: application/json
Array of objects (Event)

Response samples

Content type
application/json
{
  • "events": [
    ]
}

Stream the run timeline (SSE)

A text/event-stream of the run timeline.

Fanout is hybrid on purpose: a database NOTIFY is only a wakeup, and the seq catch-up query is the delivery source of truth. That makes the stream immune to missed notifications and to the database scaling to zero.

Send Last-Event-ID to resume exactly where you left off.

curl -N -H "Authorization: Bearer $FLUIDBOX_TOKEN" \
  "$FLUIDBOX_URL/v1/sessions/$RUN/events/stream"
Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The run (session) identifier.

header Parameters
Last-Event-ID
string

The last seq you processed. Delivery resumes after it.

Responses

Response Schema: text/event-stream
string

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Approvals

Human-in-the-loop decisions. Approvals are idempotent by (session_id, tool_call_id): the database row is the source of truth, so a runner retry after a restart re-attaches to the pending row rather than duplicating or hanging.

The approval inbox

Every approval currently waiting on a human decision.

Authorizations:
adminToken
query Parameters
limit
integer <int64>
Default: 50

Maximum items to return.

Responses

Response Schema: application/json
Array of objects (Approval)

Response samples

Content type
application/json
{
  • "approvals": [
    ]
}

List a run's approvals

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The run (session) identifier.

Responses

Response Schema: application/json
Array of objects (Approval)

Response samples

Content type
application/json
{
  • "approvals": [
    ]
}

Approve or deny a paused tool call

Unblocks a run waiting at the permission gate.

The decision is settled by a compare-and-swap, and the resulting approval.decided and tool.decision events are appended inside the deciding transaction — so only the CAS winner emits, and a double-submit produces exactly one decision and one pair of events.

Authority is not uniform: a call against a personal connection is decidable only by the owner who invoked it. There is no role, admin, or operator override, symmetric across approve and deny.

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The approval identifier.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
required
decision
required
string
Enum: "approved_once" "approved_session" "denied"

approved_once allows this single call. approved_session allows this tool for the rest of the run. denied refuses it and returns a tool error to the model.

Responses

Response Schema: application/json
id
string <uuid>
session_id
string <uuid>
tool_call_id
string
tool
string
status
string
Enum: "pending" "approved_once" "approved_session" "denied" "expired"
requested_at
string <date-time>
decided_at
string <date-time>
decided_by
string

Request samples

Content type
application/json
Example
{
  • "decision": "approved_once"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "session_id": "1ffd059c-17ea-40a8-8aef-70fd0307db82",
  • "tool_call_id": "string",
  • "tool": "string",
  • "status": "pending",
  • "requested_at": "2019-08-24T14:15:22Z",
  • "decided_at": "2019-08-24T14:15:22Z",
  • "decided_by": "string"
}

Agents

Versioned agent definitions. Append-only: an edit is a new revision. The system prompt lives on the revision (who the agent is); the task is supplied per run (what to do this time).

List agents

Authorizations:
adminToken
query Parameters
limit
integer <int64>
Default: 50

Maximum items to return.

Responses

Response Schema: application/json
Array of objects (Agent)

Response samples

Content type
application/json
{
  • "agents": [
    ]
}

Register an agent

Creates the agent and its first revision. The model, system prompt, policy, and budgets all live on the revision — to change any of them later, append a revision rather than mutating this one.

Authorizations:
adminToken
header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
required
name
required
string
description
string
harness
string

From /v1/harnesses. Defaults to the deployment default.

model
string
system_prompt
string

Who the agent is. Distinct from the per-run task.

policy
string

Policy name.

runner_image
string

Defaults to the configured sandbox image. Note that a seeded agent pins the image reference from its creation time.

object (Budgets)

Spending limits. Anything omitted falls back to the policy's value. Budgets can be tightened per run but never loosened past the policy.

Scratch (object) or Local copy (object) or Git repository (object) (WorkspaceInput)
capability_bundles
Array of strings

"name" pins the newest version as of now; "name@3" pins explicitly. Nothing floats — a pin taken today does not change under you tomorrow.

Array of objects (ConnectionRequirement)

Responses

Response Schema: application/json
object (Agent)

Request samples

Content type
application/json
Example
{
  • "name": "reviewer",
  • "system_prompt": "You review diffs for correctness and security."
}

Response samples

Content type
application/json
{
  • "agent": {
    }
}

Get an agent

Returns the agent with its revision history.

Authorizations:
adminToken
path Parameters
id
required
string

The agent's id or its name.

Responses

Response Schema: application/json
object (Agent)
Array of objects (AgentRevision)

Response samples

Content type
application/json
{
  • "agent": {
    },
  • "revisions": [
    ]
}

Append a revision

The only way to change an agent. Omitted fields inherit from the latest revision; an explicit empty array clears a list (this is how you drop every capability pin, and how a bundle upgrade lands — re-resolving "name" pins the newest version as of now).

In-flight runs are unaffected: they are governed by the snapshot they froze at creation.

Authorizations:
adminToken
path Parameters
id
required
string

The agent's id or its name.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
required
harness
string
model
string
system_prompt
string
policy
string
runner_image
string
object (Budgets)

Spending limits. Anything omitted falls back to the policy's value. Budgets can be tightened per run but never loosened past the policy.

Scratch (object) or Local copy (object) or Git repository (object) (WorkspaceInput)
capability_bundles
Array of strings
Array of objects (ConnectionRequirement)

Responses

Response Schema: application/json
object (AgentRevision)

Immutable once written.

Request samples

Content type
application/json
Example
{
  • "model": "claude-haiku-4-5"
}

Response samples

Content type
application/json
{
  • "revision": {
    }
}

Policies

The governance rules a run is judged against. Publishing is optimistically concurrent — you send the base_version you loaded, and a publish over a moved head is a 409 rather than a silent overwrite.

List policies

Authorizations:
adminToken

Responses

Response Schema: application/json
Array of objects (Policy)

Response samples

Content type
application/json
{
  • "policies": [
    ]
}

Create or replace a policy from YAML

Authorizations:
adminToken
header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
required
name
required
string
yaml
required
string

The policy document.

Responses

Response Schema: application/json
name
string
version
integer <int32>

The head version. Send this back as base_version when publishing.

content
object

The policy document as structure.

yaml
string

The document's YAML form, when it was authored as YAML.

summary
string

The review note recorded with this version.

published_by
string
created_at
string <date-time>

Request samples

Content type
application/json
{
  • "name": "string",
  • "yaml": "string"
}

Response samples

Content type
application/json
{
  • "name": "string",
  • "version": 0,
  • "content": { },
  • "yaml": "string",
  • "summary": "string",
  • "published_by": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

Get a policy

Authorizations:
adminToken
path Parameters
name
required
string

The policy name.

Responses

Response Schema: application/json
name
string
version
integer <int32>

The head version. Send this back as base_version when publishing.

content
object

The policy document as structure.

yaml
string

The document's YAML form, when it was authored as YAML.

summary
string

The review note recorded with this version.

published_by
string
created_at
string <date-time>

Response samples

Content type
application/json
{
  • "name": "string",
  • "version": 0,
  • "content": { },
  • "yaml": "string",
  • "summary": "string",
  • "published_by": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

Delete a policy

Runs that already froze this policy keep their snapshot and continue to be governed by it.

Authorizations:
adminToken
path Parameters
name
required
string

The policy name.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Responses

Response Schema: application/json
deleted
boolean

Response samples

Content type
application/json
{
  • "deleted": true
}

Get one historical policy version

Authorizations:
adminToken
path Parameters
name
required
string

The policy name.

version
required
integer <int32>

Responses

Response Schema: application/json
name
string
version
integer <int32>

The head version. Send this back as base_version when publishing.

content
object

The policy document as structure.

yaml
string

The document's YAML form, when it was authored as YAML.

summary
string

The review note recorded with this version.

published_by
string
created_at
string <date-time>

Response samples

Content type
application/json
{
  • "name": "string",
  • "version": 0,
  • "content": { },
  • "yaml": "string",
  • "summary": "string",
  • "published_by": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

Publish a policy draft

Publishes a new head version. base_version is the head you loaded the draft from — publishing over a moved head is a 409 rather than a silent overwrite of the other editor's intent.

That same 409 is what makes this safe to retry: a post-commit retry of your own publish also lands on 409, so a network failure cannot produce two versions.

Authorizations:
adminToken
path Parameters
name
required
string

The policy name.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
required
content
required
object

The whole draft as structure. The server validates it; the browser never resolves a verdict.

summary
required
string

What changed and why. Required and non-blank.

base_version
required
integer <int32>

Responses

Response Schema: application/json
name
string
version
integer <int32>

The head version. Send this back as base_version when publishing.

content
object

The policy document as structure.

yaml
string

The document's YAML form, when it was authored as YAML.

summary
string

The review note recorded with this version.

published_by
string
created_at
string <date-time>

Request samples

Content type
application/json
{
  • "content": { },
  • "summary": "string",
  • "base_version": 0
}

Response samples

Content type
application/json
{
  • "name": "string",
  • "version": 0,
  • "content": { },
  • "yaml": "string",
  • "summary": "string",
  • "published_by": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

Revert a policy to an earlier version

Publishes the content of version as a new head. History is never rewritten — a revert moves forward.

Authorizations:
adminToken
path Parameters
name
required
string

The policy name.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
required
version
required
integer <int32>

The version to restore.

base_version
required
integer <int32>

The head you were looking at. Same guard as publish.

Responses

Response Schema: application/json
name
string
version
integer <int32>

The head version. Send this back as base_version when publishing.

content
object

The policy document as structure.

yaml
string

The document's YAML form, when it was authored as YAML.

summary
string

The review note recorded with this version.

published_by
string
created_at
string <date-time>

Request samples

Content type
application/json
{
  • "version": 0,
  • "base_version": 0
}

Response samples

Content type
application/json
{
  • "name": "string",
  • "version": 0,
  • "content": { },
  • "yaml": "string",
  • "summary": "string",
  • "published_by": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

Validate policy YAML

Parses and checks a policy document without storing anything.

Authorizations:
adminToken
header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
required
yaml
required
string

Responses

Response Schema: application/json
valid
boolean
errors
Array of strings

Request samples

Content type
application/json
{
  • "yaml": "string"
}

Response samples

Content type
application/json
{
  • "valid": true,
  • "errors": [
    ]
}

Preview the effective permission matrix

Resolves a draft into the per-tool verdict matrix without publishing — this is how you see what a rule change will actually do before it governs a run.

Passing an existing policy's name folds that policy's agents' mcp__* tool roster into the matrix. A brand-new draft previews the canonical tool vocabulary only.

Authorizations:
adminToken
header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
required
content
required
object
name
string

An existing policy name, to fold in its agents' tools.

Responses

Response Schema: application/json
Array of objects

Request samples

Content type
application/json
{
  • "content": { },
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "matrix": [
    ]
}

Clone a policy

Creates a new policy from an existing one. Omitting from starts blank — an empty rule set under the fail-safe defaults, where everything asks a human.

Authorizations:
adminToken
header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
required
name
required
string

The new policy's name.

from
string

Source policy name. Omit to start blank.

from_version
integer <int32>

Pin the exact source version. Omit for its latest.

Responses

Response Schema: application/json
name
string
version
integer <int32>

The head version. Send this back as base_version when publishing.

content
object

The policy document as structure.

yaml
string

The document's YAML form, when it was authored as YAML.

summary
string

The review note recorded with this version.

published_by
string
created_at
string <date-time>

Request samples

Content type
application/json
{
  • "name": "string",
  • "from": "string",
  • "from_version": 0
}

Response samples

Content type
application/json
{
  • "name": "string",
  • "version": 0,
  • "content": { },
  • "yaml": "string",
  • "summary": "string",
  • "published_by": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

Triggers

A trigger subscription invokes an agent from outside the dashboard: an API call, a clock, or a connected-service event. A schedule is a trigger subscription with a clock attached, never a separate object.

List trigger subscriptions

Authorizations:
adminToken
query Parameters
limit
integer <int64>
Default: 50

Maximum items to return.

Responses

Response Schema: application/json
Array of objects (Trigger)

Response samples

Content type
application/json
{
  • "triggers": [
    ]
}

Create a trigger subscription

One object covers all three invocation shapes:

  • API — the default. Invoke it with the returned trigger token.
  • Schedule — attach schedule with a cron expression.
  • Event — attach connection to listen to a connected service.

schedule and connection are mutually exclusive.

The response includes the subscription's trigger token exactly once. Store it now; it is stored hashed and is never returned again. That token can invoke this one subscription and poll its runs — never the admin API, and the admin token can never invoke.

Authorizations:
adminToken
header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
required
agent
required
string
name
required
string
task_template
string
allow_task_override
boolean
Default: false
allow_workspace_override
boolean
Default: false
autonomous
boolean
Default: false
object (Budgets)

Spending limits. Anything omitted falls back to the policy's value. Budgets can be tightened per run but never loosened past the policy.

Scratch (object) or Local copy (object) or Git repository (object) (WorkspaceInput)
callback_url
string <uri>

A pre-registered signed-webhook destination. Destinations are approved at subscription time and can never be invented by a caller. Validated against the egress policy on save.

pinned_revision_id
string <uuid>

Pin runs to one revision. Omit to always use the latest.

concurrency_policy
string
Default: "allow"
Enum: "allow" "skip_if_running" "replace"
object (ScheduleInput)
connection
string

Listen to a connection's events. Mutually exclusive with schedule.

repositories
Array of strings

Only these repositories match. Omit for every repository the connection sees.

events
Array of strings

Omit for the connector's defaults — opened and reopened. synchronize is an explicit opt-in because it fires on every push.

publish
Array of strings
Items Enum: "pr_comment" "check"

Omit for ["pr_comment"]. An explicit [] publishes only to the dashboard and webhook. Results publish under the App identity — checks require it, and attribution rides in the content.

capabilities
Array of strings

Keep-list, intersected with the revision's attachments. Remove-only.

Responses

Response Schema: application/json
id
string <uuid>
name
string
agent_id
string <uuid>
trigger_kind
string
Enum: "api" "schedule" "event"
enabled
boolean
concurrency_policy
string
Enum: "allow" "skip_if_running" "replace"
callback_url
string <uri>
object (ScheduleInput)
next_fire_at
string <date-time>
created_at
string <date-time>
token
string

The subscription-scoped trigger token, shown once.

Request samples

Content type
application/json
Example
{}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978",
  • "trigger_kind": "api",
  • "enabled": true,
  • "concurrency_policy": "allow",
  • "callback_url": "http://example.com",
  • "schedule": {
    },
  • "next_fire_at": "2019-08-24T14:15:22Z",
  • "created_at": "2019-08-24T14:15:22Z",
  • "token": "fbx_trig_2f9c…"
}

Get a trigger subscription

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The trigger subscription identifier.

Responses

Response Schema: application/json
id
string <uuid>
name
string
agent_id
string <uuid>
trigger_kind
string
Enum: "api" "schedule" "event"
enabled
boolean
concurrency_policy
string
Enum: "allow" "skip_if_running" "replace"
callback_url
string <uri>
object (ScheduleInput)
next_fire_at
string <date-time>
created_at
string <date-time>

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978",
  • "trigger_kind": "api",
  • "enabled": true,
  • "concurrency_policy": "allow",
  • "callback_url": "http://example.com",
  • "schedule": {
    },
  • "next_fire_at": "2019-08-24T14:15:22Z",
  • "created_at": "2019-08-24T14:15:22Z"
}

Invoke a trigger

Starts a run through this subscription. Authenticate with the trigger token, not the admin token — the admin token can never invoke.

Overrides can only ever narrow authority, and only when the subscription opted in (allow_task_override, allow_workspace_override, both off by default). A workspace override must stay inside the subscription's existing repository and connection: it can pick a different ref or commit, never a new connection, clone URL, or local path.

Authorizations:
triggerToken
path Parameters
id
required
string <uuid>

The trigger subscription identifier.

Request Body schema: application/json
task
string

Requires allow_task_override on the subscription.

context
object

Arbitrary context frozen into the run's InvocationContext.

object

Requires allow_workspace_override. Narrowing only.

Responses

Response Schema: application/json
object (Session)

A run. The run_spec is frozen at creation and is what actually governs the run — current agent and policy configuration is irrelevant to it.

Request samples

Content type
application/json
Example
{ }

Response samples

Content type
application/json
{
  • "session": {
    }
}

Poll a run started by this subscription

The trigger token's read side. Polling is scoped to the subscription, not to the token: rotation replaces the credential, not the authority, so a replacement token can still poll runs created before the rotation.

Authorizations:
triggerToken
path Parameters
id
required
string <uuid>

The trigger subscription identifier.

sid
required
string <uuid>

The run identifier.

Responses

Response Schema: application/json
object (Session)

A run. The run_spec is frozen at creation and is what actually governs the run — current agent and policy configuration is irrelevant to it.

Response samples

Content type
application/json
{
  • "session": {
    }
}

Enable a subscription

A disabled subscription's schedule does not advance while it is off, so re-enabling goes through the missed-run path — with the default skip policy that records exactly one skip row rather than firing a backlog.

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The trigger subscription identifier.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Responses

Response Schema: application/json
id
string <uuid>
name
string
agent_id
string <uuid>
trigger_kind
string
Enum: "api" "schedule" "event"
enabled
boolean
concurrency_policy
string
Enum: "allow" "skip_if_running" "replace"
callback_url
string <uri>
object (ScheduleInput)
next_fire_at
string <date-time>
created_at
string <date-time>

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978",
  • "trigger_kind": "api",
  • "enabled": true,
  • "concurrency_policy": "allow",
  • "callback_url": "http://example.com",
  • "schedule": {
    },
  • "next_fire_at": "2019-08-24T14:15:22Z",
  • "created_at": "2019-08-24T14:15:22Z"
}

Disable a subscription

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The trigger subscription identifier.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Responses

Response Schema: application/json
id
string <uuid>
name
string
agent_id
string <uuid>
trigger_kind
string
Enum: "api" "schedule" "event"
enabled
boolean
concurrency_policy
string
Enum: "allow" "skip_if_running" "replace"
callback_url
string <uri>
object (ScheduleInput)
next_fire_at
string <date-time>
created_at
string <date-time>

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978",
  • "trigger_kind": "api",
  • "enabled": true,
  • "concurrency_policy": "allow",
  • "callback_url": "http://example.com",
  • "schedule": {
    },
  • "next_fire_at": "2019-08-24T14:15:22Z",
  • "created_at": "2019-08-24T14:15:22Z"
}

Rotate the trigger token

Mints a replacement and retires the old one. The new token is returned exactly once. Rotation replaces the credential, not the authority — the replacement still polls runs created before it existed.

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The trigger subscription identifier.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Responses

Response Schema: application/json
token
string

Response samples

Content type
application/json
{
  • "token": "string"
}

Connections

Custodied credentials for external services. The credential is sealed at rest and is only ever used control-plane-side — it never enters a sandbox.

List connections

Personal connections are visible only to their owner — administrators are excluded by design.

Authorizations:
adminToken

Responses

Response Schema: application/json
Array of objects (Connection)

Response samples

Content type
application/json
{
  • "connections": [
    ]
}

Create a connection

Custodies a credential for an external service. Every secret in this request is consumed here, sealed at rest, and never returned by any endpoint.

Requires FLUIDBOX_CREDENTIAL_KEY to be configured — without it the server boots fine but connections are disabled.

For MCP servers with auth_kind: oauth, the connection starts pending; run /v1/connections/{id}/oauth/start to complete it.

Authorizations:
adminToken
header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
required
provider
required
string
token
string <password>

Personal-access-token flavor.

app_id
string
installation_id
string
private_key
string <password>
webhook_secret
string <password>
display_name
string
base_url
string <uri>

For mcp_http, the base URL its credential is audience-bound to. Validated against the egress policy on save.

header_name
string
Default: "authorization"
scheme
string
Default: "Bearer"

Bearer (default), Basic for base64(email:token), or "" for a bare token.

auth_kind
string
Default: "static"
Enum: "static" "oauth" "none"
scopes
Array of strings
client_id
string
client_secret
string <password>
owner
string
Default: "organization"
Enum: "organization" "personal"

organization is visible to every member and requires an admin or owner role. personal is one member's private custody, allowed for any signed-in member — and administrators are excluded from it by design.

Responses

Response Schema: application/json
id
string <uuid>
provider
string
display_name
string
status
string
Enum: "pending" "active" "error" "revoked"

error is terminal until a reconnect — most often an invalid_grant from the authorization server. Run creation, photographing, and brokering all fail closed off this field.

auth_kind
string
Enum: "static" "oauth" "none"
owner_type
string
Enum: "organization" "personal"
base_url
string <uri>
authorization_generation
integer

Bumped when an ever-activated OAuth connection is reconnected. Bindings from an earlier generation refuse mid-run. Ordinary token rotation within a generation is fine, and GitHub App lifecycle never bumps it.

created_at
string <date-time>

Request samples

Content type
application/json
Example
{
  • "provider": "github",
  • "token": "ghp_xxxxxxxxxxxxxxxxxxxx",
  • "display_name": "acme org"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "provider": "string",
  • "display_name": "string",
  • "status": "pending",
  • "auth_kind": "static",
  • "owner_type": "organization",
  • "base_url": "http://example.com",
  • "authorization_generation": 0,
  • "created_at": "2019-08-24T14:15:22Z"
}

Approve a pending connection

Admin intent for a connection that GitHub-initiated discovery created as pending. Approving a previously revoked installation revives the same connection id, which keeps its dedup history continuous.

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The connection identifier.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Responses

Response Schema: application/json
id
string <uuid>
provider
string
display_name
string
status
string
Enum: "pending" "active" "error" "revoked"

error is terminal until a reconnect — most often an invalid_grant from the authorization server. Run creation, photographing, and brokering all fail closed off this field.

auth_kind
string
Enum: "static" "oauth" "none"
owner_type
string
Enum: "organization" "personal"
base_url
string <uri>
authorization_generation
integer

Bumped when an ever-activated OAuth connection is reconnected. Bindings from an earlier generation refuse mid-run. Ordinary token rotation within a generation is fine, and GitHub App lifecycle never bumps it.

created_at
string <date-time>

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "provider": "string",
  • "display_name": "string",
  • "status": "pending",
  • "auth_kind": "static",
  • "owner_type": "organization",
  • "base_url": "http://example.com",
  • "authorization_generation": 0,
  • "created_at": "2019-08-24T14:15:22Z"
}

Revoke a connection

Marks the connection revoked and evicts any cached tokens. In-flight runs holding a binding to it fail closed on their next brokered call — every credential access re-verifies status before the secret is touched.

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The connection identifier.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Responses

Response Schema: application/json
id
string <uuid>
provider
string
display_name
string
status
string
Enum: "pending" "active" "error" "revoked"

error is terminal until a reconnect — most often an invalid_grant from the authorization server. Run creation, photographing, and brokering all fail closed off this field.

auth_kind
string
Enum: "static" "oauth" "none"
owner_type
string
Enum: "organization" "personal"
base_url
string <uri>
authorization_generation
integer

Bumped when an ever-activated OAuth connection is reconnected. Bindings from an earlier generation refuse mid-run. Ordinary token rotation within a generation is fine, and GitHub App lifecycle never bumps it.

created_at
string <date-time>

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "provider": "string",
  • "display_name": "string",
  • "status": "pending",
  • "auth_kind": "static",
  • "owner_type": "organization",
  • "base_url": "http://example.com",
  • "authorization_generation": 0,
  • "created_at": "2019-08-24T14:15:22Z"
}

Begin the OAuth connect flow

Returns only a go_url. Navigate a browser to it — that navigation is what sets the flow cookie and claims the one-time flow row, with the browser hash inside the atomic single-use predicate. A leaked authorization URL can therefore neither complete nor burn a flow.

The flow freezes the authorization and token endpoints, the resolved client, the resource parameter, and the PKCE verifier at start, so the callback exchanges against the frozen row rather than re-discovering anything.

The go_url itself is transferable — whichever browser opens it becomes the initiating browser. Treat it as a secret and hand it straight to the intended user.

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The connection identifier.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Responses

Response Schema: application/json
go_url
string <uri>

Response samples

Content type
application/json
{}

Read the photographed tool surface

The latest append-only snapshot of the tools this connection exposes, taken by a forced initialize handshake at connect time.

A run freezes the snapshot it saw. If the upstream server later changes its tools, calls against the drifted set are denied rather than silently re-negotiated — re-photograph with /tools/refresh and start a new run.

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The connection identifier.

Responses

Response Schema: application/json
connection_id
string <uuid>
version
integer
protocol_version
string

The negotiated MCP version. At call time this must match the frozen surface exactly — drift denies the call rather than silently re-negotiating.

Array of objects (Tool)
digest
string
created_at
string <date-time>

Response samples

Content type
application/json
{
  • "connection_id": "d3547de1-d1f2-4344-b4c2-17169b7526f9",
  • "version": 0,
  • "protocol_version": "2025-11-25",
  • "tools": [
    ],
  • "digest": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

Re-photograph the tool surface

Takes a new snapshot. Existing runs keep the surface they froze; only runs created after this call see the new one.

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The connection identifier.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Responses

Response Schema: application/json
connection_id
string <uuid>
version
integer
protocol_version
string

The negotiated MCP version. At call time this must match the frozen surface exactly — drift denies the call rather than silently re-negotiating.

Array of objects (Tool)
digest
string
created_at
string <date-time>

Response samples

Content type
application/json
{
  • "connection_id": "d3547de1-d1f2-4344-b4c2-17169b7526f9",
  • "version": 0,
  • "protocol_version": "2025-11-25",
  • "tools": [
    ],
  • "digest": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

List repositories a connection can see

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The connection identifier.

query Parameters
page
integer
Default: 1
per_page
integer
Default: 30

Responses

Response Schema: application/json
Array of objects

Response samples

Content type
application/json
{
  • "repositories": [
    ]
}

List inbound webhook deliveries

What this connection received and what it fanned out to. Deliveries are deduplicated at two levels, which is what makes a webhook retry heal a partial fan-out rather than duplicate runs or comments.

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The connection identifier.

query Parameters
limit
integer <int64>
Default: 50

Maximum items to return.

Responses

Response Schema: application/json
deliveries
Array of objects

Response samples

Content type
application/json
{
  • "deliveries": [
    ]
}

Catalog

Untrusted reference data describing connectors you can connect to. The catalog suggests; the permission gate decides.

List connector catalog entries

Reference data only. Curated and imported entries are deployment-global; custom entries are tenant-scoped, and a tenant entry shadows a global one with the same slug.

tool_hints are display defaults — they are policy seeds, never decisions. The permission gate stays the judge.

Authorizations:
adminToken

Responses

Response Schema: application/json
Array of objects (CatalogEntry)

Response samples

Content type
application/json
{
  • "entries": [
    ]
}

Add a custom catalog entry

Custom entries are forced to tier: custom. Requires an admin or owner role.

Authorizations:
adminToken
header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
required
slug
required
string
name
required
string
icon
string
description
string
categories
Array of strings
url
string <uri>
transport
string
auth_mode
string
Enum: "none" "api_key" "oauth"
auth_hints
object
scopes
Array of strings
egress
object
tool_hints
object
sandbox_launch
object

Responses

Response Schema: application/json
slug
string
name
string
icon
string
description
string
tier
string
Enum: "curated" "imported" "custom"
url
string <uri>
transport
string
auth_mode
string
Enum: "none" "api_key" "oauth"
auth_hints
object
scopes
Array of strings
tool_hints
object

Policy-default seeds for display. The gate stays the judge.

Request samples

Content type
application/json
{
  • "slug": "string",
  • "name": "string",
  • "icon": "string",
  • "description": "string",
  • "categories": [
    ],
  • "transport": "string",
  • "auth_mode": "none",
  • "auth_hints": { },
  • "scopes": [
    ],
  • "egress": { },
  • "tool_hints": { },
  • "sandbox_launch": { }
}

Response samples

Content type
application/json
{
  • "slug": "string",
  • "name": "string",
  • "icon": "string",
  • "description": "string",
  • "tier": "curated",
  • "transport": "string",
  • "auth_mode": "none",
  • "auth_hints": { },
  • "scopes": [
    ],
  • "tool_hints": { }
}

Get a catalog entry

Authorizations:
adminToken
path Parameters
slug
required
string

The connector slug.

Responses

Response Schema: application/json
slug
string
name
string
icon
string
description
string
tier
string
Enum: "curated" "imported" "custom"
url
string <uri>
transport
string
auth_mode
string
Enum: "none" "api_key" "oauth"
auth_hints
object
scopes
Array of strings
tool_hints
object

Policy-default seeds for display. The gate stays the judge.

Response samples

Content type
application/json
{
  • "slug": "string",
  • "name": "string",
  • "icon": "string",
  • "description": "string",
  • "tier": "curated",
  • "transport": "string",
  • "auth_mode": "none",
  • "auth_hints": { },
  • "scopes": [
    ],
  • "tool_hints": { }
}

Connect a catalog entry

The one-call path from catalog entry to usable connection.

  • api_key entries seal the token and immediately photograph the tool surface. If the photograph is refused, the whole connect rolls back.
  • oauth entries return a go_url and photograph on callback.
  • In-image (none) entries register a sandbox capability bundle instead — no credential is involved.
Authorizations:
adminToken
path Parameters
slug
required
string

The connector slug.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
display_name
string
token
string <password>

For api_key entries. Basic-composite connectors take email:api_token — check the entry's auth_hints.

bundle_name
string
client_id
string
client_secret
string <password>
scopes
Array of strings
owner
string
Default: "organization"
Enum: "organization" "personal"

Responses

Response Schema: application/json
object (Connection)
go_url
string <uri>

Present for OAuth entries. Navigate a browser to it.

Request samples

Content type
application/json
Example
{
  • "token": "sk-xxxxxxxx",
  • "display_name": "Acme production"
}

Response samples

Content type
application/json
{
  • "connection": {
    },
  • "go_url": "http://example.com"
}

Probe an MCP server

Paste a URL, detect its authentication mode, and preview its tools — without committing anything. Nothing is stored.

Authorizations:
adminToken
header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
required
url
required
string <uri>

Responses

Response Schema: application/json
auth_mode
string
Enum: "none" "api_key" "oauth"
protocol_version
string
Array of objects (Tool)

Request samples

Content type
application/json

Response samples

Content type
application/json
{
  • "auth_mode": "none",
  • "protocol_version": "string",
  • "tools": [
    ]
}

Bring your own MCP server

Creates a tier: custom catalog entry and connects it in one call. The catalog entry stays organization reference data regardless of owner — only the resulting connection carries personal custody.

Authorizations:
adminToken
header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
required
url
required
string <uri>
name
required
string
auth_mode
string
Default: "none"
Enum: "none" "api_key" "oauth"
token
string <password>
display_name
string
icon
string
description
string
header_name
string
Default: "authorization"
scheme
string
Default: "Bearer"
scopes
Array of strings
client_id
string
client_secret
string <password>
owner
string
Default: "organization"
Enum: "organization" "personal"

Responses

Response Schema: application/json
object (CatalogEntry)
object (Connection)
go_url
string <uri>

Request samples

Content type
application/json
{
  • "name": "acme-tools",
  • "auth_mode": "api_key",
  • "token": "sk-xxxxxxxx",
  • "header_name": "authorization",
  • "scheme": "Bearer"
}

Response samples

Content type
application/json
{
  • "entry": {
    },
  • "connection": {
    },
  • "go_url": "http://example.com"
}

Capabilities

Sandbox-class MCP servers — credential-free stdio subprocesses packaged in the runner image. Brokered (credentialed) servers are not capability bundles; they are connections.

List capability bundles

Authorizations:
adminToken

Responses

Response Schema: application/json
Array of objects (CapabilityBundle)

Response samples

Content type
application/json
{
  • "bundles": [
    ]
}

Register a capability bundle

Bundles describe sandbox-class MCP servers only: credential-free stdio subprocesses packaged in the runner image, contained by the container.

A brokered (credentialed) server is refused here — those are connections, and the whole point of the split is that their credential never enters a sandbox.

The registry is append-only: re-registering a name publishes the next version.

Authorizations:
adminToken
header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
required
name
required
string
description
string
required
Array of objects (CapabilityServer)

Responses

Response Schema: application/json
id
string <uuid>
name
string
version
integer
description
string
Array of objects (CapabilityServer)
created_at
string <date-time>

Request samples

Content type
application/json
{
  • "name": "string",
  • "description": "string",
  • "servers": [
    ]
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "version": 0,
  • "description": "string",
  • "servers": [
    ],
  • "created_at": "2019-08-24T14:15:22Z"
}

Get a capability bundle

Authorizations:
adminToken
path Parameters
id
required
string

The bundle id, or name@version.

Responses

Response Schema: application/json
id
string <uuid>
name
string
version
integer
description
string
Array of objects (CapabilityServer)
created_at
string <date-time>

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "version": 0,
  • "description": "string",
  • "servers": [
    ],
  • "created_at": "2019-08-24T14:15:22Z"
}

Identity

Login, sessions, and personal access tokens. Multi-user identity is off by default and enabled with FLUIDBOX_REQUIRE_SSO=1.

The login entry page

The neutral, IdP-agnostic entry point. Unauthenticated by design.

query Parameters
org
string
redirect_to
string

Responses

Response Schema: text/html
string

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Begin OIDC login for an organization

Redirects to the organization's configured identity provider. The browser-bound one-time flow row and the sealed state are the authentication here — there is no bearer token to present.

path Parameters
slug
required
string

The organization slug.

query Parameters
redirect_to
string

Responses

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

OIDC callback

The one stable redirect URI for every organization. Unauthenticated by design — the sealed state plus the per-flow cookie are the authentication, the same pattern as webhook signatures.

query Parameters
code
string
state
string
error
string
error_description
string

Responses

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Who am I

Resolves the caller. Useful for confirming which of the three principal kinds you are actually authenticating as — operator, user, or PAT.

Authorizations:
sessionCookiepat

Responses

Response Schema: application/json
object
object
roles
Array of strings

Response samples

Content type
application/json
{
  • "user": {
    },
  • "org": {
    },
  • "roles": [
    ]
}

Sign out

Authorizations:
sessionCookie
header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Responses

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Confirm an organization switch

Authorizations:
sessionCookie
path Parameters
id
required
string <uuid>

The pending organization-switch identifier.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Responses

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

List personal access tokens

Metadata only — token values are stored as SHA-256 digests and cannot be read back.

Authorizations:
sessionCookie

Responses

Response Schema: application/json
Array of objects (PersonalAccessToken)

Response samples

Content type
application/json
{
  • "tokens": [
    ]
}

Mint a personal access token

Machine access without a browser flow. Requires a browser session — a PAT can never mint another PAT.

The token value is returned exactly once.

Authorizations:
sessionCookie
header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Request Body schema: application/json
required
name
required
string
expires_in
integer <int64>

Lifetime in seconds. Omit for the deployment default.

Responses

Response Schema: application/json
id
string <uuid>
name
string
created_at
string <date-time>
expires_at
string <date-time>
last_used_at
string <date-time>
token
string

Request samples

Content type
application/json
{
  • "name": "ci-runner",
  • "expires_in": 2592000
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "created_at": "2019-08-24T14:15:22Z",
  • "expires_at": "2019-08-24T14:15:22Z",
  • "last_used_at": "2019-08-24T14:15:22Z",
  • "token": "fbx_pat_9c2e…"
}

Revoke a personal access token

Authorizations:
sessionCookie
path Parameters
id
required
string <uuid>
header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Responses

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Connector OAuth boot leg

Sets the flow cookie and redirects to the authorization server. Unauthenticated by design — a browser redirect cannot carry a bearer token, so the sealed boot token plus the one-time flow claim are the authentication.

Navigate a browser here; do not call it programmatically.

query Parameters
f
required
string

The sealed boot token from oauth/start.

Responses

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Connector OAuth callback

The single redirect URI for every connector OAuth flow. The AEAD-sealed state carrying the connection id and PKCE verifier is the authentication.

The exchange runs against the endpoints frozen at start (closing authorization-server mix-up) and refuses a moved authorization generation. A successful exchange requires a refresh token.

query Parameters
code
string
state
string
error
string

Responses

Response Schema: text/html
string

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

GitHub

GitHub App registration, installation, and lifecycle.

List GitHub App registrations

A registration custodies an app identity — its private key, webhook secret, and client secret, all sealed. There is one registration per GitHub account or organization, because a private app installs only on its owner.

Authorizations:
adminToken

Responses

Response Schema: application/json
Array of objects (GithubAppRegistration)

Response samples

Content type
application/json
{
  • "registrations": [
    ]
}

Begin GitHub App creation

Mints a one-time flow and returns a go_url. Requires admin intent — activation is never something GitHub can initiate on its own.

Authorizations:
adminToken
header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Responses

Response Schema: application/json
go_url
string <uri>

Response samples

Content type
application/json
{}

GitHub App manifest form

Browser-facing. Posts the app manifest to GitHub.

Responses

Response Schema: text/html
string

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

GitHub App manifest callback

Exchanges the manifest code and seals the resulting app credentials.

query Parameters
code
string
state
string

Responses

Response Schema: text/html
string

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Begin installing the app

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The GitHub App registration identifier.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Responses

Response Schema: application/json
go_url
string <uri>

Response samples

Content type
application/json
{}

GitHub App install redirect

Responses

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

GitHub post-install setup landing

Where GitHub sends the browser after an installation. A state-less hit performs zero writes and zero GitHub callsinstallation_id from a query string is never trusted. Use sync or approve to record intent.

path Parameters
id
required
string <uuid>

The GitHub App registration identifier.

query Parameters
installation_id
string
state
string

Responses

Response Schema: text/html
string

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Reconcile installations against GitHub

Reconciles local state against GitHub's truth. Webhook ordering never wins over a sync — an installation id is only ever trusted after it resolves under our own app's JWT.

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The GitHub App registration identifier.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Responses

Response Schema: application/json
Array of objects (Connection)

Response samples

Content type
application/json
{
  • "connections": [
    ]
}

Revoke a registration

Cascades to every connection the registration custodies and evicts their cached tokens. Custody resolution is fail-closed: a connection whose registration is missing or inactive is refused rather than falling back to per-connection credentials.

Authorizations:
adminToken
path Parameters
id
required
string <uuid>

The GitHub App registration identifier.

header Parameters
x-fluidbox-csrf
string
Examples: 1

Required on non-safe methods when authenticating with the session cookie. Bearer principals are exempt. Send 1.

Responses

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Service

Health and metadata endpoints.

Liveness probe

Answers as soon as the process is serving. Does not touch the database.

Responses

Response Schema: application/json
status
string

Response samples

Content type
application/json
{
  • "status": "ok"
}

Readiness probe

Reports whether the control plane can serve real traffic, which includes reaching the database. Use this one for load-balancer readiness; use /v1/health for liveness.

Responses

Response Schema: application/json
status
string

Response samples

Content type
application/json
{
  • "status": "ready"
}

List supported harnesses and models

The single source of truth for which agent harnesses this deployment supports and which models each one accepts. The dashboard pickers read exactly this; build yours from it too rather than hardcoding names.

Authorizations:
adminToken

Responses

Response Schema: application/json
Array of objects

Response samples

Content type
application/json
{
  • "harnesses": [
    ]
}

OAuth client ID metadata document

The CIMD document (MCP spec 2025-11-25). This document's URL is the control plane's OAuth client_id — authorization servers fetch it, so it is public by nature.

It is only used when FLUIDBOX_PUBLIC_URL is HTTPS and non-loopback, because the authorization server has to be able to reach it. Local deployments always fall back to dynamic client registration.

Responses

Response Schema: application/json
object

Response samples

Content type
application/json
{ }

Runner contract

The contract every runner image implements. This is how a sandbox talks to the control plane, and implementing it is how you add a new agent harness.

The sandbox holds four audience-scoped tokens, not one bearer. Each guarded route checks the audience as its first statement and answers 403 {"error":"wrong_audience"} otherwise — that body code is load-bearing, because runners key their fatal abort on it.

Audience Routes
tool /permission, /tools/call
control /events, /heartbeat, /result, /token/renew
workspace /workspace
llm /llm/*

Ask permission for a tool call

The heart of the system. Every tool the agent wants to run comes through here, and the answer is authoritative.

The gate runs a fixed sequence, and the order is the security model:

  1. Budget — is there anything left to spend?
  2. Frozen-set availability — is this tool in the surface the run froze? A drifted or withdrawn tool denies with source=capability; a missing or stale binding denies with source=binding.
  3. Frozen schema — do the arguments validate against the schema frozen at run creation? Denies with source=schema.
  4. Trust tier — a read_only run (any fork pull request) is refused write and secret-reaching tools here, above policy and above human approval. There is no approval escape from this tier.
  5. Policy — the run's frozen policy snapshot.
  6. Approval — pause for a human, or auto-decide in autonomous mode.

The permission callback stays wired in both autonomy modes. An autonomous run rewrites a require_approval verdict to the policy fallback inside the evaluation and records both the original and the rewritten verdict — it never bypasses the gate.

Decisions are idempotent by (session_id, tool_call_id), so retrying after a restart re-attaches to the pending row.

Tool names must use the canonical vocabulary — Bash{command}, Edit/Write/MultiEdit{file_path}, Read/Glob/Grep/LS, and mcp__<server>__<tool>. Canonicalization is the runner's job.

Authorizations:
sessionToken
path Parameters
id
required
string <uuid>

The run (session) identifier.

Request Body schema: application/json
required
tool_call_id
required
string
tool
required
string
input
object

Responses

Response Schema: application/json
allow
boolean
verdict
string (Verdict)
Enum: "allow" "deny" "require_approval"

What the policy engine decided for a tool call.

source
string
Enum: "budget" "capability" "binding" "schema" "trust_tier" "policy" "approval"

Which gate stage produced a denial — useful for diagnosing why a call was refused.

reason
string

Request samples

Content type
application/json
{
  • "tool_call_id": "toolu_01ABC",
  • "tool": "Bash",
  • "input": {
    }
}

Response samples

Content type
application/json
{
  • "allow": true,
  • "verdict": "allow",
  • "source": "budget",
  • "reason": "string"
}

Invoke a brokered tool

Intent in, governed result out. The sealed credential turns server-side — it never enters the sandbox, the same inversion as the LLM facade and the credentialed git fetch.

Runners auto-allow brokered mcp__* calls in their own permission callback precisely because the broker runs the identical gate here.

Every dispatch is wrapped in a durable four-state execution claim keyed (session_id, tool_call_id, input_digest). A reused id with different arguments is a new claim, never an adoption. Only failed_before_send — which requires positive proof nothing was written — is re-claimable; a definitive upstream response is terminal.

The ledger records tool.requestedtool.decisiontool.brokered, carrying latency and a result digest, never payloads or secrets.

Authorizations:
sessionToken
path Parameters
id
required
string <uuid>

The run (session) identifier.

Request Body schema: application/json
required
tool_call_id
required
string
tool
required
string

The prefixed name, e.g. mcp__issues__create_issue.

input
object

Responses

Response Schema: application/json
ok
boolean
object

Request samples

Content type
application/json
{
  • "tool_call_id": "string",
  • "tool": "string",
  • "input": { }
}

Response samples

Content type
application/json
{
  • "ok": true,
  • "result": {
    }
}

Report a timeline event

Appends to the run ledger. The ledger only accepts redacted envelopes — model prompts never reach it, only digests, usage, and cost. Session tokens (fbx_sess_), web tokens, and PATs are all scrubbed by the redactor.

seq is assigned server-side, gaplessly, under a row lock.

Authorizations:
sessionToken
path Parameters
id
required
string <uuid>

The run (session) identifier.

Request Body schema: application/json
required
actor
required
string
body
required
object

Responses

Response Schema: application/json
seq
integer <int64>

Request samples

Content type
application/json
{
  • "actor": "string",
  • "body": { }
}

Response samples

Content type
application/json
{
  • "seq": 0
}

Report liveness

Keeps the watchdog satisfied. A run that stops heartbeating is reaped by the heartbeat worker — the server is the single status writer, and the runner only ever reports.

Authorizations:
sessionToken
path Parameters
id
required
string <uuid>

The run (session) identifier.

Responses

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Report the final outcome

The runner's last call. The server decides the terminal status from this report; the runner never writes status itself.

The audience is checked before the revoked-token leniency, so a revoked control token still acknowledges here — but an llm or tool token never does.

Authorizations:
sessionToken
path Parameters
id
required
string <uuid>

The run (session) identifier.

Request Body schema: application/json
required
outcome
required
string
summary
string

Responses

Request samples

Content type
application/json
{
  • "outcome": "completed",
  • "summary": "string"
}

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Fetch the workspace archive

The immutable workspace archive the Kubernetes init container pulls. Credential-free and digest-verified.

Workspace initialization is control-plane-side by design: the credentialed fetch happens in the orchestrator before the agent starts, so the original repository is never touched and the sandbox stays egress-free. The agent only ever sees a copy at /workspace.

Authorizations:
sessionToken
path Parameters
id
required
string <uuid>

The run (session) identifier.

Responses

Response Schema: application/octet-stream
string <binary>

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Renew a session token

Extends the calling token's lifetime for a long-running run.

Authorizations:
sessionToken
Request Body schema: application/json
ttl_secs
integer <int64>

Responses

Response Schema: application/json
token
string
expires_at
string <date-time>

Request samples

Content type
application/json
{
  • "ttl_secs": 0
}

Response samples

Content type
application/json
{
  • "token": "string",
  • "expires_at": "2019-08-24T14:15:22Z"
}

The LLM facade

The sandbox's ANTHROPIC_API_KEY is its session token — there is no real provider key inside a sandbox, ever.

The facade validates the token, enforces the budget stop, swaps in the real upstream credential, forwards to the gateway, and tees the streaming response to meter usage. It dispatches on the run's harness, speaking the Anthropic Messages dialect or the OpenAI Responses dialect as appropriate.

Admission books a durable, request-keyed reservation whose primary key becomes the usage entry's external id — which is what makes a retry and a late drain idempotent.

Authorizations:
sessionToken
path Parameters
rest
required
string

The provider path the harness appends, e.g. v1/messages. The Claude Agent SDK appends it to ANTHROPIC_BASE_URL.

Request Body schema: application/json
required
object

Responses

Response Schema:
object

Request samples

Content type
application/json
{ }

Response samples

Content type
{ }

Gateway usage callback

The LiteLLM usage callback. Called by the gateway, not by a runner.

Authorizations:
sessionToken
Request Body schema: application/json
required
object

Responses

Request samples

Content type
application/json
{ }

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Operator

Break-glass and deployment-lifecycle surfaces. Under FLUIDBOX_REQUIRE_SSO=1 this is the only surface the admin token reaches — everywhere else it is refused in favour of user sessions and PATs.

List organizations

Authorizations:
adminToken

Responses

Response Schema: application/json
Array of objects (Org)

Response samples

Content type
application/json
{
  • "orgs": [
    ]
}

Create an organization

Authorizations:
adminToken
Request Body schema: application/json
required
slug
required
string
name
string

Responses

Response Schema: application/json
id
string <uuid>
slug
string
name
string
created_at
string <date-time>

Request samples

Content type
application/json
{
  • "slug": "string",
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "slug": "string",
  • "name": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

List identity provider configurations

Authorizations:
adminToken
path Parameters
slug
required
string

The organization slug.

Responses

Response Schema: application/json
Array of objects (IdpConfig)

Response samples

Content type
application/json
{
  • "idps": [
    ]
}

Add an identity provider

Created inactive. Activate it explicitly once discovery has been verified — the whole lifecycle is deliberately multi-step so a misconfigured issuer cannot lock an organization out.

Authorizations:
adminToken
path Parameters
slug
required
string

The organization slug.

Request Body schema: application/json
required
issuer
required
string <uri>
client_id
required
string
client_secret
string <password>
scopes
Array of strings

Responses

Response Schema: application/json
id
string <uuid>
issuer
string <uri>
client_id
string
status
string
Enum: "inactive" "active" "disabled"
created_at
string <date-time>

Request samples

Content type
application/json
{
  • "issuer": "http://example.com",
  • "client_id": "string",
  • "client_secret": "pa$$word",
  • "scopes": [
    ]
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "issuer": "http://example.com",
  • "client_id": "string",
  • "status": "inactive",
  • "created_at": "2019-08-24T14:15:22Z"
}

Update an identity provider

Authorizations:
adminToken
path Parameters
slug
required
string

The organization slug.

id
required
string <uuid>

The identity provider configuration identifier.

Request Body schema: application/json
required
issuer
required
string <uri>
client_id
required
string
client_secret
string <password>
scopes
Array of strings

Responses

Response Schema: application/json
id
string <uuid>
issuer
string <uri>
client_id
string
status
string
Enum: "inactive" "active" "disabled"
created_at
string <date-time>

Request samples

Content type
application/json
{
  • "issuer": "http://example.com",
  • "client_id": "string",
  • "client_secret": "pa$$word",
  • "scopes": [
    ]
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "issuer": "http://example.com",
  • "client_id": "string",
  • "status": "inactive",
  • "created_at": "2019-08-24T14:15:22Z"
}

Activate an identity provider

Authorizations:
adminToken
path Parameters
slug
required
string

The organization slug.

id
required
string <uuid>

The identity provider configuration identifier.

Responses

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Disable an identity provider

Authorizations:
adminToken
path Parameters
slug
required
string

The organization slug.

id
required
string <uuid>

The identity provider configuration identifier.

Responses

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Reactivate an identity provider

Authorizations:
adminToken
path Parameters
slug
required
string

The organization slug.

id
required
string <uuid>

The identity provider configuration identifier.

Responses

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Migrate to a new issuer

Moves an organization's users to a new issuer without re-inviting them.

Authorizations:
adminToken
path Parameters
slug
required
string

The organization slug.

id
required
string <uuid>

The identity provider configuration identifier.

Request Body schema: application/json
required
issuer
required
string <uri>

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Arm a break-glass owner

The recovery path when an organization has locked itself out. Every accepted mutation audits inside its own transaction; rejected attempts audit separately.

Authorizations:
adminToken
path Parameters
slug
required
string

The organization slug.

Request Body schema: application/json
required
email
required
string <email>

Responses

Request samples

Content type
application/json
{
  • "email": "user@example.com"
}

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

List members

Authorizations:
adminToken
path Parameters
slug
required
string

The organization slug.

Responses

Response Schema: application/json
Array of objects (Membership)

Response samples

Content type
application/json
{
  • "members": [
    ]
}

Set a member's roles

Authorizations:
adminToken
path Parameters
slug
required
string

The organization slug.

membership_id
required
string <uuid>
Request Body schema: application/json
required
roles
required
Array of strings

Responses

Response Schema: application/json
id
string <uuid>
user_id
string <uuid>
email
string <email>
roles
Array of strings
active
boolean

Request samples

Content type
application/json
{
  • "roles": [
    ]
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",
  • "email": "user@example.com",
  • "roles": [
    ],
  • "active": true
}

Deactivate a member

This is also the kill switch for that member's personal connections: every brokered call re-verifies owner-membership before touching a credential, so deactivation cuts them off mid-run.

Authorizations:
adminToken
path Parameters
slug
required
string

The organization slug.

membership_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Rotate a tenant's LLM key

Mints a fresh gateway virtual key, swaps the sealed row, and retires the old key upstream. The key itself is never returned.

Authorizations:
adminToken
path Parameters
slug
required
string

The organization slug.

Responses

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Re-seal job status

Progress of the legacy-to-envelope re-seal, including the v1 row count that gates retiring FLUIDBOX_CREDENTIAL_KEY.

Authorizations:
adminToken

Responses

Response Schema: application/json
state
string
Enum: "idle" "running" "completed" "failed"
v1_rows_remaining
integer <int64>

The retirement gate. FLUIDBOX_CREDENTIAL_KEY may only be dropped once boot proves this is zero.

v2_rows
integer <int64>
started_at
string <date-time>

Response samples

Content type
application/json
{
  • "state": "idle",
  • "v1_rows_remaining": 0,
  • "v2_rows": 0,
  • "started_at": "2019-08-24T14:15:22Z"
}

Start the re-seal job

Re-seals every legacy (v1) sealed column under per-tenant envelope encryption. Resumable, compare-and-swap guarded, count-parity checked, and a singleton.

This is the supported path to retiring the legacy credential key. Do not drop FLUIDBOX_CREDENTIAL_KEY until this job proves zero v1 rows — and be aware that from the moment any v2 row exists, custody roots on the KEK, so losing the KEK is unrecoverable. Back it up.

Authorizations:
adminToken

Responses

Response Schema: application/json
state
string
Enum: "idle" "running" "completed" "failed"
v1_rows_remaining
integer <int64>

The retirement gate. FLUIDBOX_CREDENTIAL_KEY may only be dropped once boot proves this is zero.

v2_rows
integer <int64>
started_at
string <date-time>

Response samples

Content type
application/json
{
  • "state": "idle",
  • "v1_rows_remaining": 0,
  • "v2_rows": 0,
  • "started_at": "2019-08-24T14:15:22Z"
}

Prometheus metrics

Bounded-cardinality counters, gauges, and histograms. There are deliberately no per-tenant labels — per-tenant accounting lives in the usage ledger, not in metrics.

The optional FLUIDBOX_METRICS_BIND listener serves this same body unauthenticated on its own port; point it at a private interface only.

Authorizations:
adminToken

Responses

Response Schema: text/plain
string

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Prometheus metrics (private listener)

The same body as /v1/admin/metrics, served unauthenticated on the optional listener configured by FLUIDBOX_METRICS_BIND.

This path exists only on that listener — it is not mounted on the public or sandbox planes. Because it carries no authentication, bind it to a private interface only. A bad address fails boot by design, but a reachable one is your responsibility.

Responses

Response Schema: text/plain
string

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Ingress

Webhook receivers for connected services. Deliberately unauthenticated in the bearer sense: the signature verified against the connection's sealed secret is the authentication, and nothing is stored before it verifies.

Receive a connected-service webhook

Unauthenticated in the bearer sense by design: the signature verified against the connection's sealed secret is the authentication, and nothing is stored before it verifies.

The pipeline is provider-ignorant — ingress, verify, normalize, match, create run, publish — with all provider knowledge behind a single dispatch.

Retries are safe and in fact healing: two database-unique dedup levels (delivery per connection, dispatch per subscription) mean a retry completes a partial fan-out rather than duplicating runs or comments.

A pull request from a fork freezes trust_tier: read_only, enforced at the permission gate above policy and above human approval. The check fails toward "fork" when the head repository is hidden.

path Parameters
provider
required
string

The connector provider, e.g. github.

connection_id
required
string <uuid>
header Parameters
X-Hub-Signature-256
string

The HMAC signature over the raw body.

Request Body schema: application/json
required
object

Responses

Request samples

Content type
application/json
{ }

Response samples

Content type
application/json
{
  • "error": "agent not found"
}

Receive an app-level GitHub webhook

App-level ingress, verified against the registration's sealed webhook secret. The connection is resolved from the verified payload's installation id and fed into the same pipeline as connection-level ingress.

Lifecycle database failures answer 5xx on purpose so GitHub retries — never swallow-and-acknowledge.

path Parameters
registration_id
required
string <uuid>

The GitHub App registration identifier.

header Parameters
X-Hub-Signature-256
string
Request Body schema: application/json
required
object

Responses

Request samples

Content type
application/json
{ }

Response samples

Content type
application/json
{
  • "error": "agent not found"
}