Scopes

Overview

Scopes define what your app can access on behalf of a user. They are declared when you register your app and shown to the user on the consent screen. Users grant or deny each scope individually. Request only what you need.

Available scopes

ScopeWhat it grants
identityReturns sub, an opaque stable user ID. It is unique per user and safe to store as your own key. It is not an email address and not a name.
avatarReturns avatar_url, the user's profile photo.
themeReturns theme, so your interface can open in the light or dark mode the user already prefers.
profileUmbrella covering all three above. Granting it is equivalent to granting identity, avatar and theme together.
There is no email scope. Cladior never returns a user's email address to a connected app, at any scope. If your product needs to contact a user, collect an address from them directly.
Billing needs no scope. Charging happens through the proxy against the rate the user accepted, so a metered product works with no scopes granted at all. Scopes cover profile data only, never money.

Declaring scopes

Scopes are set in the developer dashboard when you create or edit your app. They cannot be changed at runtime.

Adding a scope does not re-prompt anyone who already connected. Their grant was written when they consented and is never revisited, so they keep the scopes they originally approved and the new field simply never arrives for them. New connections get the new set.

Which is the right behaviour, even though it reads as a trap: a scope somebody approved is the only scope you should have, and quietly widening it later would make consent meaningless. But it does mean adding a scope after launch is a migration rather than an edit.

Two ways through it. Ship the scope and let it apply to new connections, accepting that existing users will not have it until they reconnect for some other reason. Or revoke the affected grants from your dashboard, which sends them through consent again on their next visit, where they see the new scope and approve it explicitly.

/oauth/userinfo returns an X-Cladior-Scopes-Missing header listing any scope your app now requests that this particular grant does not cover. If a field you expect is absent, that header says why.

Reading what you were granted

There is no list of granted scopes to read. You find out what you were given by calling the userinfo endpoint and seeing which fields came back: a scope you were not granted simply produces no field.

const res = await fetch(
  `https://www.cladior.com/oauth/userinfo?site=${APP_ID}`,
  { headers: { Authorization: `Bearer ${sessionToken}` } },
);
const info = await res.json();

// identity granted -> info.sub is set
// avatar granted   -> info.avatar_url is set
// theme granted    -> info.theme is set
if (info.avatar_url) showAvatar(info.avatar_url);

// Absent field you were expecting? This names the reason.
res.headers.get('X-Cladior-Scopes-Missing');

Treat every field as optional. If your product needs one it did not get, say what the permission is for and offer to reconnect, rather than assuming it is present.

Scope minimization

The consent screen shows every scope you declared. A long list of scopes reduces conversion. Request only what your product needs at launch. You can add more scopes later as you ship features that require them.

The true minimum is none at all. Billing runs through the proxy without any scope, so an app that only meters usage can request nothing and still work. Add identity when you need a stable key to store your own data against, and the other two only when you actually use them.