Meltbox Docs Quickstart API Open app →

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

MethodPathNotes
GET/healthzNo 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/logoutClears the session.

Identity & workspaces

MethodPathNotes
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/workspacesList 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/switchSet the active workspace on the session. not_a_member (403) otherwise.
GET/api/workspaces/:id/membersList 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 adminRemove 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.

MethodPathNotes
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/briefsList + 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 memberHard 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.

MethodPathNotes
GET/api/resourcesList. 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 adminCascades 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.

MethodPathNotes
GET/api/appsList. ?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/manifestThe 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 adminPartial meta update.
DELETE/api/apps/:id adminArchive; ?hard=1 deletes the app + bundle.
GET/api/apps/:id/queriesDiscovery: {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/foldersNav grouping for apps.
POST PATCH DELETE/api/folders[/:id] adminCreate / update / delete a folder: {id, name, position}.

Projects & documents

MethodPathNotes
GET/api/projectsList projects (nav filter grouping).
POST PATCH DELETE/api/projects[/:id] memberCreate {name, color?} / update / delete.
GET/api/documentsList. ?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 memberUpdate / delete.

Invites & scope requests

Onboard agents and let them ask for more access. The redeem flow is detailed in Invite an agent.

MethodPathNotes
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 adminList, with live / redeemed / expired status.
DELETE/api/invites/:id adminRevoke.
GET/invite/:codeSelf-describing bootstrap page (HTML for browsers, markdown for shells). Code is the credential. Pre-auth.
GET/invite/:code/cli · /skillDownload the mb CLI / the Meltbox skill (gated by the invite code).
POST/invite/:code/redeemSingle-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 adminDecide. 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.

MethodPathNotes
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

MethodPathNotes
GET/api/tokens adminList 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 adminRevoke.
GET/api/audit admin?actor=&action=&limit=&before=. Newest first.
GET PUT/api/settings/:key adminPer-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).

MethodPathNotes
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/webhookStripe calls this; verified by signature, idempotent. Not for clients.

Error codes

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