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

ModelHowUsed by
PublicNo 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 keyAuthorization: Bearer <key>. A key is scoped to exactly one project.Machine callers — CI, the Sorb MCP server, the plugin's publish step.
Dashboard sessionSigned 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.

MethodPathAuthWhat it does
POST/api/plugin-connect/startpublicRegister a pairing code as pending, before the plugin starts polling.
GET/api/plugin-connect/pollpublicPoll a pairing code. Returns pending, then ready with bridgeUrl/apiKey/orgId exactly once — the pairing is single-use.
POST/api/plugin/publish-tokenskey (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

MethodPathAuthWhat it does
GET/api/projectssession or keyList projects (session: every project in the org; key: only its own).
POST/api/projectssession or key (sk)Create a project.
GET/api/projects/:idsession or keyRead one project.
PATCH/api/projects/:idsession or key (sk)Update name, allowed_origins, or app_url (the canonical preview URL).
DELETE/api/projects/:idsession or key (sk)Delete a project. Cascades its keys and token-set commits.
POST/api/projects/:id/token-prsession 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/tokenizesession or key (sk)Start a tokenize run: scan the connected repo and open an additive PR — see Tokenize a repo.
GET/api/projects/:id/tokenizesession or keyRead the latest tokenize run's status, for polling (~every 2s while a run is active).
POST/api/projects/:id/preview-pushsessionPush 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-linksession or key (sk)Mint a live preview from a committed token-set version; returns a deep link.
GET/api/projects/:id/preview-statussession or keyReport 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.

MethodPathAuthWhat it does
GET/api/token-setssession or keyList a project's token sets (query: projectId).
POST/api/token-setssession or key (sk)Create a token set.
GET/api/token-sets/:id/headsession or keyRead the set's published HEAD as resolved tokens.
GET/api/token-sets/:id/diffsession or keyStructured {added, removed, changed} diff between two versions (query: from, to).
POST/api/token-sets/:id/publishsession or key (sk)Point HEAD at a version, optionally tagging it.
POST/api/token-sets/:id/rollbacksession or key (sk)Re-commit an earlier version's payload as a new HEAD — non-destructive, history is kept.
GET/api/token-sets/:id/versionssession or keyList version metadata, newest first.
GET/api/token-sets/:id/versions/:versionsession or keyRead one version's resolved tokens.
POST/api/token-sets/:id/apply-proposalsession or key (sk)Commit a new version with only the explicitly accepted token values replaced.
POST/api/token-sets/:id/apply-renamesession 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-variantsession 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.

MethodPathAuthWhat it does
GET/api/keyssessionList a project's keys, last4 only (query: projectId).
POST/api/keyssessionIssue a key (publishable or secret). The raw value is returned exactly once.
DELETE/api/keys/:idsessionRevoke a key (idempotent).
POST/api/keys/:id/rotatesessionRevoke the key and issue a fresh one of the same type/project; the new raw value is returned once.
GET/api/keys/countsessionCount of active keys across every project in the org.

Orgs

MethodPathAuthWhat it does
GET/api/orgs/resolvepublic (key)Resolve a publishable key to its org's connection config (bridgeMode, bridgeUrl, …) — the one call leaf/juice make to boot.
GET/api/orgs/settingssessionRead the org's bridge mode/URL and token-source settings.
PATCH/api/orgs/settingssession (admin)Update org settings.
GET/api/orgs/consentsessionRead the org's data-sharing consent flag.
POST/api/orgs/consentsession (admin)Set the org's data-sharing consent flag.
GET/api/orgs/memberssessionList org members.
PATCH/api/orgs/members/:userIdsession (admin)Change a member's role. Refuses demoting the org's last owner (409 last_owner).
GET/api/orgs/invitessessionList the org's invites.
POST/api/orgs/invitessession (admin)Create an invite; returns the redeemable code.
DELETE/api/orgs/invites/:idsession (admin)Revoke an invite.
PATCH/api/orgs/invites/:idsession (admin)Mark an invite as sent.
POST/api/orgs/invites/acceptsessionRedeem 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

CodeStatusMeaning
unauthenticated / invalid_api_key401No session and no valid Bearer key.
no_active_org400/403A session exists but has no active organization.
read_only403A publishable key called a route that needs a secret key.
forbidden403The signed-in user's org role doesn't grant the action (for example, approve_merge).
not_found / *_not_found404Unknown id, or it belongs to another org/project (tenant isolation never leaks existence).
invalid_request / invalid_input400The body or query failed validation; details carries the field errors.
project_limit_reached402The org's plan caps project count.
run_in_progress409A tokenize run is already active on this project.
unemittable_tokens422One or more tokens have non-scalar values and can't be emitted as CSS.
rate_limited429Back off; retryAfterMs (pairing routes) or a plain retry (others).

Next

Works with Figma. Not affiliated with, or endorsed by, Figma. Figma is a trademark of Figma, Inc.