new: v0.3 — kubernetes-native sandboxes and the multi-user control plane. read the changelog ›
DocsAPIReference

API reference

106 operations · v0.3.0 · generated from the OpenAPI description. Request and response schemas live in the full reference; the raw spec is what a generator or agent wants.

Public API

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.

GET/v1/sessionsList runs

Auth: adminToken

No further description.

POST/v1/sessionsStart a run

Auth: adminToken

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.

GET/v1/sessions/{id}Get a run

Auth: adminToken

Returns the run alongside its accumulated usage totals.

GET/v1/sessions/{id}/artifactsList run artifacts

Auth: adminToken

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

GET/v1/sessions/{id}/artifacts/{aid}Get one artifact

Auth: adminToken

No further description.

POST/v1/sessions/{id}/cancelCancel a run

Auth: adminToken

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.

GET/v1/sessions/{id}/costGet run cost

Auth: adminToken

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

GET/v1/sessions/{id}/deliveriesList result deliveries for a run

Auth: adminToken

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.

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.

GET/v1/sessions/{id}/eventsRead the run timeline

Auth: adminToken

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.

GET/v1/sessions/{id}/events/streamStream the run timeline (SSE)

Auth: adminToken

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"

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.

GET/v1/approvalsThe approval inbox

Auth: adminToken

Every approval currently waiting on a human decision.

POST/v1/approvals/{id}/decisionApprove or deny a paused tool call

Auth: adminToken

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.

GET/v1/sessions/{id}/approvalsList a run's approvals

Auth: adminToken

No further description.

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).

GET/v1/agentsList agents

Auth: adminToken

No further description.

POST/v1/agentsRegister an agent

Auth: adminToken

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.

GET/v1/agents/{id}Get an agent

Auth: adminToken

Returns the agent with its revision history.

POST/v1/agents/{id}/revisionsAppend a revision

Auth: adminToken

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.

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.

GET/v1/policiesList policies

Auth: adminToken

No further description.

POST/v1/policiesCreate or replace a policy from YAML

Auth: adminToken

No further description.

GET/v1/policies/{name}Get a policy

Auth: adminToken

No further description.

DELETE/v1/policies/{name}Delete a policy

Auth: adminToken

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

POST/v1/policies/{name}/publishPublish a policy draft

Auth: adminToken

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.

POST/v1/policies/{name}/revertRevert a policy to an earlier version

Auth: adminToken

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

GET/v1/policies/{name}/versions/{version}Get one historical policy version

Auth: adminToken

No further description.

POST/v1/policies/cloneClone a policy

Auth: adminToken

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.

POST/v1/policies/previewPreview the effective permission matrix

Auth: adminToken

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.

POST/v1/policies/validateValidate policy YAML

Auth: adminToken

Parses and checks a policy document without storing anything.

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.

GET/v1/triggersList trigger subscriptions

Auth: adminToken

No further description.

POST/v1/triggersCreate a trigger subscription

Auth: adminToken

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.

GET/v1/triggers/{id}Get a trigger subscription

Auth: adminToken

No further description.

POST/v1/triggers/{id}/disableDisable a subscription

Auth: adminToken

No further description.

POST/v1/triggers/{id}/enableEnable a subscription

Auth: adminToken

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.

POST/v1/triggers/{id}/invokeInvoke a trigger

Auth: triggerToken

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.

POST/v1/triggers/{id}/rotate_tokenRotate the trigger token

Auth: adminToken

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.

GET/v1/triggers/{id}/runs/{sid}Poll a run started by this subscription

Auth: triggerToken

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.

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.

GET/v1/connectionsList connections

Auth: adminToken

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

POST/v1/connectionsCreate a connection

Auth: adminToken

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.

POST/v1/connections/{id}/approveApprove a pending connection

Auth: adminToken

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.

GET/v1/connections/{id}/deliveriesList inbound webhook deliveries

Auth: adminToken

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.

POST/v1/connections/{id}/oauth/startBegin the OAuth connect flow

Auth: adminToken

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.
GET/v1/connections/{id}/reposList repositories a connection can see

Auth: adminToken

No further description.

POST/v1/connections/{id}/revokeRevoke a connection

Auth: adminToken

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.

GET/v1/connections/{id}/toolsRead the photographed tool surface

Auth: adminToken

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.

POST/v1/connections/{id}/tools/refreshRe-photograph the tool surface

Auth: adminToken

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

Catalog

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

GET/v1/catalogList connector catalog entries

Auth: adminToken

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.

POST/v1/catalogAdd a custom catalog entry

Auth: adminToken

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

GET/v1/catalog/{slug}Get a catalog entry

Auth: adminToken

No further description.

POST/v1/catalog/{slug}/connectConnect a catalog entry

Auth: adminToken

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.
POST/v1/mcp/probeProbe an MCP server

Auth: adminToken

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

POST/v1/mcp/serversBring your own MCP server

Auth: adminToken

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.

Capabilities

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

GET/v1/capabilitiesList capability bundles

Auth: adminToken

No further description.

POST/v1/capabilitiesRegister a capability bundle

Auth: adminToken

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.

GET/v1/capabilities/{id}Get a capability bundle

Auth: adminToken

No further description.

Identity

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

GET/v1/auth/callbackOIDC callback

No bearer credential — see the description for what authenticates this call.

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.

GET/v1/auth/loginThe login entry page

No bearer credential — see the description for what authenticates this call.

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

GET/v1/auth/login/{slug}/startBegin OIDC login for an organization

No bearer credential — see the description for what authenticates this call.

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.

POST/v1/auth/logoutSign out

