diff --git a/concepts/scopes.mdx b/concepts/scopes.mdx
new file mode 100644
index 0000000..f69b7ac
--- /dev/null
+++ b/concepts/scopes.mdx
@@ -0,0 +1,139 @@
+---
+title: "Scopes and access"
+sidebarTitle: "Scopes"
+description: "Configure an API key to hold only what your code needs, see what an app asks for before you allow it, and read the 403 that names a missing scope."
+icon: "key"
+keywords: ["api scopes", "oauth scopes", "api key permissions", "least privilege api", "insufficient_scope"]
+canonical: "https://rendobar.com/docs/concepts/scopes"
+---
+
+
+
+Every credential carries scopes, and every endpoint states the one it needs. What a caller may do
+comes from what it was granted, not from whether it authenticated with an API key, an OAuth token
+or a dashboard session.
+
+## The scopes
+
+Six resources, each with a read and a write scope.
+
+| Resource | Read | Write | Write lets it |
+|---|---|---|---|
+| Jobs | `jobs:read` | `jobs:write` | Run jobs, which spends credits |
+| Assets | `assets:read` | `assets:write` | Upload and delete files |
+| Webhooks | `webhooks:read` | `webhooks:write` | Change where events are delivered |
+| Billing | `billing:read` | `billing:write` | Change the plan and what you are charged |
+| Organization | `orgs:read` | `orgs:write` | Add and remove members |
+| API keys | `keys:read` | `keys:write` | Create keys that act outside the session |
+
+Two rules cover the rest:
+
+- **`write` includes `read`.** A credential holding `jobs:write` can list jobs. You never need both.
+- **Self-description is never scoped.** Any credential can ask which workspace it acts for and what
+ limits apply, whatever else it holds.
+
+## Narrowing an API key
+
+Pass `scopes` when you create a key and it can do only that.
+
+
+
+```ts SDK
+const key = await rendobar.apiKeys.create({
+ name: "CI deploy key",
+ scopes: ["jobs:write", "assets:write"],
+});
+
+key.scopes; // what it actually got
+```
+
+```bash cURL
+curl -X POST https://api.rendobar.com/api-keys \
+ -H "Authorization: Bearer $RENDOBAR_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{"name":"CI deploy key","scopes":["jobs:write","assets:write"]}'
+```
+
+
+
+Omit `scopes` and the key gets everything the credential creating it can pass on, which for a
+dashboard session is everything a key may hold. The response reports what the key actually
+received, which is not always what was asked for.
+
+Two limits apply to every key, however it was made:
+
+- **A key can never hold `keys:write`.** A key that can create keys survives its own revocation, so
+ one leak becomes permanent access. Create and revoke keys from the dashboard, or from an app you
+ granted key management to.
+- **A key is never broader than what created it.** Asking for more than the creating credential
+ holds is refused rather than quietly narrowed.
+
+## What an app is asking for
+
+When you connect an app over OAuth, the consent screen lists what it requested, area by area, with
+what each one lets it do. You can grant less than it asked for, and the app receives exactly what
+you allowed.
+
+A grant is bound to **one workspace**, the one named on the screen when you approved it. The same
+app connected to a second workspace is a second grant.
+
+[Account, Security](https://app.rendobar.com/account/security) lists every connected app with what
+it can reach and where. Revoking deletes the grant and every stored token, so the app cannot get new
+access or renew what it has. An access token it already holds stays valid until it expires, which is
+under an hour.
+
+## When a scope is missing
+
+A request outside a credential's scopes is refused with `403` and a header naming what was missing,
+per [RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750#section-3.1).
+
+```http
+HTTP/1.1 403 Forbidden
+WWW-Authenticate: Bearer error="insufficient_scope",
+ error_description="This endpoint requires the jobs:write scope.",
+ scope="jobs:write"
+```
+
+```json
+{
+ "error": {
+ "code": "INSUFFICIENT_SCOPE",
+ "message": "This endpoint requires the jobs:write scope."
+ }
+}
+```
+
+It is a `403`, not a `401`: the credential authenticated correctly and presenting it again cannot
+help. Read the `scope` parameter to see what to ask for, then create a key that carries it or
+reconnect the app and allow that area.
+
+## Keys and apps that predate scopes
+
+Keys created before scopes hold everything a key may hold and keep working unchanged.
+
+Apps connected before scopes asked for `media:full`, which covers jobs, assets and reading billing.
+That does not include key management, so those connections cannot create or revoke API keys.
+Reconnecting the app grants it the current set.
+
+## See also
+
+- [MCP server](/mcp-server): connecting an app over OAuth
+- [Error codes](/support/errors): every code the API returns
+- [Plan limits](/support/limits): what a plan allows, which is a separate question from what a
+ credential may do
diff --git a/docs.json b/docs.json
index b4973ad..d635c6c 100644
--- a/docs.json
+++ b/docs.json
@@ -36,6 +36,7 @@
"pages": [
"concepts/job",
"concepts/generation",
+ "concepts/scopes",
"guides/webhooks",
"guides/callbacks"
]
diff --git a/mcp-server.mdx b/mcp-server.mdx
index d99294d..b1df3ee 100644
--- a/mcp-server.mdx
+++ b/mcp-server.mdx
@@ -25,7 +25,7 @@ canonical: "https://rendobar.com/docs/mcp-server"
}}
/>
-The MCP server at `https://api.rendobar.com/mcp` gives any MCP client 10 tools: submit a job, upload a file, poll status, and chain a completed job into the next one. Connect it with one command over OAuth, no API key to copy.
+The MCP server at `https://api.rendobar.com/mcp` gives an MCP client the whole platform: submit a job, upload a file, poll status, chain a completed job into the next one, and manage API keys. Connect it with one command over OAuth, no API key to copy.
## Connect
@@ -196,9 +196,22 @@ Cursor, Cline, Windsurf, Zed, and Continue all run the same `npx -y @rendobar/mc
Needs Node 20.10 or later. The server checks at startup and exits with a clear message if it's older.
+## Manage what it can reach
+
+Approving a connection grants access to one workspace, and you can take it back.
+
+**[Account, Security](https://app.rendobar.com/account/security)** lists every app you have
+connected, what each one can reach, and the workspace it was granted in. Revoking cuts an app off
+and forces it to ask again from the start.
+
+One caveat worth knowing: revoking deletes the grant and every stored token, so the app cannot get
+new access or renew what it has, but an access token it is already holding stays valid until it
+expires. That is under an hour.
+
## See also
- [FFmpeg reference](/jobs/ffmpeg): the job type `submit_job` calls in every example on this page
- [How a job works](/concepts/job): the job lifecycle and the `output` shape
- [Error codes](/support/errors): every code behind an `isError` response
- [Plan limits](/support/limits): file size caps and concurrency by plan
+- [Scopes](/concepts/scopes): what an app is asking for, and how to narrow it