Authentication

Two ways in. For your own scripts, create a personal access token under Settings then API and send it as a bearer token; for an application other people connect, use OAuth instead. Tokens start with sk_live_ and are stored only as a SHA-256 hash, so the value is shown exactly once. Three scopes exist — read, write and delete — and deleting is deliberately separate from writing. A token acts as you and revoking one takes effect immediately.

Which one do you want

A personal access tokenYour own scripts, your own pipeline, a cron job. You make it, you hold it.
OAuthAn application that other people connect — including any MCP client. Nobody pastes a credential.

Both end at the same place: a bearer token that resolves to one person's own session. Everything on this page about scopes, what a credential can reach, and the audit trail applies to both.

Minting a token

Settings → API → New token. Give it a name that says where it will be used, so you know which one to revoke later.

Allow writes is the only scope control. Leave it off for anything that only needs to read.

The token is displayed once, at creation. It is the prefix undefined followed by 40 hexadecimal characters — 160 bits of randomness:

undefined<40 hex characters>

Only a SHA-256 hash is stored. There is no "show token" and no recovery — this is deliberate, and it means a database compromise does not yield working credentials.

This format is identical in shape to a Stripe live secret key. Secret scanners — GitHub push protection among them — will flag a Soleil token as a Stripe key and block the commit. That is useful when a real token leaks, and a nuisance when you paste an example into a repo. Write examples as undefined… rather than spelling out 40 plausible characters.

Using it

curl https://clusters.soleilpictures.com/api/v1/me \
  -H "Authorization: Bearer undefined…"

The scheme must be Bearer. A missing or malformed header gets 401 with a WWW-Authenticate header.

Scopes

Three: delete · read · write.

ScopeAllows
readEvery GET
writeCreating and changing — POST, PATCH
deleteRemoving — DELETE

Deleting is deliberately separate from writing. "Can add cards to my moodboard" and "can destroy my moodboard" are different levels of trust to hand out, and the distinction matters most when the caller is a language model driving MCP tools.

A token missing the scope it needs gets 403 with code: "insufficient_scope" and a required_scope field naming what was missing, rather than a generic refusal.

GET /me reports the scopes a token actually has — check it before assuming.

A token acts as you

The design point worth understanding:

The token resolves to your user, and then every request runs as that user, under the same row-level security the app runs under.

  • Boards you own — full access
  • Boards you were invited to as an editor — writable
  • Boards you can only view — reads succeed, writes get 403
  • Boards you cannot see — 404, never a confirmation they exist

There is no per-resource permission list on a token, because there does not need to be. This is also why a token cannot be used to escalate: it has no authority of its own.

Revoking

Settings → API → Revoke, effective immediately. The next request with that token gets 401.

Unknown, revoked and expired tokens all return the same 401 invalid token — the API does not distinguish, so a probe cannot learn which tokens once existed.

Up to 20 active tokens per account. Revoked ones do not count against it.

Expiry

Tokens do not expire by default. An optional lifetime can be set at creation.

Keeping a token safe

A undefined token is equivalent to your account for everything the API can reach. Treat it like a password.
  • Never put one in front-end code. CORS is open so it will work, and anyone who opens dev tools will have it.
  • Never commit one. Use an environment variable.
  • One token per integration, so you can revoke one without breaking the others.
  • Read-only unless you need writes.
  • Rotate by creating the new token, deploying it, then revoking the old one — both work simultaneously, so there is no downtime.

When a token should not be a person

A personal token acts as you, which means an integration built on one stops working the day you leave the workspace. For anything a team depends on, create a service account instead: a credential owned by the workspace, scoped to it, and unaffected by who comes and goes.

Rate limiting

1000 requests per hour, per token. (Service tokens default to 10000.)

Every response carries the current state, not just refusals:

HeaderMeaning
x-ratelimit-limitYour ceiling
x-ratelimit-remainingWhat is left in this window
x-ratelimit-resetUnix seconds when the window resets
retry-afterSeconds to wait — only on 429

A client that can only learn its budget by being rejected has to hit the wall to find it, so the numbers ride along on every call. GET /me reports the same figures in its body.

Separate tokens have separate budgets, which is another reason to give each integration its own.

Tokens and MCP

The MCP server uses the same token — it holds no credentials of its own and simply forwards yours. Everything on this page applies to it.

Frequently asked questions

I lost my token. Can I recover it?

No. Only a hash is stored, by design. Revoke it and create a new one.

Why is delete a separate scope from write?

Because "can add cards to my moodboard" and "can destroy my moodboard" are different levels of trust, especially when the caller is a language model.

How many tokens can I have?

Up to 20 active at once. Revoked ones do not count.

Machine-readable: /docs/api/authentication.md · /llms.txt