Auth: sessionCookie

No further description.

GET/v1/auth/meWho am I

Auth: sessionCookiepat

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

POST/v1/auth/switch/{id}Confirm an organization switch

Auth: sessionCookie

No further description.

GET/v1/auth/tokensList personal access tokens

Auth: sessionCookie

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

POST/v1/auth/tokensMint a personal access token

Auth: sessionCookie

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

The token value is returned exactly once.

DELETE/v1/auth/tokens/{id}Revoke a personal access token

Auth: sessionCookie

No further description.

GET/v1/oauth/callbackConnector OAuth callback

No bearer credential — see the description for what authenticates this call.

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.

GET/v1/oauth/goConnector OAuth boot leg

No bearer credential — see the description for what authenticates this call.

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.

GitHub

GitHub App registration, installation, and lifecycle.

GET/v1/github/appList GitHub App registrations

Auth: adminToken

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.

POST/v1/github/app/{id}/install/startBegin installing the app

Auth: adminToken

No further description.

POST/v1/github/app/{id}/revokeRevoke a registration

Auth: adminToken

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.

GET/v1/github/app/{id}/setupGitHub post-install setup landing

No bearer credential — see the description for what authenticates this call.

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.

POST/v1/github/app/{id}/syncReconcile installations against GitHub

Auth: adminToken

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.

GET/v1/github/app/install/goGitHub App install redirect

No bearer credential — see the description for what authenticates this call.

No further description.

GET/v1/github/app/manifest/callbackGitHub App manifest callback

No bearer credential — see the description for what authenticates this call.

Exchanges the manifest code and seals the resulting app credentials.

GET/v1/github/app/manifest/goGitHub App manifest form

No bearer credential — see the description for what authenticates this call.

Browser-facing. Posts the app manifest to GitHub.

POST/v1/github/app/manifest/startBegin GitHub App creation

Auth: adminToken

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

Service

Health and metadata endpoints.

GET/.well-known/fluidbox-client.jsonOAuth client ID metadata document

No bearer credential — see the description for what authenticates this call.

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.

GET/v1/harnessesList supported harnesses and models

Auth: adminToken

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.

GET/v1/healthLiveness probe

No bearer credential — see the description for what authenticates this call.

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

GET/v1/health/readyReadiness probe

No bearer credential — see the description for what authenticates this call.

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.

Runner contract

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.

AudienceRoutes
tool/permission, /tools/call
control/events, /heartbeat, /result, /token/renew
workspace/workspace
llm/llm/*
POST/internal/llm-usageGateway usage callback

Auth: sessionToken

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

POST/internal/llm/{rest}The LLM facade

Auth: sessionToken

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.

POST/internal/sessions/{id}/eventsReport a timeline event

Auth: sessionToken

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.

POST/internal/sessions/{id}/heartbeatReport liveness

Auth: sessionToken

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.

POST/internal/sessions/{id}/permissionAsk permission for a tool call

Auth: sessionToken

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.

POST/internal/sessions/{id}/resultReport the final outcome

Auth: sessionToken

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.

POST/internal/sessions/{id}/tools/callInvoke a brokered tool

Auth: sessionToken

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.

GET/internal/sessions/{id}/workspaceFetch the workspace archive

Auth: sessionToken

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.

POST/internal/token/renewRenew a session token

Auth: sessionToken

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

Operator

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.

GET/metricsPrometheus metrics (private listener)

No bearer credential — see the description for what authenticates this call.

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.

GET/v1/admin/metricsPrometheus metrics

Auth: adminToken

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.

GET/v1/admin/orgsList organizations

Auth: adminToken

No further description.

POST/v1/admin/orgsCreate an organization

Auth: adminToken

No further description.

POST/v1/admin/orgs/{slug}/break-glass-ownerArm a break-glass owner

Auth: adminToken

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

GET/v1/admin/orgs/{slug}/idpList identity provider configurations

Auth: adminToken

No further description.

POST/v1/admin/orgs/{slug}/idpAdd an identity provider

Auth: adminToken

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.

PATCH/v1/admin/orgs/{slug}/idp/{id}Update an identity provider

Auth: adminToken

No further description.

POST/v1/admin/orgs/{slug}/idp/{id}/activateActivate an identity provider

Auth: adminToken

No further description.

POST/v1/admin/orgs/{slug}/idp/{id}/disableDisable an identity provider

Auth: adminToken

No further description.

POST/v1/admin/orgs/{slug}/idp/{id}/migrateMigrate to a new issuer

Auth: adminToken

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

POST/v1/admin/orgs/{slug}/idp/{id}/reactivateReactivate an identity provider

Auth: adminToken

No further description.

POST/v1/admin/orgs/{slug}/llm-key/rotateRotate a tenant's LLM key

Auth: adminToken

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

GET/v1/admin/orgs/{slug}/membersList members

Auth: adminToken

No further description.

POST/v1/admin/orgs/{slug}/members/{membership_id}/deactivateDeactivate a member

Auth: adminToken

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.

POST/v1/admin/orgs/{slug}/members/{membership_id}/rolesSet a member's roles

Auth: adminToken

No further description.

GET/v1/admin/resealRe-seal job status

Auth: adminToken

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

POST/v1/admin/resealStart the re-seal job

Auth: adminToken

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.

Ingress

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.

POST/v1/ingress/{provider}/{connection_id}Receive a connected-service webhook

No bearer credential — see the description for what authenticates this call.

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.

POST/v1/ingress/github/app/{registration_id}Receive an app-level GitHub webhook

No bearer credential — see the description for what authenticates this call.

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.