Sign in with Tabbio
Scopes and consent
The four user scopes, what the consent screen looks like, and how to handle a person who grants less than you asked for.
Scopes are how you say what your app needs. The consent screen is where the person decides how much of that they are willing to give. Those two are not always the same set, which is the single most important thing to design for.
The four user scopes
| Scope | Grants |
|---|---|
profile | Name, username, headline, language and public profile link. The photo is only shared if the user allows it. |
email | The verified email address on the Tabbio account. |
cv:read | The CV the user chose to share, kept up to date automatically, as structured data and as a PDF. |
cv:contact | Phone number, full address, date of birth, nationality and similar personal details from that CV. |
Ask for the least that makes your product work. A screen that requests cv:contact
when it only ever renders a job history reads as overreach, and people decline.
What the person sees
The consent screen shows your app name, your logo if you set one, and a summary of what you asked for. Three presets sit at the top, and only the ones your requested scopes can satisfy are offered:
| Field | Type | Description |
|---|---|---|
| Sign in only | profile, email | Identity, no CV. |
| Share my CV | profile, email, cv:read | Identity plus the CV, without private contact details. |
| Share everything | All requested | Adds private contact details when you asked for cv:contact. |
Below the presets are the individual choices:
- Name, username and photo. Required when you request
profile. The photo has its own toggle: a person can share their identity and keep their picture off. - Email address. Required when you request
email. - Which CV. Their main CV is preselected. They can pick a different one, or turn CV sharing off entirely even though you asked for it.
- Private contact details. Only shown when you request
cv:contact.
profile and email cannot be unchecked when requested, because an app cannot sign
somebody in without knowing who they are. Everything else is theirs to refuse.
Partial grants
The token response tells you what you actually hold:
{
"access_token": "tbo_at_9QpX2f",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tbo_rt_4Kd81a",
"scope": "profile email"
}You asked for profile email cv:read. You were given two of the three. This is legal
OAuth and your code has to handle it.
const granted = new Set(tokens.scope.split(" "));
if (granted.has("cv:read")) {
await importCv(accessToken);
} else {
// Not an error state. Show the part of your product that works without a CV,
// and offer a way to share one later.
await promptForManualUpload();
}Do not treat a partial grant as a failure
Sending the person straight back through the authorization flow because they withheld
one scope trains them to decline harder. Degrade instead: build the feature that
needs cv:read around a check, and put a "Share your CV" action next to it.
Calling an endpoint without its scope returns 403:
{
"data": null,
"error": {
"code": "OAUTH_INSUFFICIENT_SCOPE",
"message": "This token does not carry the cv:read scope."
},
"meta": null
}Asking again later
Send the person through the authorization flow again with the wider scope. The
consent screen recognises the existing connection, says your app is already connected,
and pre-fills their previous choices. Approving updates the same connection rather than
creating a second one, and the new token carries the new set.
There is no incremental grant endpoint. Re-running the flow is the mechanism.
What cv:contact covers
Two things move when cv:contact is withheld, and both are in the CV read:
personalDetailsis null instead of an object.document.socials.phoneis absent from the socials map.
personalDetails holds the fields a Gulf employer asks for and a candidate may not
want to hand to every platform:
| Field | Type |
|---|---|
| dateOfBirth | string or null |
| gender | string or null |
| maritalStatus | string or null |
| nationality | string or null |
| residence | string or null |
| visaResidency | string or null |
| nationalService | string or null |
| dependents | string or null |
Everything else in the CV is not private and is always present: location, links, work history, education, skills, languages, certifications and projects. A CV without those is not a CV.
Two neighbouring permissions are governed elsewhere, which is worth knowing because they look like contact details:
| Permission | What you get without it |
|---|---|
cv:contact | personalDetails is null, and document.socials.phone is absent. |
email | document.contactEmail is null, and email is absent from GET /v1/me. |
Profile photo | document.avatarUrl and the user's avatarUrl are both null. |
So a cv:read grant without email gives you a CV whose contactEmail is null, even
though the CV itself came through.
// null, not an empty object, and phone is absent rather than empty.
const nationality = cv.personalDetails?.nationality ?? null;
const phone = cv.document.socials.phone ?? null;What the person can do afterwards
Settings > Connected apps in Tabbio lists every app they have connected. From there they can:
- see exactly what your app can read;
- switch which CV you see, which changes your next read with no action from you;
- turn the photo or private details off;
- disconnect, which revokes every token and makes the durable PDF link answer 404.
Design for that last one. A connection ending is a normal event, not an incident: the
next call returns OAUTH_INVALID_TOKEN and the fix is to ask the person to connect
again, not to retry.