Wie Termine, Aufnahmen und Zusammenfassungen zusammenhängen
Wenn du nur eine Grundlagen-Seite liest, dann diese. Termine, Aufnahmen und Zusammenfassungen sehen austauschbar aus, sind aber unterschiedlich geschnitten, und genau an diesem Unterschied gehen die meisten Anbindungen schief.
Diese Seite zeigt, wie jede der drei geschnitten ist, welcher Schlüssel sie eindeutig macht, wie die Antworten aussehen, und eine kopierbare Import-Schleife, die jede Zusammenfassung genau einmal speichert. Jedes Beispiel funktioniert so, wie es dasteht, sobald SALLY_TOKEN und DIRECTORY_ID gesetzt sind, siehe Schnellstart.
Schnellnavigation
- Einen Termin gibt es je Person
- Eine Aufnahme gibt es einmal
- Eine Zusammenfassung gibt es je Sprache
- Was das in der Praxis heißt
- Zusammenfassungen ohne Dubletten importieren
- Alle Entitäten im Überblick
1. Einen Termin gibt es je Person
Jeder Teilnehmer desselben echten Meetings hat seinen eigenen Termin-Datensatz. Ein Meeting mit sechs Personen kann innerhalb eines Unternehmenskontos also bis zu sechsmal auftauchen, mit gleichem Betreff und gleicher Zeit.
Ein Termin trägt keine recordingId. Das hier ist ein Eintrag aus GET …/appointments, gekürzt auf die Felder, um die es hier geht:
{
"appointmentId": "d4398f97-cd92-4d21-a6ec-4aab0a85441b",
"subject": "Quartalsplanung",
"startTime": "2026-09-24T09:30:00Z",
"endTime": "2026-09-24T10:00:00Z",
"icalId": "e32cc9bd-3639-442b-a946-3e99bc12da69",
"attendees": [
{
"name": "Anna Berger",
"email": "anna.berger@example.com",
"invitationStatus": "organizer",
"attendanceStatus": "attended",
"isRequired": true
}
]
}
Betreff plus Zeit ist keine Identität. Wer Termine zählt, zählt keine Meetings, und wer nach Betreff gruppiert, wirft zusammen, was nicht zusammengehört. Auch die icalId ist keine Identität: Sie ist innerhalb eines Unternehmenskontos nicht eindeutig, weil es je synchronisiertem Kalender eine Kopie gibt. Wenn du das Meeting brauchst, geh über die Aufnahme.
2. Eine Aufnahme gibt es einmal
Je aufgezeichnetem Meeting gibt es innerhalb eines Unternehmenskontos genau eine Aufnahme, egal wie viele Personen dabei waren. Damit ist die Aufnahme der stabile Anker: Sie speicherst du, auf ihr entdoppelst du, über sie verknüpfst du.
- cURL
- JavaScript
- Python
curl "https://api.sally.io/v1.0/directories/$DIRECTORY_ID/recordings/$RECORDING_ID" \
-H "Authorization: Bearer $SALLY_TOKEN"
const response = await fetch(
`https://api.sally.io/v1.0/directories/${directoryId}/recordings/${recordingId}`,
{headers: {Authorization: `Bearer ${process.env.SALLY_TOKEN}`}},
);
const recording = await response.json();
import os
import requests
response = requests.get(
f"https://api.sally.io/v1.0/directories/{directory_id}/recordings/{recording_id}",
headers={"Authorization": f"Bearer {os.environ['SALLY_TOKEN']}"},
)
recording = response.json()
{
"recordingId": "53e2e041-4b34-41c7-a285-634d80b3675d",
"name": "Wöchentliches Teammeeting",
"durationInSeconds": 1843,
"isManualUpload": false,
"isTranscriptionSucceeded": true,
"isTranscriptionCompleted": true,
"transcriptionCompletionReason": "succeeded",
"languageCode": "de-DE",
"createdOn": "2026-09-24T09:30:00Z",
"tags": [
{
"tagId": "18063a7e-a82b-4972-a850-f866b8b4a046",
"name": "Kunde",
"colorCode": "indigo"
}
]
}
3. Eine Zusammenfassung gibt es je Sprache
Eine Zusammenfassung gehört zu genau einer Aufnahme, und eine Aufnahme kann mehrere Zusammenfassungen haben, je Sprache eine. Eindeutig ist eine Zusammenfassung erst über Aufnahme und languageCode. Wenn du eine bestimmte willst, nutze beides.
Alle Zusammenfassungen einer Aufnahme kommen aus einem Aufruf:
- cURL
- JavaScript
- Python
curl "https://api.sally.io/v1.0/directories/$DIRECTORY_ID/recordings/$RECORDING_ID/summaries" \
-H "Authorization: Bearer $SALLY_TOKEN"
const response = await fetch(
`https://api.sally.io/v1.0/directories/${directoryId}/recordings/${recordingId}/summaries`,
{headers: {Authorization: `Bearer ${process.env.SALLY_TOKEN}`}},
);
const summaries = await response.json();
const german = summaries.find((s) => s.languageCode === 'de-DE');
import os
import requests
response = requests.get(
f"https://api.sally.io/v1.0/directories/{directory_id}/recordings/{recording_id}/summaries",
headers={"Authorization": f"Bearer {os.environ['SALLY_TOKEN']}"},
)
summaries = response.json()
german = next((s for s in summaries if s["languageCode"] == "de-DE"), None)
Die Antwort ist ein einfaches Array mit einem Eintrag je Sprache. Beide Einträge teilen sich die recordingId:
[
{
"recordingSummaryId": "731180f9-257a-46cf-a7e3-8ca508fb9c35",
"recordingId": "53e2e041-4b34-41c7-a285-634d80b3675d",
"appointmentId": "d4398f97-cd92-4d21-a6ec-4aab0a85441b",
"languageCode": "de-DE",
"summary": "Wir haben den Rollout-Plan für Oktober beschlossen.",
"isSummarizationCompleted": true,
"isSummarizationSucceeded": true,
"summarizationCompletionReason": "succeeded",
"createdOn": "2026-09-24T09:30:00Z",
"sectionItems": null
},
{
"recordingSummaryId": "0f5d2a51-6c1e-4b8e-9d7a-2e4c3b1a9f60",
"recordingId": "53e2e041-4b34-41c7-a285-634d80b3675d",
"appointmentId": "d4398f97-cd92-4d21-a6ec-4aab0a85441b",
"languageCode": "en-US",
"summary": "We agreed on the rollout plan for October.",
"isSummarizationCompleted": true,
"isSummarizationSucceeded": true,
"summarizationCompletionReason": "succeeded",
"createdOn": "2026-09-24T09:30:00Z",
"sectionItems": null
}
]
| Feld | Bedeutung |
|---|---|
recordingSummaryId | Id dieser einen Zusammenfassung, für GET …/summaries/{recordingSummaryId}. |
recordingId | Die Aufnahme, aus der die Zusammenfassung erzeugt wurde. Dein Schlüssel zum Verknüpfen. |
languageCode | Sprache der Zusammenfassung als IETF-Tag, etwa de-DE oder en-US. |
appointmentId | Dein eigener Termin, der mit der Aufnahme verknüpft ist, oder null, wenn es keinen gibt. |
sectionItems | Hier null. Mit ?includeDetails=true kommen die strukturierten Abschnittseinträge mit. |
4. Was das in der Praxis heißt
| Entität | Gibt es | Eindeutiger Schlüssel |
|---|---|---|
| Termin | Einmal je Teilnehmer | appointmentId |
| Aufnahme | Einmal je aufgezeichnetem Meeting im Unternehmenskonto | recordingId |
| Zusammenfassung | Einmal je Aufnahme und Sprache | recordingId + languageCode, oder recordingSummaryId |
- Speichere die
recordingIdals Fremdschlüssel, nicht den Termin. - Wenn du einem Nutzer ein Meeting zeigst, nimm den Termin dieses Nutzers, denn er trägt dessen Sicht auf das Meeting.
- Wenn du Zusammenfassungen importierst, lauf über Aufnahmen und wähle die gewünschte Sprache, statt über Termine zu laufen und dasselbe Meeting mehrfach zu importieren.
| Vorgehen | Sechs Kollegen, Zusammenfassungen auf Deutsch und Englisch | Ergebnis |
|---|---|---|
| Schlüssel ist der Termin | 6 Termine | Sechs Einträge für ein Meeting |
| Schlüssel ist die Aufnahme | 1 Aufnahme | Ein Eintrag je Meeting |
| Schlüssel ist Aufnahme plus Sprache | 2 Zusammenfassungen | Ein Eintrag je Meeting und Sprache |
Sechs Kollegen sitzen im selben Wochenmeeting. Im Unternehmenskonto liegen sechs Termine, eine Aufnahme und, wenn es Zusammenfassungen auf Deutsch und Englisch gibt, zwei Zusammenfassungen. Ein Import, der auf den Termin schlüsselt, legt für ein Meeting sechs Einträge an.
5. Zusammenfassungen ohne Dubletten importieren
Diese Schleife läuft über die Aufnahmen, holt zu jeder die Zusammenfassungen und speichert die deutsche unter dem Schlüssel recordingId + languageCode. Läuft sie zweimal, aktualisiert sie dieselben Einträge, statt neue anzulegen. Wie das Blättern funktioniert, steht unter Paginierung und Suche.
- cURL
- JavaScript
- Python
# Deutsche Zusammenfassung einer Aufnahme, Schlüssel ist recordingId + languageCode
curl -s "https://api.sally.io/v1.0/directories/$DIRECTORY_ID/recordings/$RECORDING_ID/summaries" \
-H "Authorization: Bearer $SALLY_TOKEN" \
| jq '.[] | select(.languageCode == "de-DE") | {key: "\(.recordingId):\(.languageCode)", summary}'
const base = `https://api.sally.io/v1.0/directories/${directoryId}`;
const headers = {Authorization: `Bearer ${process.env.SALLY_TOKEN}`};
const store = new Map(); // deine Datenbank
for (let page = 1; ; page++) {
const list = await fetch(
`${base}/recordings?page=${page}&pageSize=100&fields=recordingId`,
{headers},
).then((r) => r.json());
for (const {recordingId} of list.items) {
const summaries = await fetch(`${base}/recordings/${recordingId}/summaries`, {headers})
.then((r) => r.json());
const german = summaries.find((s) => s.languageCode === 'de-DE');
if (german) store.set(`${recordingId}:${german.languageCode}`, german); // Upsert
}
if (!list.hasMore) break;
}
import os
import requests
base = f"https://api.sally.io/v1.0/directories/{directory_id}"
headers = {"Authorization": f"Bearer {os.environ['SALLY_TOKEN']}"}
store = {} # deine Datenbank
page = 1
while True:
listing = requests.get(
f"{base}/recordings",
params={"page": page, "pageSize": 100, "fields": "recordingId"},
headers=headers,
).json()
for item in listing["items"]:
recording_id = item["recordingId"]
summaries = requests.get(f"{base}/recordings/{recording_id}/summaries", headers=headers).json()
german = next((s for s in summaries if s["languageCode"] == "de-DE"), None)
if german:
store[(recording_id, german["languageCode"])] = german # Upsert
if not listing["hasMore"]:
break
page += 1
Prüf isSummarizationSucceeded, bevor du eine Zusammenfassung speicherst. Ein Lauf über eine Aufnahme ohne Transkript wird auch fertig, dann steht summarizationCompletionReason auf noTranscript. Das ist ein erwartetes Ergebnis, kein Fehler.
6. Alle Entitäten im Überblick
Die meisten Ressourcen liegen unter einem Unternehmenskonto: /v1.0/directories/{directoryId}/….
| Entität | Was sie ist |
|---|---|
| Directory | Ein Sally-Unternehmenskonto und die oberste Ebene. Jede andere Ressource gehört zu genau einem Directory. |
| User / Membership | Eine Person und die Verbindung eines Nutzers mit einem Unternehmenskonto, samt Rolle und Lizenz. |
| Role / License | Nachschlagelisten, auf die eine Mitgliedschaft verweist. |
| Team | Eine benannte Gruppe von Nutzern in einem Unternehmenskonto. |
| Appointment | Ein Kalendertermin mit seinen Teilnehmern, einmal je Nutzer gespeichert. |
| Recording | Eine Audio- oder Videoaufnahme eines Meetings samt Transkript. |
| Recording summary | Die KI-Zusammenfassung einer Aufnahme, eine je Sprache, aufgebaut aus Abschnittseinträgen. |
| Meeting template | Legt die Abschnitte fest, in die eine Zusammenfassung gegliedert wird (nur lesend). |
| Folder / Tag | Ordnen Aufnahmen in einem Baum und kennzeichnen Termine und Aufnahmen. |
| Task | Eine Aufgabe, oft aus einem Meeting abgeleitet. |
| Vocabulary | Begriffe, die die Transkription verbessern. |
| Webhook | Ein ausgehendes Abonnement, das Ereignisse an deine URL schickt. |
| File | Ein binärer Inhalt wie ein Profilbild, per Id abrufbar. |
Die Endpunkte für die drei Entitäten dieser Seite:
- GET
…/appointmentsDurchsucht Termine (Betreff, Teilnehmer, Zeitraum), paginiertAusprobieren - GET
…/recordingsListet die Aufnahmen eines Unternehmenskontos auf (paginiert, optional nach Datum gefiltert)Ausprobieren - GET
…/recordings/{recordingId}Gibt eine einzelne Aufnahme zurückAusprobieren - GET
…/recordings/{recordingId}/summariesGibt die Zusammenfassungen einer Aufnahme zurückAusprobieren - GET
…/summaries/{recordingSummaryId}Gibt eine einzelne Zusammenfassung anhand ihrer Id zurückAusprobieren