Skip to main content

How authentication works in the Sally API

Every request to the Sally API, except the health check GET /health, carries a bearer token in the Authorization header. This page explains which tokens exist, how to send one and check that it works, what a token is allowed to see and how to find out who a token belongs to.

Authorization: Bearer <token>

Quick navigation

  1. Two kinds of token
  2. Send the token
  3. What a token may access
  4. Find out who you are
  5. Keep tokens safe

1. Two kinds of token​

The API accepts two kinds of token. Both are sent the same way, so the rest of your code does not need to know which one it holds.

TokenWhere it comes fromRight choice for
Personal access token, prefix sally_user_api_You create it in Sally under Settings > Integrations > Personal Access Token. It is bound to your user and does not expire unless you set an expiry date.An integration you run yourself. The Quickstart uses it.
OAuth 2.1 access tokenIssued by Sally.Something that other companies install and sign in to with their own Sally account.

2. Send the token​

Put the token in the Authorization header with the Bearer scheme. GET /v1.0/me is the simplest way to check that a token works, because it needs no ids:

curl "https://api.sally.io/v1.0/me" \
-H "Authorization: Bearer $SALLY_TOKEN"

With a valid token you get the user behind it:

200 OK
{
"userId": "11f967df-7dd1-4719-a9aa-46530940c443",
"email": "anna.berger@example.com",
"firstName": "Anna",
"lastName": "Berger",
"timeZone": "Europe/Berlin"
}
FieldTypeDescription
userIdstringUnique id of the user.
emailstringEmail address of the user.
firstNamestringFirst name.
lastNamestringLast name.
timeZonestring | nullIANA time zone of the user, for example Europe/Berlin. null if the user has no time zone set.

Without a token, or with an invalid one, the API answers 401 with the error body that every error uses:

401 Unauthorized
{
"message": "Ungültiger oder abgelaufener API-Key.",
"error": "Unauthorized",
"statusCode": 401
}

The message is meant for people, so branch on statusCode. Errors describes the body and the optional code field.

3. What a token may access​

A token acts as the user it belongs to. It reaches exactly the company accounts that user is a member of, with that user's role and visibility. There is no separate permission model to learn: if the person cannot see a recording in the app, the token cannot see it either.

You notice this in the status codes:

StatusMeaningWhat to do
401The bearer token is missing or invalid.Check the header and the token.
403The resource exists and you can see it, but the operation needs a higher role, for example admin or owner.Use a token of a user with that role.
404The resource does not exist or you are not allowed to see it.Check the id and whether the user has access in the app.
Important

Something you are not allowed to see answers 404, not 403, so a response never reveals that the data exists. Do not treat a 404 as proof that something was deleted. Errors lists all status codes.

4. Find out who you are​

Two calls tell you who the token belongs to and which company accounts it can address:

GET /v1.0/me is shown above. The memberships call returns every company account (directoryId) the token can address, with your role and license in it:

curl "https://api.sally.io/v1.0/me/directories/memberships" \
-H "Authorization: Bearer $SALLY_TOKEN"
200 OK
[
{
"directoryId": "5daeda15-8fc7-4693-a1e1-13119cceb461",
"directoryName": "Example GmbH",
"roleId": "164531fc-9be4-4de4-a183-89cc85d6a98c",
"licenseId": "f5f084e9-b993-425f-ad36-bfd5dc3f0e65",
"isBlocked": false
}
]
FieldTypeDescription
directoryIdstringId of the company account, used in paths like /v1.0/directories/{directoryId}/....
directoryNamestringDisplay name of the company account.
roleIdstringYour role in this company account. Resolve it via GET /v1.0/roles.
licenseIdstringYour license in this company account. Resolve it via GET /v1.0/licenses.
isBlockedbooleanWhether you are blocked in this company account.

5. Keep tokens safe​

Keep the token out of your source code and read it from the environment or a secret store at runtime, as every example here does with SALLY_TOKEN:

export SALLY_TOKEN="sally_user_api_..."
Recommendation

Treat a token like a password. Send it only in the Authorization header, never in a URL. Give it an expiry date if the integration is temporary, and revoke a token in Sally as soon as nobody needs it any more.