HTTP API
Use https://api.getstead.dev for API, CLI, and SDK traffic. The developer console uses its own same-origin routes at https://app.getstead.dev; see developer account endpoints.
JSON routes return { "ok": true, ...result } or { "ok": false, "error": { "code", "message", "requestId"? } }. Timestamps are ISO strings. Dollar totals are decimal strings. Authentication endpoints use their own auth response format, and run streams use SSE.
Send Content-Type: application/json with JSON bodies. Auth requests also require a bounded Content-Length; normal Fetch clients provide this for string bodies. Bearer tokens go in Authorization: Bearer …, never URL parameters. Do not follow redirects while sending credentials to another host.
Public status
Section titled “Public status”| Method and path | Result |
|---|---|
GET /healthz |
200, { ok: true } for HTTP liveness. Does not test every dependency. |
GET /readyz |
200 or 503, { ok: boolean } for configured service readiness. |
GET /v1/developer/config |
{ ok, emailEnabled, googleEnabled, apiUrl, consoleUrl }. Public login metadata. |
Customer authentication
Section titled “Customer authentication”These paths are under /v1/auth/:projectId. Use the project’s public UUID. Browser requests must use an origin registered in the project settings. SteadAuth implements these calls and reads the signed session response header for you.
| Method and suffix | Body/query and result |
|---|---|
POST /sign-up/email |
{ name, email, password, callbackURL }; registers an account and sends verification. |
POST /sign-in/email |
{ email, password }; verified login returns a signed session in set-auth-token. |
POST /send-verification-email |
{ email, callbackURL }; requests a verification email. |
GET /verify-email |
Follow the emailed URL with its token and optional callbackURL; may redirect to the callback. |
POST /request-password-reset |
{ email, redirectTo }; requests a reset email. |
GET /reset-password/:token |
Emailed reset entry point; forwards to the configured redirect URL for your reset form. |
POST /reset-password |
{ token, newPassword }; completes reset. |
GET /get-session |
Signed session bearer; returns auth session/user information or no valid session. |
POST /sign-out |
{} with the signed session bearer; requests session revocation. |
POST /token |
{} with a valid verified signed session bearer; returns { ok, token, expiresAt, endUser } for customer API access. |
Verification and reset links are credentials. Treat their entire URLs as secrets. Auth errors may use { code, message } instead of the general API envelope. Use status and the SDK’s surfaced message; do not infer account existence from email-request responses.
Customer operations
Section titled “Customer operations”Use the short-lived customer token from built-in auth or a trusted backend’s identity exchange. The SDK refreshes through its token callback. Customer responses deliberately omit administrative budget/configuration details.
| Method and path | Request | Success result |
|---|---|---|
POST /v1/runs |
{ agent, input?, async?, conversationId? } |
{ run: { id, endUserId, status }, outcome: { status, approvalId?, failure? } }; 202 while executing, otherwise 200. |
POST /v1/runs/:id/cancel |
{} |
{ run: { id, status, cancelRequestedAt } }; 202 when pending, 200 when terminal. |
GET /v1/runs/:id/stream |
Optional resume cursor described below | SSE for the customer’s own run. |
POST /v1/data |
{ table, where?, limit? } |
{ rows, rowCount }; select-only through exposures/RLS. |
POST /v1/conversations |
{ agent, key? } |
{ conversation: { id, created } }. |
GET /v1/conversations/:id/messages |
Query limit?, cursor? |
{ messages: [{ seq, role, content, createdAt }], nextCursor? }. |
Customers cannot supply steps, identity, budgets, tools, or resume; additional fields are refused. Conversation creation requires deployed opt-in. A foreign run or conversation is not visible. Unsupported project/administrative operations are forbidden to customer tokens.
Example executable Node script using a customer token supplied privately in the environment after the starter is deployed:
// ask.mjs — run with node ask.mjsimport { SteadClient } from '@getstead/client';
const token = process.env.STEAD_CUSTOMER_TOKEN;if (!token) throw new Error('Supply a customer token through your secret environment.');const client = new SteadClient({ url: 'https://api.getstead.dev', token });const { run } = await client.startRun({ agent: 'support-agent', input: 'What orders do I have?', async: true,});const stream = client.streamRun(run.id);for await (const item of stream) { if (item.kind === 'model-delta') process.stdout.write(item.text);}console.log('\nRun status:', stream.state.runStatus);For an interactive application, use the renewing token callback from the authentication guide instead of a fixed environment token.
Project operations
Section titled “Project operations”Use a project credential on a trusted backend. It determines the project; these routes do not accept an arbitrary project ID to change its authority. Owning developer sessions can access equivalent JSON operations through the developer project prefix.
| Method and path | Request | Success result |
|---|---|---|
GET /v1/project |
None | { project: { id, name, createdAt } }. |
GET /v1/agents |
None | { agents: [{ id, name, config, createdAt }] }. |
PUT /v1/agents/:name |
{ config } |
{ agent }; configuration upsert alone does not publish a runnable deployment. Use deploy. |
POST /v1/deploy |
{ bundle, agents, exposures, migrations } |
{ deployment: { id, bundleHash, appliedMigrations, agents, createdAt }, url }. bundle is base64 ESM. See configuration. |
POST /v1/provider-key |
{ baseUrl, apiKey } |
{ ok: true }; no key read-back. |
POST /v1/provider-check |
{} |
{ check: { model, runId, status, message } }; small billable Luna request. |
GET /v1/diagnostics |
None | Project checks, storage usage, capacity, and setup status; no secrets. |
POST /v1/identity/token |
{ externalId } |
{ token, expiresAt, endUser: { id, externalId } }; external ID 1–200 characters without control characters. |
POST /v1/runs |
Project scenario or resume, below | { run, outcome }; 202 while executing, otherwise 200. |
GET /v1/runs |
Query status?, cursor? |
{ runs, nextCursor? }; newest-first, bounded pages. |
GET /v1/runs/:id |
None | { run } including spend/budget and optional recovery metadata. |
GET /v1/runs/:id/trace |
None | { run, events, ledger, totals }. |
POST /v1/runs/:id/cancel |
{} |
{ run: { id, status, cancelRequestedAt } }; 202 pending or 200 terminal. |
GET /v1/runs/:id/stream |
Optional cursor | SSE for a run in the project. |
GET /v1/approvals |
Query status?, cursor? |
{ approvals, nextCursor? }. The HTTP route has no default status filter; the CLI defaults to pending. |
GET /v1/approvals/:id |
None | { approval }. |
POST /v1/approvals/:id/resolve |
`{ decision: ‘approved’ | ‘denied’ }` |
Project starts require agent, endUser (the external customer ID), and steps. Optional budget, tools, and async follow the configuration schemas. Tool steps and named model steps are accepted. Deployed tool postures cannot be redeclared by the scenario. Resume uses { resume: runId, async? } without agent, endUser, steps, tools, or budget.
A run summary includes id, agentId, endUserId, status, budget: { tokens, usd }, spend: { tokensInput, tokensOutput, costUsd }, and timestamps. Optional recovery is { nextAttemptAt, attempts, lastError }. Approval records include id, runId, status, action, requestedAt, expiresAt, and resolvedAt. Trace events include seq, type, payload, and createdAt; ledger entries carry their recorded token and dollar amounts.
Example project read without placing a secret in a command argument:
// project.mjs — run with node project.mjsconst secret = process.env.STEAD_PROJECT_SECRET;if (!secret) throw new Error('Supply STEAD_PROJECT_SECRET from your secret store.');const response = await fetch('https://api.getstead.dev/v1/project', { headers: { authorization: `Bearer ${secret}` }, redirect: 'error', signal: AbortSignal.timeout(30000),});const body = await response.json();if (!response.ok || body.ok !== true) { throw new Error(body.error?.message ?? `HTTP ${response.status}`);}console.log(body.project.id, body.project.name);Stream protocol
Section titled “Stream protocol”GET /v1/runs/:id/stream responds as text/event-stream. Send Last-Event-ID with the last durable sequence to resume after it, or use ?lastEventId=42 when you cannot set that header. The header takes precedence. The cursor must be a nonnegative decimal integer no greater than 2,147,483,647. Authentication/refusal errors arrive as normal JSON before stream headers.
Durable messages carry an SSE ID and event type. Model deltas have no durable ID. The SDK reconciles completed steps and the server’s end-of-log signal. A stream ending after your cursor does not necessarily replay a terminal event; retain your previous state and cursor together. A network disconnect does not cancel execution.
Errors and retries
Section titled “Errors and retries”| Status/code | Action |
|---|---|
400 invalid-request |
Fix the body, field, cursor, or configuration described by the message. |
401 unauthorized |
Refresh customer auth, reauthorize CLI, or replace the invalid credential. |
403 forbidden |
Wrong credential type, origin, exposure, or SQL permission. Do not retry with broader browser authority. |
404 not-found |
Missing/foreign resource, undeployed customer agent, or stale approval. Refresh project state. |
409 conflict / run-busy |
Resolve the conflicting operation or wait for its owner. |
413 |
Reduce request or bundle size. |
429 |
Rate limit; respect retry guidance. |
503 unavailable / resource-limit |
Inspect Diagnostics, back off, and respect Retry-After when present. |
500 internal |
Preserve the opaque request reference. Refresh state before repeating a mutation. |
Broker data errors can have more specific codes such as column-not-exposed. Read the message and fix the named permission/configuration. Not every transport error has a JSON body. See troubleshooting for recovery steps.