# Scopes

> 18 permissions, resource-dot-action. Coarse on purpose: you should be able to pick the right ones without reading a manual.

A credential carries a set of scopes chosen when it is minted. An endpoint that needs a scope the credential lacks returns `403 insufficient_scope` and **names the missing scope**, so you never have to guess which one you forgot. Each endpoint's reference page shows the scope it needs.

## Every scope

| Scope | What it lets the holder do | Notes |
| --- | --- | --- |
| `account.read` | See your plan and remaining credits | Read-only preset |
| `projects.read` | Read your projects and their edit documents | Read-only preset |
| `projects.write` | Create, edit and delete projects |   |
| `media.read` | List the media you have uploaded | Read-only preset |
| `media.write` | Upload media and import it from a URL |   |
| `render.read` | Check render progress and fetch preview frames | Read-only preset |
| `render.write` | Start renders, using your monthly export allowance | Can spend |
| `ai.transcribe` | Transcribe audio, spending your credits | Can spend |
| `ai.speech` | Generate speech, spending your credits | Can spend |
| `ai.image` | Generate images and stickers, spending your credits | Can spend |
| `ai.video` | Generate video, spending your credits | Can spend |
| `ai.agent` | Run the AI editing agent, spending your credits | Can spend |
| `ai.analyze` | Analyse footage for scenes, faces and speech | Can spend |
| `stock.read` | Search the stock media library | Read-only preset |
| `webhooks.manage` | Manage webhook endpoints for this app | Never inherited |
| `plugins.invoke` | Call third-party plugins you have installed, sending them the arguments of each call | Never inherited |
| `telegram.users.read` | See the people using your Telegram bot and what they spent | Never inherited |
| `telegram.users.write` | Allow, block and set daily limits for the people using your Telegram bot | Never inherited |

**Can spend** means the scope can cost the owner real money (credits or export allowance). **Read-only preset** is the default offered when you mint a key. **Never inherited** means keys minted before the scope existed do not carry it.

## Presets

The console offers named bundles instead of 18 checkboxes, least privileged first:

- **Read only**: Look at projects, media and render status. Cannot change or spend anything. (`account.read`, `projects.read`, `media.read`, `render.read`, `stock.read`)
- **Build & render**: Everything above, plus creating and editing projects, uploading media and starting renders. (`account.read`, `projects.read`, `media.read`, `render.read`, `stock.read`, `projects.write`, `media.write`, `render.write`)
- **Build & render + AI**: Everything above, plus transcription, generation and the editing agent. Spends credits. (`account.read`, `projects.read`, `media.read`, `render.read`, `stock.read`, `projects.write`, `media.write`, `render.write`, `ai.transcribe`, `ai.speech`, `ai.image`, `ai.video`, `ai.agent`, `ai.analyze`)

> **Warning: A scope is necessary but never sufficient.** Passing the scope check does not skip the plan check or the credit check. `ai.video` on a plan without video generation still returns `402 plan_required`. Without this rule, a scope would be a privilege-escalation path: mint yourself a permission your plan cannot buy and get the feature free.

## Older keys

A key minted before scopes existed stores no scope list, and that means the legacy full grant: it behaves exactly as it always did. That grant never includes the scopes that did not exist then and that send data somewhere or reach real people: `webhooks.manage`, `plugins.invoke`, `telegram.users.read`, `telegram.users.write`. To use those, mint a new key.

## Choosing them

Mint time is the one moment least privilege is cheap. Afterwards it means rotating a credential that is already in production, which is why the picker is the first thing on the key form.

A useful set for a render pipeline (read projects, write them, render, poll):

```text
account.read projects.read projects.write render.read render.write
```

Note what is absent: no `media.write` (this pipeline points clips at URLs it already has) and no `ai.*` at all. If that key leaks, the worst case is somebody using your export allowance, not your credits.

## The app ceiling

Each app carries a ceiling: the most any of its keys or OAuth tokens may ever hold. New apps get the full vocabulary, so the ceiling is invisible until it is lowered, which happens for apps not verified for the expensive generation scopes. A key requesting more than its app's ceiling gets the intersection, and the ceiling is applied again on every request. Check what a credential actually ended up with:

```bash
curl https://api.vidmoat.com/v1/me \
  -H "Authorization: Bearer $VIDMOAT_KEY"
```

## Scopes that will never exist

**Posting to Vidmoat Social**: a public feed written to by third parties is a moderation liability. **Anything admin**. And **key management**: keys never mint keys, which is enforced at the route rather than by the absence of a scope, so it cannot be undone by adding one.

`webhooks.manage` is reserved for a future public webhook API. Today, webhook destinations are managed in the [console](https://developer.vidmoat.com/developer/webhooks); see [webhooks](https://developer.vidmoat.com/developer/docs/webhooks).

---

Source: https://developer.vidmoat.com/developer/docs/scopes
Previous: [Authentication](https://developer.vidmoat.com/developer/docs/authentication.md)
Next: [Projects and commands](https://developer.vidmoat.com/developer/docs/concepts/projects-and-commands.md)
All documentation: https://developer.vidmoat.com/llms-full.txt
