Wie die Authentifizierung in der Sally API funktioniert
Jede Anfrage an die Sally API, außer der Verfügbarkeitsprüfung GET /health, trägt ein Bearer-Token im Header Authorization. Diese Seite erklärt, welche Tokens es gibt, wie du eines mitschickst und prüfst, ob es funktioniert, was ein Token sehen darf und wie du herausfindest, wem ein Token gehört.
Authorization: Bearer <token>
Schnellnavigation
- Zwei Arten von Token
- Token mitschicken
- Was ein Token darf
- Herausfinden, wer du bist
- Tokens sicher aufbewahren
1. Zwei Arten von Token
Die API nimmt zwei Arten von Token an. Beide werden gleich mitgeschickt, der Rest deines Codes muss also nicht wissen, welche er gerade hat.
| Token | Woher es kommt | Richtige Wahl für |
|---|---|---|
Persönliches Zugangstoken, Präfix sally_user_api_ | Du legst es in Sally unter Einstellungen > Integrationen > Personal Access Token an. Es hängt an deinem Nutzer und läuft nicht ab, solange du kein Ablaufdatum setzt. | Eine Anbindung, die du selbst betreibst. Der Schnellstart nutzt es. |
| OAuth-2.1-Zugangstoken | Von Sally ausgestellt. | Etwas, das andere Unternehmen installieren und bei dem sie sich mit ihrem eigenen Sally-Konto anmelden. |
2. Token mitschicken
Setz das Token mit dem Schema Bearer in den Header Authorization. GET /v1.0/me ist der einfachste Weg zu prüfen, ob ein Token funktioniert, weil der Aufruf keine Ids braucht:
- cURL
- JavaScript
- Python
curl "https://api.sally.io/v1.0/me" \
-H "Authorization: Bearer $SALLY_TOKEN"
const response = await fetch('https://api.sally.io/v1.0/me', {
headers: {Authorization: `Bearer ${process.env.SALLY_TOKEN}`},
});
if (response.status === 401) throw new Error('Token fehlt oder ist ungültig');
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const me = await response.json();
console.log(`Angemeldet als ${me.firstName} ${me.lastName} <${me.email}>`);
import os
import requests
response = requests.get(
"https://api.sally.io/v1.0/me",
headers={"Authorization": f"Bearer {os.environ['SALLY_TOKEN']}"},
)
if response.status_code == 401:
raise SystemExit("Token fehlt oder ist ungültig")
response.raise_for_status()
me = response.json()
print(f"Angemeldet als {me['firstName']} {me['lastName']} <{me['email']}>")
Mit einem gültigen Token bekommst du den Nutzer dahinter:
{
"userId": "11f967df-7dd1-4719-a9aa-46530940c443",
"email": "anna.berger@example.com",
"firstName": "Anna",
"lastName": "Berger",
"timeZone": "Europe/Berlin"
}
| Feld | Typ | Beschreibung |
|---|---|---|
userId | string | Eindeutige Id des Nutzers. |
email | string | E-Mail-Adresse des Nutzers. |
firstName | string | Vorname. |
lastName | string | Nachname. |
timeZone | string | null | IANA-Zeitzone des Nutzers, zum Beispiel Europe/Berlin. null, wenn keine Zeitzone gesetzt ist. |
Ohne Token oder mit einem ungültigen antwortet die API mit 401 und dem Fehler-Body, den jeder Fehler nutzt:
{
"message": "Ungültiger oder abgelaufener API-Key.",
"error": "Unauthorized",
"statusCode": 401
}
Die message ist für Menschen gedacht, verzweige deshalb auf statusCode. Fehler beschreibt den Body und das optionale Feld code.
3. Was ein Token darf
Ein Token handelt als der Nutzer, dem es gehört. Es erreicht genau die Unternehmenskonten, in denen dieser Nutzer Mitglied ist, mit dessen Rolle und Sichtbarkeit. Es gibt kein zweites Rechtemodell zu lernen: Was die Person in der App nicht sieht, sieht auch das Token nicht.
Das merkst du an den Statuscodes:
| Status | Bedeutung | Was du tust |
|---|---|---|
| 401 | Das Bearer-Token fehlt oder ist ungültig. | Header und Token prüfen. |
| 403 | Die Ressource existiert und du siehst sie, aber die Operation verlangt eine höhere Rolle, etwa Admin oder Owner. | Ein Token eines Nutzers mit dieser Rolle verwenden. |
| 404 | Die Ressource gibt es nicht oder du darfst sie nicht sehen. | Id prüfen und nachsehen, ob der Nutzer in der App Zugriff hat. |
Etwas, das du nicht sehen darfst, antwortet mit 404, nicht mit 403. So verrät eine Antwort nie, dass es die Daten überhaupt gibt. Werte ein 404 deshalb nicht als Beweis, dass etwas gelöscht wurde. Fehler listet alle Statuscodes.
4. Herausfinden, wer du bist
Zwei Aufrufe verraten dir, wem das Token gehört und welche Unternehmenskonten es ansprechen darf:
- GET
/v1.0/meGibt die Basisinformationen des authentifizierten Nutzers zurückAusprobieren - GET
/v1.0/me/directories/membershipsListet die Mitgliedschaften des authentifizierten Nutzers über alle Unternehmenskonten hinweg aufAusprobieren
GET /v1.0/me steht weiter oben. Der Aufruf der Mitgliedschaften liefert jedes Unternehmenskonto (directoryId), das das Token ansprechen darf, mit deiner Rolle und Lizenz darin:
- cURL
- JavaScript
- Python
curl "https://api.sally.io/v1.0/me/directories/memberships" \
-H "Authorization: Bearer $SALLY_TOKEN"
const response = await fetch('https://api.sally.io/v1.0/me/directories/memberships', {
headers: {Authorization: `Bearer ${process.env.SALLY_TOKEN}`},
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const memberships = await response.json();
const usable = memberships.filter((membership) => !membership.isBlocked); // gesperrte weglassen
console.log(usable.map(({directoryId, directoryName}) => `${directoryId} ${directoryName}`));
import os
import requests
response = requests.get(
"https://api.sally.io/v1.0/me/directories/memberships",
headers={"Authorization": f"Bearer {os.environ['SALLY_TOKEN']}"},
)
response.raise_for_status()
usable = [m for m in response.json() if not m["isBlocked"]] # gesperrte weglassen
for membership in usable:
print(membership["directoryId"], membership["directoryName"])
[
{
"directoryId": "5daeda15-8fc7-4693-a1e1-13119cceb461",
"directoryName": "Example GmbH",
"roleId": "164531fc-9be4-4de4-a183-89cc85d6a98c",
"licenseId": "f5f084e9-b993-425f-ad36-bfd5dc3f0e65",
"isBlocked": false
}
]
| Feld | Typ | Beschreibung |
|---|---|---|
directoryId | string | Id des Unternehmenskontos, genutzt in Pfaden wie /v1.0/directories/{directoryId}/.... |
directoryName | string | Anzeigename des Unternehmenskontos. |
roleId | string | Deine Rolle in diesem Unternehmenskonto. Auflösen über GET /v1.0/roles. |
licenseId | string | Deine Lizenz in diesem Unternehmenskonto. Auflösen über GET /v1.0/licenses. |
isBlocked | boolean | Ob du in diesem Unternehmenskonto gesperrt bist. |
5. Tokens sicher aufbewahren
Halte das Token aus deinem Quellcode heraus und lies es zur Laufzeit aus der Umgebung oder einem Secret-Speicher, so wie jedes Beispiel hier SALLY_TOKEN liest:
export SALLY_TOKEN="sally_user_api_..."
Behandle ein Token wie ein Passwort. Schick es nur im Header Authorization mit, nie in einer URL. Gib ihm ein Ablaufdatum, wenn die Anbindung nur vorübergehend ist, und widerrufe ein Token in Sally, sobald es niemand mehr braucht.