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

ScopeGrants
profileName, username, headline, language and public profile link. The photo is only shared if the user allows it.
emailThe verified email address on the Tabbio account.
cv:readThe CV the user chose to share, kept up to date automatically, as structured data and as a PDF.
cv:contactPhone 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:

FieldTypeDescription
Sign in onlyprofile, email

Identity, no CV.

Share my CVprofile, email, cv:read

Identity plus the CV, without private contact details.

Share everythingAll 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:

JSON
{
  "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.

after the exchange
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:

JSON
{
  "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:

  • personalDetails is null instead of an object.
  • document.socials.phone is 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:

FieldType
dateOfBirthstring or null
genderstring or null
maritalStatusstring or null
nationalitystring or null
residencestring or null
visaResidencystring or null
nationalServicestring or null
dependentsstring 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:

PermissionWhat you get without it
cv:contactpersonalDetails is null, and document.socials.phone is absent.
emaildocument.contactEmail is null, and email is absent from GET /v1/me.
Profile photodocument.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.

reading defensively
// 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.