Sorb™ Cloud REST API
When you finish this page you know every customer-facing route on
app.sorbcloud.com, what it needs to authenticate, and what it
does. You need a Sorb account for anything beyond plugin pairing; most
routes also accept a project API key, so CI and agent callers never need a
browser session.
All paths are relative to https://app.sorbcloud.com. Every response is JSON;
errors are { "error": "<code>", "message"?: "<string>" } with a 4xx/5xx
status — the error code is stable, the message is for humans.
This page lists only the customer-touched surface. It omits internal
routes (admin/*, ai/*, billing/*, webhooks, dev/*, e2e-hooks/*,
sensor/ingest, render/*, feed/*, waitlist, support, onboarding/*)
and the OAuth connector routes (connectors/*), which are covered as a
sentence in the plugin and cloud guides rather than as a REST reference.
Auth models
| Model | How | Used by |
|---|---|---|
| Public | No credential — an unguessable code (or, for orgs/resolve, the key itself) is the credential. | Plugin pairing, resolving a publishable key to its org config. |
| Bearer project key | Authorization: Bearer <key>. A key is scoped to exactly one project. | Machine callers — CI, the Sorb MCP server, the plugin's publish step. |
| Dashboard session | Signed in at app.sorbcloud.com (browser cookie). | Everything a human does from the dashboard. |
Most routes accept either a session or a project key — the tables below
mark those session or key. A publishable key (sorb_pk_…) is
read-scoped on this API: it can call reads, and the one deliberate exception
below. A secret key is required for everything else that writes. Routes
marked (sk) need a secret key when called by key; a session is always
read-write (route-level role checks, where noted, still apply on top).
One deliberate exception:
POST /api/plugin/publish-tokens accepts a publishable key too,
because that's the only key type the plugin's sign-in pairing ever issues
it — a key can still only publish to its own project. This is a
bounded relaxation, not the general rule.
Publishable keys are meant to be embedded in a client — the plugin and the React SDK both boot with one. Never embed a secret key in anything that ships to a browser or device.
Plugin pairing & publish
The Figma plugin (Canopy) has no browser session, so it authenticates by a short-lived pairing code instead — see Connect flows for the user-facing walkthrough.
| Method | Path | Auth | What it does |
|---|---|---|---|
| POST | /api/plugin-connect/start | public | Register a pairing code as pending, before the plugin starts polling. |
| GET | /api/plugin-connect/poll | public | Poll a pairing code. Returns pending, then ready with bridgeUrl/apiKey/orgId exactly once — the pairing is single-use. |
| POST | /api/plugin/publish-tokens | key (pk or sk) | Persist the plugin's current Figma-Variable tokens as a new version on the key's own project — this is what backs Save to project in the plugin. |
Projects
| Method | Path | Auth | What it does |
|---|---|---|---|
| GET | /api/projects | session or key | List projects (session: every project in the org; key: only its own). |
| POST | /api/projects | session or key (sk) | Create a project. |
| GET | /api/projects/:id | session or key | Read one project. |
| PATCH | /api/projects/:id | session or key (sk) | Update name, allowed_origins, or app_url (the canonical preview URL). |
| DELETE | /api/projects/:id | session or key (sk) | Delete a project. Cascades its keys and token-set commits. |
| POST | /api/projects/:id/token-pr | session or key (sk) | Open (or converge on) a GitHub pull request for a token version — see Token version to GitHub PR. |
| POST | /api/projects/:id/tokenize | session or key (sk) | Start a tokenize run: scan the connected repo and open an additive PR — see Tokenize a repo. |
| GET | /api/projects/:id/tokenize | session or key | Read the latest tokenize run's status, for polling (~every 2s while a run is active). |
| POST | /api/projects/:id/preview-push | session | Push a token set (or an explicit token map) to the hosted bridge as an ephemeral preview; returns a deep link. |
| POST | /api/projects/:id/preview-link | session or key (sk) | Mint a live preview from a committed token-set version; returns a deep link. |
| GET | /api/projects/:id/preview-status | session or key | Report the project's bridge preview liveness: linked, no_preview, or unreachable. |
token-pr, tokenize, and both preview-mint routes require the approve_merge
org role when called from a session (a key's write scope is its own authorization).
Token sets
A token set is a named, versioned collection of resolved tokens. head, diff
and versions* are reads and free of any entitlement gate.
| Method | Path | Auth | What it does |
|---|---|---|---|
| GET | /api/token-sets | session or key | List a project's token sets (query: projectId). |
| POST | /api/token-sets | session or key (sk) | Create a token set. |
| GET | /api/token-sets/:id/head | session or key | Read the set's published HEAD as resolved tokens. |
| GET | /api/token-sets/:id/diff | session or key | Structured {added, removed, changed} diff between two versions (query: from, to). |
| POST | /api/token-sets/:id/publish | session or key (sk) | Point HEAD at a version, optionally tagging it. |
| POST | /api/token-sets/:id/rollback | session or key (sk) | Re-commit an earlier version's payload as a new HEAD — non-destructive, history is kept. |
| GET | /api/token-sets/:id/versions | session or key | List version metadata, newest first. |
| GET | /api/token-sets/:id/versions/:version | session or key | Read one version's resolved tokens. |
| POST | /api/token-sets/:id/apply-proposal | session or key (sk) | Commit a new version with only the explicitly accepted token values replaced. |
| POST | /api/token-sets/:id/apply-rename | session or key (sk) | Apply accepted renames alias-preserving: the old id stays in the set as a deprecated alias, never hard-deleted. |
| POST | /api/token-sets/:id/apply-variant | session or key (sk) | Create or update the set's dark-mode sibling from accepted edits; the light set is never mutated. |
Keys
Key management is dashboard-session-only — a key can't be used to mint, rotate, or revoke keys.
| Method | Path | Auth | What it does |
|---|---|---|---|
| GET | /api/keys | session | List a project's keys, last4 only (query: projectId). |
| POST | /api/keys | session | Issue a key (publishable or secret). The raw value is returned exactly once. |
| DELETE | /api/keys/:id | session | Revoke a key (idempotent). |
| POST | /api/keys/:id/rotate | session | Revoke the key and issue a fresh one of the same type/project; the new raw value is returned once. |
| GET | /api/keys/count | session | Count of active keys across every project in the org. |
Orgs
| Method | Path | Auth | What it does |
|---|---|---|---|
| GET | /api/orgs/resolve | public (key) | Resolve a publishable key to its org's connection config (bridgeMode, bridgeUrl, …) — the one call leaf/juice make to boot. |
| GET | /api/orgs/settings | session | Read the org's bridge mode/URL and token-source settings. |
| PATCH | /api/orgs/settings | session (admin) | Update org settings. |
| GET | /api/orgs/consent | session | Read the org's data-sharing consent flag. |
| POST | /api/orgs/consent | session (admin) | Set the org's data-sharing consent flag. |
| GET | /api/orgs/members | session | List org members. |
| PATCH | /api/orgs/members/:userId | session (admin) | Change a member's role. Refuses demoting the org's last owner (409 last_owner). |
| GET | /api/orgs/invites | session | List the org's invites. |
| POST | /api/orgs/invites | session (admin) | Create an invite; returns the redeemable code. |
| DELETE | /api/orgs/invites/:id | session (admin) | Revoke an invite. |
| PATCH | /api/orgs/invites/:id | session (admin) | Mark an invite as sent. |
| POST | /api/orgs/invites/accept | session | Redeem an invite code, joining the org at the invite's role. |
The full allowlist backing these tables is committed as
www/app/docs/cloud/_routes.js in this site's source, alongside a comment
listing every route family this reference deliberately omits.
One request per auth model
Public + code — a plugin pairs without ever holding a credential:
curl -X POST https://app.sorbcloud.com/api/plugin-connect/start \
-H "content-type: application/json" \
-d '{"code":"<a-high-entropy-code-you-generate>"}'
# -> {"status":"pending"}
curl "https://app.sorbcloud.com/api/plugin-connect/poll?code=<the-same-code>"
# -> {"status":"pending"} while you wait, then once:
# -> {"status":"ready","bridgeUrl":"...","apiKey":"sorb_pk_...","orgId":"..."}
Bearer project key — publish a token set from a script or CI job:
curl -X POST https://app.sorbcloud.com/api/plugin/publish-tokens \
-H "authorization: Bearer <your-project-key>" \
-H "content-type: application/json" \
-d '{"tokens":{"color-action-primary":"#f26722"},"name":"CI publish"}'
Dashboard session — the keys and org-administration routes have no key
path; they manage the credentials and membership a key represents, so they're
driven from a signed-in browser at app.sorbcloud.com, not curl. Every other
route above accepts the same Bearer project key shown here instead of a
session — that's how the Sorb MCP server and CI reach them.
Errors you'll actually see
| Code | Status | Meaning |
|---|---|---|
unauthenticated / invalid_api_key | 401 | No session and no valid Bearer key. |
no_active_org | 400/403 | A session exists but has no active organization. |
read_only | 403 | A publishable key called a route that needs a secret key. |
forbidden | 403 | The signed-in user's org role doesn't grant the action (for example, approve_merge). |
not_found / *_not_found | 404 | Unknown id, or it belongs to another org/project (tenant isolation never leaks existence). |
invalid_request / invalid_input | 400 | The body or query failed validation; details carries the field errors. |
project_limit_reached | 402 | The org's plan caps project count. |
run_in_progress | 409 | A tokenize run is already active on this project. |
unemittable_tokens | 422 | One or more tokens have non-scalar values and can't be emitted as CSS. |
rate_limited | 429 | Back off; retryAfterMs (pairing routes) or a plain retry (others). |
Next
- Cloud & accounts — sign up, orgs, and keys.
- Tokenize a repo — the
tokenizepipeline in depth. - Token version to GitHub PR — the
token-prpipeline in depth. - Troubleshooting — symptom → cause → fix.
Works with Figma. Not affiliated with, or endorsed by, Figma. Figma is a trademark of Figma, Inc.