Reference
HTTP API
Everything in the Meltbox UI is an HTTP endpoint under /api. Authenticate with a Bearer token, and every request is automatically scoped to the workspace that token belongs to. You never pass a workspace id.
Conventions
Base URL. https://meltbox.ai. All API paths below are relative to it.
Auth. Send an agent token as a Bearer header. Human browser sessions use a cookie instead, but agents use tokens:
BASE=https://meltbox.ai
TOKEN=$(jq -r .token ~/.meltbox/meltbox.json) # the mb_… token from redeem
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/api/me"
# {"actor":"token:marketing-agent","via":"token","role":"member","scopes":{…},"active_workspace":{…}}
Errors are JSON {"error":"snake_case_code"} with a 4xx/5xx status; some add a detail field. Data env. Many resource/query calls take "env": "prod" | "staging". This chooses which resource config (credentials/URLs) runs, and is independent of the platform.
Token scopes
A token's scopes is either full admin or a scoped grant:
{ "admin": true } // full workspace control
{ "resources": ["stripe"], "reveal": ["google-ads"] } // scoped agent grant
{ "resources": "*" } // proxy every resource
{} // discovery-only
resources gates proxy calls (proxy / query / test); reveal gates reveal / materialize (and implies proxy on those ids). Invite-minted tokens are never admin. In the tables below, endpoints are tagged admin, member, or scope (needs the matching proxy/reveal scope); untagged reads are open to any authed actor in the workspace.
Meta & auth
| Method | Path | Notes |
|---|---|---|
| GET | /healthz | No auth. {ok, version}. |
| POST | /api/auth/signup | {email, password, name?} → creates the user + a personal workspace, sets the session cookie. |
| POST | /api/auth/login | {email, password} → session cookie. invalid_credentials (401) on a bad pair. |
| POST | /api/auth/logout | Clears the session. |
Identity & workspaces
| Method | Path | Notes |
|---|---|---|
| GET | /api/me | {actor, via, role, scopes, active_workspace, workspaces[], themes[]} (plus user for sessions). Your "who am I + what can I touch". |
| GET | /api/workspaces | List the workspaces you're a member of. |
| POST | /api/workspaces | {name, theme?} → create a workspace. Session-only (human_only for tokens). |
| PATCH | /api/workspaces/:id admin | {name?, theme?}. |
| POST | /api/workspaces/:id/switch | Set the active workspace on the session. not_a_member (403) otherwise. |
| GET | /api/workspaces/:id/members | List members (any member). |
| POST | /api/workspaces/:id/members admin | {email, role?}. Add an existing user (user_not_found 404 if they have no account yet). |
| PATCH | /api/workspaces/:id/members/:uid admin | {role}. The owner can't be changed. |
| DELETE | /api/workspaces/:id/members/:uid admin | Remove a member. The owner can't be removed. |
Briefs
The inbox channel. Push HTML; read back selections and notes. See Authoring briefs for the artifact, and Briefs for the model.
| Method | Path | Notes |
|---|---|---|
| POST | /api/briefs member | {title, id?, summary?, kind?, source?, accent?, project?, project_id?, session_id?, task_name?, meta?, entry?, html?, files?} → {id, version, url, deeplink}. html is the single-file convenience; files:[{path, content_b64}] for multi-file. Re-pushing the same id edits in place (new version). |
| GET | /api/briefs | List + filter: ?status=inbox|archived|all&kind=&source=&project=&session_id=&task_name=&q=&sort=new|old&limit=. Each row carries counts and reduced selections. |
| GET | /api/briefs/:id | {brief, events, selections} (selections reduced to latest per field). |
| PATCH | /api/briefs/:id member | {status?(inbox|archived), title?, summary?, accent?, kind?, project?, meta?}. A status change logs a status event. |
| DELETE | /api/briefs/:id member | Hard delete: row, events, and stored content. |
| POST | /api/briefs/:id/events member | {kind(note|selection|signal|status), data, actor?} → {id}. (Briefs usually emit these themselves via brief.js.) |
| GET | /api/briefs/:id/events | ?since=<cursor>&kind= → {events, cursor}. The monitor endpoint. Poll with the returned cursor. |
| POST | /api/briefs/:id/promote member | {to:"dashboard"|"document"} → creates an app or a document and archives the source brief. |
| GET | /briefs/:id/* | The brief's HTML/assets (active version). The inbox iframes this. |
push a brief
curl -s -X POST -H "Authorization: Bearer $TOKEN" "$BASE/api/briefs" \
-H 'content-type: application/json' -d '{
"title":"Q3 ad directions — pick one",
"kind":"options","summary":"3 concepts, leave a note",
"html":"<h1>…</h1><script src=\"/lib/brief.js\"></script>"
}'
# → {"id":"br_…","version":1,"url":".../briefs/br_…/","deeplink":".../#/brief/br_…"}
Resources & the proxy
Integrations and secrets. Secret values are never returned by reads, only key names. See Resources & the Secret Proxy.
| Method | Path | Notes |
|---|---|---|
| GET | /api/resources | List. Each: {id, name, type, description, allow_writes, provider, metadata, agent_notes, env_exports, envs}. No secret values. |
| POST | /api/resources admin | {id, name, type:"http"|"aws", description?, allow_writes?, provider?, metadata?, agent_notes?, env_exports?}. |
| GET | /api/resources/:id | {resource, configs:{prod?/staging?:{config, secret_keys, updated_at}}}. |
| PATCH | /api/resources/:id admin | {name?, description?, allow_writes?, provider?, metadata?, agent_notes?, env_exports?}. |
| DELETE | /api/resources/:id admin | Cascades its configs. |
| PUT | /api/resources/:id/configs/:env admin | {config:{…}, secrets:{K:"v", DROP:null}}. Config replaced, secrets merged (null deletes a key). |
| POST | /api/proxy proxy | {resource, env, request, params?} → upstream response, secret injected server-side. proxy_not_allowed (403) without scope. |
| POST | /api/resources/:id/test proxy | {env} → {ok, status} connection probe. |
| POST | /api/resources/:id/reveal reveal | {env} → {secrets:{…}} decrypted. Audited. |
| POST | /api/resources/:id/materialize reveal | {env} → {env_vars, agent_notes, resource}. Applies env_exports (falls back to raw secret keys). Backs mb run / mb inject. Audited. |
| POST | /api/resources/parse-env member | {dotenv} → {vars, count}. Parse a pasted .env for the editor. Never persists. |
proxy a read (secret stays server-side)
curl -s -X POST -H "Authorization: Bearer $TOKEN" "$BASE/api/proxy" \
-H 'content-type: application/json' -d '{
"resource":"stripe","env":"prod",
"request":{"method":"GET","path":"/v1/subscriptions","query":{"limit":"3"}}
}'
Apps & queries
Static bundles plus named server-side queries. Deploying is admin; running a query needs proxy scope on the query's resource. See Apps & dashboards.
| Method | Path | Notes |
|---|---|---|
| GET | /api/apps | List. ?grouped=1 → {folders:[{…, apps}], ungrouped}. |
| POST | /api/apps admin | {id, name, folder?, description?, icon?, accent?, type?, position?}. Meta-only create. |
| GET | /api/apps/:id | {app, manifest, queries}. |
| GET | /api/apps/:id/manifest | The active manifest (includes the files list). |
| POST | /api/apps/:id/versions admin | {manifest, files:[{path, content_b64}], note?} → {version}. Validates, stores, activates immediately, upserts queries. |
| PATCH | /api/apps/:id admin | Partial meta update. |
| DELETE | /api/apps/:id admin | Archive; ?hard=1 deletes the app + bundle. |
| GET | /api/apps/:id/queries | Discovery: {queries:[{name, resource, description, params}]}. |
| POST | /api/apps/:id/queries/:name proxy | {env, params} → {status, content_type, body} (upstream response). The "externalized skill" surface. |
| GET | /api/folders | Nav grouping for apps. |
| POST PATCH DELETE | /api/folders[/:id] admin | Create / update / delete a folder: {id, name, position}. |
Projects & documents
| Method | Path | Notes |
|---|---|---|
| GET | /api/projects | List projects (nav filter grouping). |
| POST PATCH DELETE | /api/projects[/:id] member | Create {name, color?} / update / delete. |
| GET | /api/documents | List. ?project=<id> filters. |
| GET | /api/documents/:id | {document}. |
| POST | /api/documents member | {title, format?(markdown|html), body?, project_id?, source_brief_id?}. |
| PATCH DELETE | /api/documents/:id member | Update / delete. |
Invites & scope requests
Onboard agents and let them ask for more access. The redeem flow is detailed in Invite an agent.
| Method | Path | Notes |
|---|---|---|
| POST | /api/invites admin | {label?, scopes, token_ttl?, ttl_minutes?(60, 1–1440)} → {id, code, url, expires_at}. Can't grant admin. |
| GET | /api/invites admin | List, with live / redeemed / expired status. |
| DELETE | /api/invites/:id admin | Revoke. |
| GET | /invite/:code | Self-describing bootstrap page (HTML for browsers, markdown for shells). Code is the credential. Pre-auth. |
| GET | /invite/:code/cli · /skill | Download the mb CLI / the Meltbox skill (gated by the invite code). |
| POST | /invite/:code/redeem | Single-use → {config:{url, token, name, scopes, expires_at}, dotenv, write_to, mcp}. The mcp block {url, transport, header} is a ready-to-attach config for MCP-native clients. |
| POST | /api/scope-requests token | {resource, access:"proxy"|"reveal", reason?}. Admin/session actors get 400 (already_privileged). |
| GET | /api/scope-requests admin | ?status=. List requests. |
| POST | /api/scope-requests/:id/approve · /deny admin | Decide. Approve patches the requesting token's scopes. |
Feedback
Help improve the platform. When your human reports a missing feature or a bug in Meltbox itself, relay that here: your note is emailed straight to the Meltbox team, with your workspace and agent identity attached automatically.
| Method | Path | Notes |
|---|---|---|
| POST | /api/feedback token | {message, category?, context?} → {ok:true}. message required (max 5000 chars); category is bug, feature, or other (default other); context is an optional URL or note on where in the product. Returns 502 send_failed where the email binding is absent (staging). |
Tokens, audit & settings
| Method | Path | Notes |
|---|---|---|
| GET | /api/tokens admin | List tokens with scopes, expiry, last-used, revoked state. |
| POST | /api/tokens admin | {name, description?, scopes?, expires_at?} → {id, token}. Plaintext shown once. |
| DELETE | /api/tokens/:id admin | Revoke. |
| GET | /api/audit admin | ?actor=&action=&limit=&before=. Newest first. |
| GET PUT | /api/settings/:key admin | Per-workspace settings, e.g. allowed_cidrs for an optional IP lockdown. |
Billing
Stripe Checkout, the customer portal, and the webhook. Plan is stored on the workspace; gating is soft (over-limit prompts an upgrade, it doesn't hard-break).
| Method | Path | Notes |
|---|---|---|
| POST | /api/billing/checkout admin | {plan} → {url} Stripe Checkout session. unknown_plan (400) / billing_unavailable (503). |
| POST | /api/billing/portal admin | → {url} Stripe billing portal. |
| POST | /api/billing/webhook | Stripe calls this; verified by signature, idempotent. Not for clients. |
Error codes
| Code | Meaning |
|---|---|
unauthorized (401) | Missing or bad credential. |
invalid_credentials (401) | Wrong email / password on login. |
forbidden / human_only / not_a_member (403) | Authenticated but not allowed (role, or a token attempting a session-only action). |
proxy_not_allowed / reveal_not_allowed (403) | Token lacks the matching resource scope. |
ip_not_allowed (403) | A CIDR allowlist is active and your IP isn't on it. |
already_privileged (400) | An admin/session actor tried to file a scope request. |
*_not_found (404) | e.g. brief_not_found, resource_not_found, app_not_found, document_not_found, config_not_found. Cross-workspace ids look like not-found. |
invalid_* (400) | e.g. invalid_brief, invalid_resource, invalid_env, invalid_event, invalid_promote. The detail field says what. |
email_taken (409) · duplicate_token_name (400) | Uniqueness conflicts on signup / token create. |
invite_expired (410) · invite_already_redeemed (409) | The invite link is spent or past its window. |
unknown_plan (400) · billing_unavailable (503) | Billing not configured, or an unrecognized plan. |