Wie du Sally-Aufgaben mit deinem System synchronisierst
Sally leitet Aufgaben aus deinen Gesprächen ab. Diese Anleitung zeigt, wie du sie liest, in das System bringst, mit dem dein Team wirklich arbeitet, und zurückmeldest, wenn eine Aufgabe erledigt ist. Am Ende steht ein komplettes Sync-Skript.
Jedes Beispiel funktioniert so, wie es dasteht, sobald SALLY_TOKEN und DIRECTORY_ID gesetzt sind, siehe Schnellstart. Wähl deine Sprache einmal, alle Beispiele im API-Bereich folgen ihr.
Schnellnavigation
- Aufgaben lesen
- Aufgaben suchen
- Aufgabe anlegen
- Aufgabe ändern
- Aufgabe erledigen oder wieder öffnen
- Erledigt ist nicht abgebrochen
- Komplettes Sync-Skript
- Endpunkte im Überblick
1. Aufgaben lesen
GET/v1.0/directories/{directoryId}/tasks liefert die Aufgaben eines Unternehmenskontos seitenweise. Der Endpunkt nimmt diese Query-Parameter:
| Parameter | Typ | Standard | Beschreibung |
|---|---|---|---|
filter | string | Voreinstellung, die Umfang und Zustand kombiniert, siehe unten. | |
search | string | Teilstring-Suche im Betreff der Aufgabe, ohne Beachtung der Groß- und Kleinschreibung. | |
page | integer | 1 | Seitennummer, beginnend bei 1. |
pageSize | integer | 25 | Einträge je Seite, höchstens 100. |
fields | string | Kommagetrennte Liste der Felder, die zurückkommen sollen, siehe Feldauswahl. |
Die filter-Voreinstellung kombiniert Umfang und Zustand: my… umfasst jede Aufgabe, an der du beteiligt bist, als verantwortliche Person oder als Owner, all… alle Aufgaben des Unternehmenskontos, die du sehen darfst. Der Zustand ist Tasks (alle), Open, Overdue, Completed oder Forwarded, zum Beispiel myOpen oder allOverdue. Forwarded ist die einzige Ausnahme von diesem Umfang: Es listet nur, was du an jemand anderen weitergegeben hast, du bist also Owner und jemand anderes ist verantwortlich. Die all…-Varianten sind Admins und Ownern vorbehalten.
| Umfang | Alle | Offen | Überfällig | Erledigt | Weitergeleitet |
|---|---|---|---|---|---|
| Deine (verantwortlich oder Owner) | myTasks | myOpen | myOverdue | myCompleted | myForwarded |
| Alle (Admin, Owner) | allTasks | allOpen | allOverdue | allCompleted | allForwarded |
Diese Anfrage holt deine offenen Aufgaben:
- cURL
- JavaScript
- Python
curl "https://api.sally.io/v1.0/directories/$DIRECTORY_ID/tasks?filter=myOpen&pageSize=100" \
-H "Authorization: Bearer $SALLY_TOKEN"
const url = new URL(`https://api.sally.io/v1.0/directories/${directoryId}/tasks`);
url.searchParams.set('filter', 'myOpen');
url.searchParams.set('pageSize', '100');
const response = await fetch(url, {
headers: {Authorization: `Bearer ${process.env.SALLY_TOKEN}`},
});
const {items, hasMore} = await response.json();
import os
import requests
response = requests.get(
f"https://api.sally.io/v1.0/directories/{directory_id}/tasks",
params={"filter": "myOpen", "pageSize": 100},
headers={"Authorization": f"Bearer {os.environ['SALLY_TOKEN']}"},
)
data = response.json()
{
"page": 1,
"pageSize": 100,
"total": 137,
"hasMore": true,
"items": [
{
"taskId": "6ceba541-fa71-44c7-a7f7-644d56b1b85d",
"subject": "Quartalsplanung",
"description": "Budgetzahlen für das Q4-Review vorbereiten.",
"ownerId": "e51b6a79-43bf-494f-a264-6825f41b0fb5",
"responsibleUserId": "1b84577d-c09e-446b-a5b8-71591dfde929",
"taskCategoryId": "4f2c8a1e-7b3d-4e9a-b6c5-2d8f1a7e3c90",
"dueDate": "2026-09-30T00:00:00Z",
"isDone": false,
"doneOn": null,
"isCanceled": false,
"canceledOn": null,
"recordingSummaryId": "731180f9-257a-46cf-a7e3-8ca508fb9c35",
"appointmentId": "d4398f97-cd92-4d21-a6ec-4aab0a85441b",
"createdOn": "2026-09-24T09:30:00Z",
"modifiedOn": "2026-09-24T09:30:00Z"
}
]
}
Jede Aufgabe hat diese Felder:
| Feld | Typ | Bedeutung |
|---|---|---|
taskId | string | Id der Aufgabe. |
subject | string | Betreff der Aufgabe. |
description | string oder null | Beschreibung, null, wenn nicht gesetzt. |
ownerId | string | Id der Person, der die Aufgabe gehört (die sie angelegt hat). |
responsibleUserId | string oder null | Id der verantwortlichen Person, null, wenn niemand zugewiesen ist. |
taskCategoryId | string oder null | Id der Aufgabenkategorie, null, wenn die Aufgabe keine hat. Siehe Aufgabenkategorie auflösen. |
dueDate | string oder null | Fälligkeit (ISO 8601), null ohne Fälligkeit. |
isDone | boolean | Ob die Aufgabe als erledigt markiert ist. |
doneOn | string oder null | Wann die Aufgabe erledigt wurde (ISO 8601), sonst null. |
isCanceled | boolean | Ob die Aufgabe abgebrochen ist. |
canceledOn | string oder null | Wann die Aufgabe abgebrochen wurde (ISO 8601), sonst null. |
recordingSummaryId | string oder null | Zusammenfassung, aus der die Aufgabe stammt, sonst null. |
appointmentId | string oder null | Termin, mit dem die Aufgabe verknüpft ist, sonst null. |
createdOn | string | Zeitpunkt der Erstellung (ISO 8601). |
modifiedOn | string | Zeitpunkt der letzten Änderung (ISO 8601). |
Eine Aufgabe weiß, woher sie kommt: recordingSummaryId und appointmentId zeigen zurück auf das Meeting. Damit kannst du in deinem Tool den Meeting-Zusammenhang neben die Aufgabe stellen.
ownerId und responsibleUserId sind reine Ids. Namen und E-Mail-Adressen bekommst du über GET /v1.0/directories/{directoryId}/memberships/users/{userId}. Die Schleife aus Alle Seiten abrufen holt alle Seiten ab.
Aufgabenkategorie auflösen
Auch taskCategoryId ist eine reine Id. GET/v1.0/directories/{directoryId}/task-categories liefert alle Kategorien, die eine Aufgabe tragen kann: die deines Unternehmenskontos und die globalen Standardkategorien von Sally (isGlobal: true), nach Name sortiert. Die Liste ist nicht seitenweise, items enthält alle.
- cURL
- JavaScript
- Python
curl "https://api.sally.io/v1.0/directories/$DIRECTORY_ID/task-categories" \
-H "Authorization: Bearer $SALLY_TOKEN"
const response = await fetch(
`https://api.sally.io/v1.0/directories/${directoryId}/task-categories`,
{headers: {Authorization: `Bearer ${process.env.SALLY_TOKEN}`}},
);
const {items} = await response.json();
// Jede Id ihrer Kategorie zuordnen, dann task.taskCategoryId nachschlagen.
const categories = new Map(items.map((category) => [category.taskCategoryId, category]));
import os
import requests
response = requests.get(
f"https://api.sally.io/v1.0/directories/{directory_id}/task-categories",
headers={"Authorization": f"Bearer {os.environ['SALLY_TOKEN']}"},
)
# Jede Id ihrer Kategorie zuordnen, dann task["taskCategoryId"] nachschlagen.
categories = {c["taskCategoryId"]: c for c in response.json()["items"]}
{
"items": [
{
"taskCategoryId": "4f2c8a1e-7b3d-4e9a-b6c5-2d8f1a7e3c90",
"name": "Kunden-Follow-up",
"description": "Anrufe, E-Mails und Angebote, auf die ein Kunde nach einem Meeting wartet.",
"active": true,
"isGlobal": false
},
{
"taskCategoryId": "9a3e5c71-2b8d-4f60-a1c4-7e9d0b2f5a38",
"name": "Intern",
"description": null,
"active": true,
"isGlobal": true
},
{
"taskCategoryId": "c81f4d26-5e0a-4b93-8d7c-3a6b9e1f0d54",
"name": "Messe 2025",
"description": "Vor- und Nachbereitung der Messe 2025.",
"active": false,
"isGlobal": false
}
]
}
| Feld | Typ | Bedeutung |
|---|---|---|
taskCategoryId | string | Id der Kategorie, also der Wert, den eine Aufgabe in taskCategoryId trägt. |
name | string | Name der Kategorie. |
description | string oder null | Wofür die Kategorie steht. Diesen Text bekommt Sally, wenn sie einer erkannten Aufgabe eine Kategorie zuordnet. null, wenn nicht gesetzt. |
active | boolean | Ob die Kategorie für neue Aufgaben noch angeboten wird. |
isGlobal | boolean | true für eine globale Standardkategorie von Sally, false für eine Kategorie deines Unternehmenskontos. |
Deaktivierte Kategorien bleiben mit active: false in der Liste, damit eine Aufgabe, die noch eine davon trägt, weiter aufgelöst werden kann. Für neue Aufgaben werden sie nicht mehr angeboten. Lässt dein Tool eine Kategorie auswählen, biete deshalb nur die aktiven an.
2. Aufgaben suchen
POST/v1.0/directories/{directoryId}/tasks/search nimmt seine Parameter im Request-Body. Anders als der search-Parameter der Liste durchsucht er Betreff und Beschreibung und filtert nach Status und verantwortlicher Person. Die Treffer sind nach Fälligkeit sortiert, die früheste zuerst.
| Feld | Typ | Standard | Beschreibung |
|---|---|---|---|
search | string | Teilstring-Suche in Betreff und Beschreibung, ohne Beachtung der Groß- und Kleinschreibung. | |
responsible | string | Id der verantwortlichen Person. Deine eigene Id ist immer erlaubt. Die Id einer anderen Person oder ein weggelassenes Feld (alle Verantwortlichen) setzt Admin oder Owner voraus. | |
status | string | open (weder erledigt noch abgebrochen), completed, overdue (offen und überfällig) oder canceled. Weglassen für jeden Status. | |
page | integer | 1 | Seitennummer, beginnend bei 1. |
pageSize | integer | 25 | Einträge je Seite, höchstens 100. |
Deine eigene Id ist die userId aus GET /v1.0/me. Diese Anfrage sucht deine überfälligen Aufgaben, in denen ein Angebot vorkommt:
- cURL
- JavaScript
- Python
curl -X POST "https://api.sally.io/v1.0/directories/$DIRECTORY_ID/tasks/search" \
-H "Authorization: Bearer $SALLY_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"search\": \"Angebot\",
\"status\": \"overdue\",
\"responsible\": \"$USER_ID\"
}"
const response = await fetch(
`https://api.sally.io/v1.0/directories/${directoryId}/tasks/search`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SALLY_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({search: 'Angebot', status: 'overdue', responsible: userId}),
},
);
const {total, items} = await response.json();
import os
import requests
response = requests.post(
f"https://api.sally.io/v1.0/directories/{directory_id}/tasks/search",
headers={"Authorization": f"Bearer {os.environ['SALLY_TOKEN']}"},
json={"search": "Angebot", "status": "overdue", "responsible": user_id},
)
data = response.json()
Die Antwort hat dieselbe Hülle und dieselben Aufgabenfelder wie die Liste unter Aufgaben lesen. Die Suchregeln gelten für alle Such-Endpunkte gleich, siehe Wie gesucht wird.
3. Aufgabe anlegen
POST/v1.0/directories/{directoryId}/tasks legt eine Aufgabe an. Du wirst ihr Owner.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
subject | string | ja | Betreff, 1 bis 1000 Zeichen. |
description | string oder null | nein | Beschreibung, höchstens 4000 Zeichen. null für keine. |
responsibleUserId | string oder null | nein | Id der verantwortlichen Person. null oder weggelassen heißt: niemand zugewiesen. |
taskCategoryId | string oder null | nein | Id der Aufgabenkategorie aus der Kategorienliste. null oder weggelassen heißt: keine. Eine Id aus einem anderen Unternehmenskonto wird abgelehnt. |
dueDate | string oder null | nein | Fälligkeit als ISO-8601-String. null für keine Fälligkeit. |
- cURL
- JavaScript
- Python
curl -X POST "https://api.sally.io/v1.0/directories/$DIRECTORY_ID/tasks" \
-H "Authorization: Bearer $SALLY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"subject": "Quartalsplanung",
"description": "Budgetzahlen für das Q4-Review vorbereiten.",
"dueDate": "2026-09-30T00:00:00Z"
}'
const response = await fetch(`https://api.sally.io/v1.0/directories/${directoryId}/tasks`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SALLY_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
subject: 'Quartalsplanung',
description: 'Budgetzahlen für das Q4-Review vorbereiten.',
dueDate: '2026-09-30T00:00:00Z',
}),
});
const task = await response.json();
import os
import requests
response = requests.post(
f"https://api.sally.io/v1.0/directories/{directory_id}/tasks",
headers={"Authorization": f"Bearer {os.environ['SALLY_TOKEN']}"},
json={
"subject": "Quartalsplanung",
"description": "Budgetzahlen für das Q4-Review vorbereiten.",
"dueDate": "2026-09-30T00:00:00Z",
},
)
task = response.json()
Die Antwort ist die neue Aufgabe mit allen Feldern:
{
"taskId": "6ceba541-fa71-44c7-a7f7-644d56b1b85d",
"subject": "Quartalsplanung",
"description": "Budgetzahlen für das Q4-Review vorbereiten.",
"ownerId": "e51b6a79-43bf-494f-a264-6825f41b0fb5",
"responsibleUserId": null,
"taskCategoryId": null,
"dueDate": "2026-09-30T00:00:00Z",
"isDone": false,
"doneOn": null,
"isCanceled": false,
"canceledOn": null,
"recordingSummaryId": null,
"appointmentId": null,
"createdOn": "2026-09-24T09:30:00Z",
"modifiedOn": "2026-09-24T09:30:00Z"
}
4. Aufgabe ändern
PATCH/v1.0/directories/{directoryId}/tasks/{taskId} ändert eine Aufgabe per PATCH. Geändert werden nur die Felder, die du mitschickst, weggelassene behalten ihren Wert, und null leert ein Feld ausdrücklich. Du brauchst das Recht, die Aufgabe zu bearbeiten.
| Feld | Typ | Beschreibung |
|---|---|---|
subject | string | Neuer Betreff, 1 bis 1000 Zeichen. |
description | string oder null | Neue Beschreibung, höchstens 4000 Zeichen. null leert sie. |
responsibleUserId | string oder null | Neue verantwortliche Person. null hebt die Zuweisung auf. |
taskCategoryId | string oder null | Neue Aufgabenkategorie aus der Kategorienliste. null entfernt sie. Eine Id aus einem anderen Unternehmenskonto wird abgelehnt. |
dueDate | string oder null | Neue Fälligkeit als ISO-8601-String. null entfernt sie. |
Diese Anfrage weist die Aufgabe zu, setzt ihre Kategorie und entfernt ihre Fälligkeit:
- cURL
- JavaScript
- Python
curl -X PATCH "https://api.sally.io/v1.0/directories/$DIRECTORY_ID/tasks/$TASK_ID" \
-H "Authorization: Bearer $SALLY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"responsibleUserId": "1b84577d-c09e-446b-a5b8-71591dfde929",
"taskCategoryId": "4f2c8a1e-7b3d-4e9a-b6c5-2d8f1a7e3c90",
"dueDate": null
}'
const response = await fetch(
`https://api.sally.io/v1.0/directories/${directoryId}/tasks/${taskId}`,
{
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.SALLY_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
responsibleUserId: '1b84577d-c09e-446b-a5b8-71591dfde929',
taskCategoryId: '4f2c8a1e-7b3d-4e9a-b6c5-2d8f1a7e3c90',
dueDate: null,
}),
},
);
const task = await response.json();
import os
import requests
response = requests.patch(
f"https://api.sally.io/v1.0/directories/{directory_id}/tasks/{task_id}",
headers={"Authorization": f"Bearer {os.environ['SALLY_TOKEN']}"},
json={
"responsibleUserId": "1b84577d-c09e-446b-a5b8-71591dfde929",
"taskCategoryId": "4f2c8a1e-7b3d-4e9a-b6c5-2d8f1a7e3c90",
"dueDate": None,
},
)
task = response.json()
Die Antwort ist die geänderte Aufgabe, in derselben Form wie unter Aufgabe anlegen.
5. Aufgabe erledigen oder wieder öffnen
POST/v1.0/directories/{directoryId}/tasks/{taskId}/done erledigt eine Aufgabe oder öffnet sie wieder, über einen eigenen Aufruf, damit ein Abschluss immer eindeutig ist. Schick {"isDone": true}, um die Aufgabe als erledigt zu markieren, und {"isDone": false}, um sie wieder zu öffnen.
- cURL
- JavaScript
- Python
curl -X POST "https://api.sally.io/v1.0/directories/$DIRECTORY_ID/tasks/$TASK_ID/done" \
-H "Authorization: Bearer $SALLY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"isDone": true}'
const response = await fetch(
`https://api.sally.io/v1.0/directories/${directoryId}/tasks/${taskId}/done`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SALLY_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({isDone: true}),
},
);
const task = await response.json();
import os
import requests
response = requests.post(
f"https://api.sally.io/v1.0/directories/{directory_id}/tasks/{task_id}/done",
headers={"Authorization": f"Bearer {os.environ['SALLY_TOKEN']}"},
json={"isDone": True},
)
task = response.json()
Die Antwort ist die geänderte Aufgabe:
{
"taskId": "6ceba541-fa71-44c7-a7f7-644d56b1b85d",
"subject": "Quartalsplanung",
"description": "Budgetzahlen für das Q4-Review vorbereiten.",
"ownerId": "e51b6a79-43bf-494f-a264-6825f41b0fb5",
"responsibleUserId": "1b84577d-c09e-446b-a5b8-71591dfde929",
"taskCategoryId": "4f2c8a1e-7b3d-4e9a-b6c5-2d8f1a7e3c90",
"dueDate": "2026-09-30T00:00:00Z",
"isDone": true,
"doneOn": "2026-09-25T14:05:00Z",
"isCanceled": false,
"canceledOn": null,
"recordingSummaryId": "731180f9-257a-46cf-a7e3-8ca508fb9c35",
"appointmentId": "d4398f97-cd92-4d21-a6ec-4aab0a85441b",
"createdOn": "2026-09-24T09:30:00Z",
"modifiedOn": "2026-09-25T14:05:00Z"
}
Um eine Aufgabe ganz zu entfernen, nimm DELETE/v1.0/directories/{directoryId}/tasks/{taskId}. Das ist ein Soft Delete, der mit 204 antwortet, und nur der Owner der Aufgabe oder ein Admin oder Owner darf ihn ausführen.
6. Erledigt ist nicht abgebrochen
Eine Aufgabe kann auf zwei verschiedene Arten enden, und dein System sollte beide auseinanderhalten:
isDone | isCanceled | Zustand | Zeitstempel |
|---|---|---|---|
true | false | Erledigt | doneOn |
false | true | Verworfen | canceledOn |
false | false | Noch offen |
isDone mit doneOn heißt erledigt, isCanceled mit canceledOn heißt verworfen. Eine Aufgabe, die keines von beidem ist, ist noch offen. Wenn du beides in deinem System auf einen gemeinsamen Zustand „geschlossen" abbildest, geht dieser Unterschied verloren.
7. Komplettes Sync-Skript
Dieses Skript holt alle deine Aufgaben Seite für Seite, ordnet jede einem der drei Zustände oben zu und übergibt sie an dein Tool. Danach meldet es Erledigungen aus deinem Tool an Sally zurück. Ersetz die beiden Funktionen, die mit dein Tool markiert sind, durch Aufrufe deines eigenen Systems. Bekommt das Skript vom Rate-Limit ein 429, wartet es jedes Mal etwas länger und wiederholt dieselbe Anfrage.
- cURL
- JavaScript
- Python
#!/usr/bin/env bash
# Braucht SALLY_TOKEN und DIRECTORY_ID sowie jq.
set -euo pipefail
BASE="https://api.sally.io/v1.0/directories/$DIRECTORY_ID"
# 1. Alle deine Aufgaben holen und Id, Zustand und Betreff ausgeben.
page=1
while : ; do
body=$(curl -s "$BASE/tasks?filter=myTasks&page=$page&pageSize=100" \
-H "Authorization: Bearer $SALLY_TOKEN")
echo "$body" | jq -r '.items[]
| [.taskId, (if .isDone then "done" elif .isCanceled then "canceled" else "open" end), .subject]
| @tsv'
[ "$(echo "$body" | jq '.hasMore')" = "true" ] || break
page=$((page + 1))
done
# 2. Erledigungen zurückmelden. COMPLETED_IDS enthält die Ids, die dein Tool als erledigt meldet.
for task_id in ${COMPLETED_IDS:-}; do
curl -s -X POST "$BASE/tasks/$task_id/done" \
-H "Authorization: Bearer $SALLY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"isDone": true}' > /dev/null
done
// sync-tasks.mjs, braucht SALLY_TOKEN und DIRECTORY_ID. Node 18 oder neuer.
const BASE = `https://api.sally.io/v1.0/directories/${process.env.DIRECTORY_ID}`;
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function sally(method, path, body) {
for (let wait = 1000; ; wait = Math.min(wait * 2, 60_000)) {
const response = await fetch(`${BASE}${path}`, {
method,
headers: {
Authorization: `Bearer ${process.env.SALLY_TOKEN}`,
...(body ? {'Content-Type': 'application/json'} : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
if (response.status === 429) {
await sleep(wait);
continue;
}
if (!response.ok) throw new Error(`${method} ${path}: ${response.status} ${await response.text()}`);
return response.status === 204 ? null : response.json();
}
}
async function* myTasks() {
for (let page = 1; ; page++) {
const body = await sally('GET', `/tasks?filter=myTasks&page=${page}&pageSize=100`);
yield* body.items;
if (!body.hasMore) return;
}
}
const stateOf = (task) => (task.isDone ? 'done' : task.isCanceled ? 'canceled' : 'open');
// Dein Tool: diese beiden Funktionen ersetzen.
async function upsertInMyTool(record) {
console.log('upsert', record.externalId, record.state, record.title);
}
async function completedInMyTool() {
return []; // Ids der Sally-Aufgaben, die dein Team in deinem Tool erledigt hat
}
for await (const task of myTasks()) {
await upsertInMyTool({
externalId: task.taskId,
title: task.subject,
notes: task.description,
due: task.dueDate,
state: stateOf(task),
meeting: task.appointmentId,
});
}
for (const taskId of await completedInMyTool()) {
await sally('POST', `/tasks/${taskId}/done`, {isDone: true});
}
# sync_tasks.py, braucht SALLY_TOKEN und DIRECTORY_ID.
import os
import time
import requests
BASE = f"https://api.sally.io/v1.0/directories/{os.environ['DIRECTORY_ID']}"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['SALLY_TOKEN']}"
def sally(method, path, body=None):
wait = 1
while True:
response = session.request(method, f"{BASE}{path}", json=body)
if response.status_code == 429:
time.sleep(wait)
wait = min(wait * 2, 60)
continue
response.raise_for_status()
return None if response.status_code == 204 else response.json()
def my_tasks():
page = 1
while True:
body = sally("GET", f"/tasks?filter=myTasks&page={page}&pageSize=100")
yield from body["items"]
if not body["hasMore"]:
return
page += 1
def state_of(task):
if task["isDone"]:
return "done"
if task["isCanceled"]:
return "canceled"
return "open"
# Dein Tool: diese beiden Funktionen ersetzen.
def upsert_in_my_tool(record):
print("upsert", record["external_id"], record["state"], record["title"])
def completed_in_my_tool():
return [] # Ids der Sally-Aufgaben, die dein Team in deinem Tool erledigt hat
for task in my_tasks():
upsert_in_my_tool({
"external_id": task["taskId"],
"title": task["subject"],
"notes": task["description"],
"due": task["dueDate"],
"state": state_of(task),
"meeting": task["appointmentId"],
})
for task_id in completed_in_my_tool():
sally("POST", f"/tasks/{task_id}/done", {"isDone": True})
Speicher die Sally-taskId in deinem Tool als externe Id. Dann aktualisiert jeder Lauf denselben Datensatz, statt ein Duplikat anzulegen, und du weißt, welche Id du zurückschickst, wenn eine Aufgabe bei dir geschlossen wird.
8. Endpunkte im Überblick
Jeder Eintrag führt zur Referenz, wo du die Anfrage mit deinem eigenen Token senden kannst.
- GET
…/tasksListet die Aufgaben eines Unternehmenskontos auf (paginiert, optional gefiltert)Ausprobieren - POST
…/tasks/searchDurchsucht Aufgaben (Parameter im Body)Ausprobieren - GET
…/tasks/{taskId}Gibt eine einzelne Aufgabe anhand ihrer Id zurückAusprobieren - POST
…/tasksErstellt eine AufgabeAusprobieren - PATCH
…/tasks/{taskId}Aktualisiert eine Aufgabe (teilweise Aktualisierung)Ausprobieren - POST
…/tasks/{taskId}/doneSchaltet den Erledigt-Status einer Aufgabe umAusprobieren - DELETE
…/tasks/{taskId}Löscht eine Aufgabe (weiches Löschen, Soft Delete)Ausprobieren - GET
…/task-categoriesListet die Aufgabenkategorien eines Unternehmenskontos auf (löst `taskCategoryId` auf)Ausprobieren