--- title: 'API' description: 'The HTTP surface your box serves: chat with any agent over three standard protocols, start and follow durable workflows, check health, and the routes Studio and the CLI operate it with.' position: 6 --- # API > Your **box** (the container running your workspace, on your laptop under > `stackbone dev` or deployed in your cloud) serves one HTTP API, and the > runtime produces every route from the files you author. Any OpenAI, Anthropic > or AG-UI client can chat with an agent. Any HTTP client can start a workflow. > Studio and the `stackbone` CLI operate the box through the same server. You > write no HTTP server, and no request routes through Stackbone: your caller > talks to your box. ![The Studio Catalog: four entries the box serves, my-workspace as a chat agent on deepagents and daily-digest, refund and weekly-digest as job workflows on wdk, each with its last run, the outcome of the last seven days, and Playground, Runs and Copy actions.](/images/api/catalog.png) _Everything the box serves, read live from the box itself: one agent and three workflows._ ## What the box serves Four layers of HTTP, all on the same server and the same port: | Layer | Routes | Who calls it | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | Agent chat | `POST /openai/v1/chat/completions`, `POST /anthropic/v1/messages`, `POST /agui/v1/agents/:name`, plus `GET …/v1/models` on the first two | Your own product or service, with the OpenAI SDK, the Anthropic SDK, an AG-UI client, or `curl`. The Studio Playground is one such client. | | Workflow runtime | `POST /api/workflows/:name/start`, `POST /api/workflows/:name/chat`, `GET /api/workflows`, `GET /api/workflows/:name/schema`, `GET /api/discovery`, the approval hooks | Studio, `stackbone workflows start`, a timer the workflow declares, an inbound trigger, another workflow. Under `stackbone dev`, anything on your machine. | | Health | `GET /live`, `GET /health`, `GET /api/contract` | Your platform's probes, `stackbone contract show`, the control plane when it checks the box is reachable. | | Operate | `/api/runs`, `/api/sessions`, `/api/approvals`, `/api/logs`, `/api/events`, `/api/catalog`, `/api/prompts`, `/api/config`, `/api/secrets`, `/api/guardrails`, `/api/evals`, and more | Studio and the CLI. Every screen and every `stackbone ` verb is a client of these routes. See [The surface area](#the-surface-area). | Under `stackbone dev` the base URL is `http://127.0.0.1:4242`. A deployed box listens on `8080`, or on `PORT` when your platform sets one, and you reach it at whatever address you gave it (see [Going to production](/docs/cli/reference/commands#going-to-production)). The routes are the same in both. Open that address in a browser under `stackbone dev` and the box prints its own route list: ![The route list the box prints when you open its address in a browser under stackbone dev: the contract handshake, the health, services, schema and discovery reads, the three chat routes, the workflow start route, the last runs and the event stream.](/images/api/box-endpoints.png) _The box's own front page under `stackbone dev`: the routes, with the chat wires and the workflow start among them._ You never see a URL of the form `stackbone.ai/…/your-agent`. The control plane holds your organization and the record of where each box lives. The traffic goes to the box. ## Chat with an agent from any client An agent speaks three standard wire formats. A client built for OpenAI Chat Completions, Anthropic Messages or AG-UI needs a base URL and a key and nothing else: the official SDKs, LibreChat, Open WebUI, the Vercel AI SDK, LangChain, `curl`. You pick the agent with the `model` field (OpenAI and Anthropic) or with the `:name` in the URL (AG-UI). `GET /openai/v1/models` lists the agent names, so a chat UI can fill its model picker from it. ```sh curl http://127.0.0.1:4242/openai/v1/chat/completions \ -H 'Authorization: Bearer stackbone-dev' \ -H 'Content-Type: application/json' \ -d '{ "model": "my-workspace", "messages": [{ "role": "user", "content": "In one sentence, what can you help me with?" }] }' ``` ```jsonc { "id": "chatcmpl-1f390752-a191-40a3-9e72-2be0afe04faa", "object": "chat.completion", "model": "my-workspace", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "I can assist you with a wide range of tasks, including research, problem-solving, writing, and general inquiries.", }, "finish_reason": "stop", }, ], "usage": { "prompt_tokens": 6393, "completion_tokens": 24, "total_tokens": 6417 }, } ``` Set `"stream": true` and you get the same server-sent events the vendor's own API streams. A tool call arrives as `tool_calls` (OpenAI) or a `tool_use` block (Anthropic). The Anthropic wire also carries the agent's `thinking` blocks; AG-UI streams every reasoning block, tool call and tool result as its own event. A turn is stateless unless you say otherwise. By default you replay the full `messages[]` on every call, the same as calling the vendor directly, and each turn is recorded as its own run. Send an `x-stackbone-session` header with any stable string and the box keeps the conversation and its tool state on its side, so you send only the newest turn. AG-UI's `threadId` does the same and is always on. The [protocol reference](/docs/cli/protocol/auth#session-keys) covers the second header, `x-stackbone-conversation`, which groups turns without storing state. A tool that needs a person pauses the turn. When an agent tool marked with `interruptOn` fires, the turn stops and the decision lands in the Studio HITL Inbox. The next call against that session answers `409` with `code: "approval_pending"` until someone decides. Over AG-UI the pause is an interrupt on the stream, and your client can resolve it in the next request instead of going through Studio. See [Tool approvals](/docs/cli/protocol/tools#tool-approvals). Every turn, whoever sent it, is a run: it shows up under **Runs** in Studio with the tools it called and the tokens it spent, and in [`stackbone runs list`](/docs/cli/reference/runs). ## Start and follow a workflow A workflow is a `'use workflow'` function under `workflows/`. The box gives each one a start route, a detail and a schema, and Studio shows the route next to the entry with a **Copy curl** button that produces the call below. ![The Catalog entry for the refund workflow: healthy, job, wdk, with Open in Playground, View runs, Copy curl and Copy link actions; the workflow id, an input contract marked as declared, the trigger POST /api/workflows/refund/start, one step named performRefund, and three recent runs.](/images/api/workflow-entry.png) _A workflow entry: its start route, its step, and the guardrail set wired to it._ ```sh curl -X POST http://127.0.0.1:4242/api/workflows/refund/start \ -H 'Content-Type: application/json' \ -d '{ "orderId": "ord_42", "amount": 19.99 }' ``` ```jsonc { "workflowName": "refund", "status": "started", "runId": "aa4f50bf-29df-58dd-86c1-5ddbbf8d74d2", "worldRunId": "wrun_01M080NBDQD31ER017X2PV3241", "trigger": "POST /api/workflows/refund/start", } ``` The call returns as soon as the run is enqueued. `runId` is the id you see in Studio and pass to `stackbone runs get`. The run itself is durable: each `'use step'` runs once, its result is written down, and a run that pauses for hours (a `sleep`, an approval) resumes where it stopped after a restart. The input is checked at the door. A workflow that declares an input schema gets it enforced before anything runs, and a bad payload starts no run: ```jsonc { "code": "workflow_input_invalid", "message": "Input does not match the schema for workflow \"refund\".", "issues": [{ "path": "amount", "message": "Invalid input: expected number, received undefined" }], } ``` | Situation | Status | `code` | | --------------------------------------------------------- | ------ | ---------------------------- | | No workflow by that name (the body lists the real ones) | `404` | `workflow_not_found` | | The input does not match the declared schema | `400` | `workflow_input_invalid` | | A guardrail refused the payload (the body names the rule) | `422` | `workflow_guardrail_blocked` | | The workflow failed to compile at boot | `503` | `workflow_compile_failed` | The same schema drives the Studio Playground. Pick a workflow and Studio builds the input form from it, with the input and output contract beside it. `GET /api/workflows/:name/schema` returns the two JSON Schemas, and `stackbone workflows schema ` prints them. ![The Studio Playground with the refund workflow selected: a form generated from its schema with orderId set to ord_42 and amount to 19.99, a Run workflow button, and the input JSON Schema shown under Contract (schema).](/images/api/playground-form.png) _The same schema the start route enforces, as a form._ Streaming workflows answer on `/chat`. A workflow written to hold a conversation (its catalog mode is `chat`) is started with `POST /api/workflows/:name/chat` and answers a server-sent-event stream of the frames its steps write. See [Workflow agents](/docs/sdk/workflow-agents/overview). A paused workflow resumes over a hook. A workflow that calls [`requestApproval()`](/docs/sdk/humans/approval) parks until a decision is posted to `POST /api/workflows/hooks/:token/resume`. You do not build that round-trip: the HITL Inbox and `stackbone hitl approve` post it for you, and the token is the only credential the route asks for. Discovery routes, for a client that needs to know what is there before calling it: | Route | Returns | | --------------------------------- | ---------------------------------------------------------------------------------- | | `GET /api/workflows` | The workflow catalog: name, start route, whether it declares a schema, its steps. | | `GET /api/workflows/:name` | One workflow's detail. | | `GET /api/workflows/:name/schema` | Its input and output JSON Schema. | | `GET /api/discovery` | The combined `{ agents, workflows }` view of the workspace. | | `GET /api/recurring-jobs` | Every timer armed on the box, when it fires next, and how its last execution went. | ## The surface area Studio and the CLI are HTTP clients of the box like any other. Every screen and every `stackbone ` verb reads or writes through the box's own routes, and the box enforces your role on each of them. The groups: | Group | What you can do | Studio | CLI | | -------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------- | -------------------------------- | | **Runs** | List, filter and open runs; read the trace step by step; retry or cancel; stream one run's logs. | Runs | `stackbone runs` | | **Sessions** | Group chat turns into conversations with a token total; open a turn's run. | Sessions | | | **Approvals** | List what paused for a person, read the payload and the audit trail, approve or reject. | HITL Inbox | `stackbone hitl` | | **Logs** | Tail everything the box prints, live, filtered by level, run or text. | Logs | `stackbone logs tail` | | **Catalog** | Every agent and workflow the box serves, with model, tools, schema, guardrails and triggers. | Catalog | `stackbone workflows list` | | **Prompts** | Read and edit the prompts your code loads by name; version and roll back. | Prompts | `stackbone prompts` | | **Dynamic config** | Change values your code reads at runtime, without a redeploy. | Dynamic config | `stackbone config` | | **Secrets** | Set, rotate and delete the keys your code reads. Values are written once and never listed. | Secrets | `stackbone secrets` | | **Guardrails** | Rules the runtime enforces on a turn: block, mask, or hold for approval. | Guardrails | | | **Evals** | Cases, suites and runs that score an agent or a workflow; the gate you put in CI. | Cases, Suites, Runs | `stackbone eval ` | | **Recurring jobs** | Every timer on the box, its next fire and last outcome. | Recurring jobs | | | **Storage, DB, RAG** | Browse the box's object storage, query its Postgres read-only, manage RAG collections. | Storage, DB Explorer, RAG | `stackbone storage`, `db`, `rag` | | **Connections** | Connect an outside service once, grant it, test it, and subscribe to its events as triggers. | Connections, Triggers | | | **Model provider** | The credential and base URL the agents use to reach a model. Lives in the box, never in the control plane. | Model provider | | | **Events** | One server-sent-event stream of run, step, session and approval lifecycle, so a screen updates without polling. | Every live screen | | > [!NOTE] Reach these through Studio and the CLI > The routes behind this table are the box's own. They are what Studio and the > `stackbone` CLI speak, and both ship in step with the box, but they are not > published as a contract for your own code to call yet. The three layers above > them (chat, workflows, health) are. ## Who can call what The box checks a credential on every request. Which one depends on the route: | Routes | Credential | | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Agent chat (`/openai`, `/anthropic`, `/agui`) | A bearer credential: `Authorization: Bearer ` or `x-api-key: `, whichever your SDK sends. A missing one answers `401` in the wire's own error shape. Today the box checks that a credential is present, not what it is: per-workspace API keys are not there yet, so keep a deployed box off the public internet or behind your own gateway until they ship. See [Security and auth](/docs/home/features/security-auth#the-chat-wires-take-any-bearer-today). | | Workflow runtime and the operate routes | The five-minute identity token the control plane mints for your Studio session or your signed-in CLI, naming your organization, the box and your role. The box verifies it against the control plane's public keys and enforces the role per action (`runs:read`, `hitl:decide`, `secrets:rotate`, `evals:run`, …). The control plane itself signs its own calls to the box with the box's signing secret. | | Approval hooks (`/api/workflows/hooks/:token/resume`) | The token in the URL. It is random and unguessable, and it is the capability. | | Health and the handshake | None. `/live`, `/health` and `/api/contract` answer without a credential, so a probe never needs one. | `stackbone dev` arms none of this. The local box holds no signing secret and verifies no token, so every route answers, and the chat wires take any non-empty bearer (`stackbone-dev` in the examples above). The tunnel it opens so that Studio can reach it is a random address that disappears when the process stops. Treat it as private while it lives. Calls from a browser go through CORS. The box answers preflights for `https://app.stackbone.ai` and any `http://localhost:*` origin by default. A frontend of your own on another origin goes on the allowlist, in `stackbone.config.json` under `studio.corsOrigins` or in the `STACKBONE_CORS_ALLOW_ORIGINS` variable. See [Configuration](/docs/cli/reference/configuration). ## Health, liveness and the handshake The box splits "the process is up" from "everything it depends on works", so your platform does not recycle the container because one subsystem is slow: | Route | Behaviour | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GET /live` | Instant `200 { "status": "ok" }`. No probes. Point your platform's liveness check here. | | `GET /health` | Runs every subsystem probe in parallel. `200 { "status": "ok", "checks": { … } }`, or `503 { "status": "degraded", "checks": { … } }` naming the subsystem that failed and why. `checks` covers what your workspace depends on, such as `database` and `redis`. | | `GET /api/health` | The lighter answer `stackbone dev` serves instead of the two above. | | `GET /api/contract` | The **Stackbone Agent Protocol** handshake: the protocol version the box speaks, the build it runs, and the capabilities your code can rely on (`database.postgres_direct`, `storage.s3`, `queues.jobs`, `secrets.read_write`, …). | ```jsonc // GET /api/contract { "version": 15, "minSupported": 1, "capabilities": [ "database.postgres_direct", "rag.basic", "queues.jobs", "secrets.read_write", "config.read_write", "approval.fire_and_forget", "storage.s3", "ai.openrouter", "prompts.basic", "browser.provider", ], "build": { "name": "stackbone-cli", "version": "0.3.3" }, } ``` `stackbone contract show` prints the same answer, and `stackbone contract validate` checks your project against it before you build an image. This handshake is what the SDK's ambient `stackbone` client (`stackbone.database`, `.storage`, `.secrets`, …) reads to know what the box offers; it is not one of the routes you drive yourself. ## Read more - [Agent runtime protocol](/docs/cli/protocol/overview): the full contract, request and response shapes for all three chat wires, session headers, tool approvals over AG-UI, and the environment the runtime injects. - [Security and auth](/docs/home/features/security-auth): how a caller proves itself to the box, and where every credential sits. - [Governance](/docs/home/features/governance): the Studio screens that sit on top of the operate routes. - [Building workflows](/docs/sdk/workflows/building-workflows): declaring an input schema and triggering a run from code, a timer or an event. - [Agent-runtime CLI commands](/docs/cli/reference/commands): `runs`, `hitl`, `logs`, `secrets`, `config`, `prompts` and `contract` from a terminal.