Scopes and grants
A scope is what your app may ask for, a grant is what one person allowed, and a run's scopes narrow it further.
Three things limit what your app can do for a person: the scopes your app may ask for, the grant that person approved, and the scopes list of each run you start. Each can only narrow the one before.
| Layer | Set by | Where |
|---|---|---|
| What the app may ask for | You | The console, app page, What it asks for tab → Add permissions |
| The grant | One person | The consent page, when they connect your app |
A run's scopes | Your code | runs.start({scopes: […]}) |
What your app may ask for
An app can only ask for scopes it has been given. A new app starts with crtr:llm and crtr:act. On the What it asks for tab, Add permissions searches the full list: agent access, parts of the person's space, and the tools of each provider. Take scope strings from that list or from providers.tools. Do not invent them. Without sms:send on the list, the consent page does not offer it and providers.call for sms is refused.
The same tab holds What your app requires (providers the app does not work without: see Providers) and Monthly spend cap, the most your app can spend on each person's AI subscription in a month. People see the cap on the consent page.
The protocol scopes
openid, email and profile are available to every app and are not saved in the list. You request them in authorizeUrl for sign-in: openid for the person's id, email for their email, profile for their name and photo. A request with only these scopes signs the person in without creating a grant or a connection.
The grant
When a person connects your app, the consent page shows each scope you asked for and the spend cap. What they approve is the grant. Your authorizeUrl should request only the scopes your app uses, because the grant holds what was requested and approved. A person can change or remove the grant from their Northlight account. Link to that page with await oauth.accountAppUrl(); never build the path yourself.
If you change what the app asks for, people already connected approve the new list the next time they sign in. Lowering the spend cap applies to everyone at once; raising it reaches each person only after they approve again.
What each scope gates
A run can only do what the grant holds and the run's own scopes allow. Narrowing a run's scopes removes abilities, so pass the smallest list that works.
| Scope | A run (or your server) with it can | Without it |
|---|---|---|
crtr:llm | Use the person's AI subscription for the run's model calls. List it in every run's scopes. | The run has no model access. |
crtr:act | Start runs (runs.start) and use a shell. Every bash call on the hosted (Linux) runtime needs it. | runs.start is refused with 403 scope_missing. Add it on the What it asks for tab and list it in the run's scopes. |
crtr:schedule | Create, pause, resume and run schedules. | Schedule calls are refused. |
crtr:memory:read:user, crtr:files:read:user:., crtr:files:rw:user:. | Read the person's documents beyond their public ones; read or write the person's files. | The run sees only the person's public documents and its own app space. |
sms:send (a provider scope) | Call the sms provider's send tool, which texts the person's own verified phone. | providers.call is refused. |
crtr:act requires crtr:llm, so drop both together. A run that only reasons and submits a typed result needs only crtr:llm.
Other scopes follow the same shape. A provider scope is <provider>:<tool group>; see Providers. Scopes for the person's files and documents are named crtr:files:… and crtr:memory:…; copy them from Add permissions.
A run's scopes
await runtime.runs.start({prompt, scopes: ['crtr:llm']});A run's list must stay inside the grant, and a child agent never regains a scope its parent's list dropped. Leaving scopes out gives the run everything the grant holds. Prefer a list.
Your app's own space
Your app has its own space on each person's runtime: documents and files owned by your app. Your server and runs started without a scopes list read and write it freely. A run started with a scopes list needs crtr:memory:read:app:<your client id> or crtr:memory:rw:app:<your client id> to touch it. Another app's space is reachable only where that app allows it.
Checked and not checked
From the SDK source, the directory console source and the existing SDK guide. 403 scope_missing for a missing crtr:act and the consent page's behavior are not yet checked on production.
Apps and sign-in
What registering an app creates, how an app proves who it is with a key pair, and the authorization-code flow with PKCE that OAuth2Client runs.
Connections
A connection is the stored token set for one person. How ConnectionStore keeps it, how it refreshes, and what to do when the directory refuses a refresh.