Need to manage API keys, view logs, or check usage?Open the Developer portal →

Scopes

The seven permissions an OAuth app can request, and the endpoints each one unlocks.

A scope is one line on the consent screen and one bundle of endpoints behind it. You send them space-delimited in the scope query parameter, and the token response echoes back the set you were actually granted.

scope=profile:read teams:read materials:read

The scopes

ParameterTypeDescription
profile:readreadThe user's name, email address and profile picture. Unlocks GET /public/me.
teams:readreadThe names and ids of the teams the user granted. Unlocks GET /public/teams — the only reliable way to learn which x-team-id values your token is currently good for.
materials:readreadList and read materials, including property values the team has already revealed. Unlocks GET /public/materials, GET /public/materials/:id and GET /public/materials/:id/properties/:propertyId, plus the legacy GET /public/library/search.
materials:pullspends creditsReveal material properties on the user's behalf. Unlocks POST /public/materials/:id/properties/pull and the legacy POST /public/library/pull-datapoint. Debits the team's credit balance.
processes:readreadList and read processes, including impacts the team has already revealed. Unlocks GET /public/processes, GET /public/processes/:id and GET /public/processes/:id/impacts/:impactId.
processes:pullspends creditsReveal processes and their impacts on the user's behalf. Unlocks POST /public/processes/:id/pull and POST /public/processes/:id/impacts/pull. Debits the team's credit balance.
impact_methods:readreadThe impact methods available to the team. Unlocks GET /public/impact-methods.
⚠
materials:pull and processes:pull spend the team's credits
The two pull scopes are not read permissions — every call they authorize debits real credits from the balance of the team named in x-team-id. That is the user's money, spent by your code, without a second confirmation. Circa flags these scopes distinctly on the consent screen for exactly that reason.

Only ask for them if pulling is a core part of what your app does, tell your users before you spend, and never pull in a retry loop — a failed request that actually succeeded server-side, retried five times, is five debits. A pull that runs out of credits returns 409 Conflict.

Calling an endpoint you were not granted

The scope check runs before the endpoint does, and rejects the call with a 403 naming the first scope you are missing:

403Forbidden
{
  "error": "insufficient_scope",
  "statusCode": 403,
  "message": "This access token is missing the 'materials:read' scope."
}

Branch on error, not on the message. This check applies to OAuth access tokens only — API-key requests carry no scopes and are never rejected by it.

Ask for the minimum

The consent screen is a conversion funnel. Every scope you add is another line the user reads and another reason to cancel, and the two pull scopes raise the bar considerably. Practical rules:

  • Request only what your first release actually calls. You can ask for more later.
  • teams:read is nearly always worth including — without it you are guessing at x-team-id values.
  • profile:readonly if you display or match on the user's identity.
  • Read the scope field in the token response and gate your UI on it. Never assume the grant matches the request.
  • Handle a 403 insufficient_scope from an endpoint whose scope you did not get, rather than crashing.

Re-authorization replaces, it does not merge

When you need a new scope — or a team the user did not originally tick — send the user through the authorization flow again with the full set you want. The new consent replaces the previous one; it is not unioned with it.

So if a user first granted materials:read processes:read and you later send them through asking only for materials:pull, you end up with materials:pull and nothing else — the reads are gone. The same applies to teams: the team selection on the new consent screen is the new list, not an addition to the old one. Always request the complete set you want to end up with.

ℹ
Your app can only ever request scopes that are ticked on its registration in the Developer portal. Asking for one outside that set fails with invalid_scope before the user sees a consent screen — so add the scope to the app first, then change your authorize URL.