Worksonar REST-API v1
Programmgesteuerter Zugriff auf Kampagnen, Antworten, Teilnehmer und Auswertungen. JSON, Bearer-Auth, Premium-Tarif. Diese Seite ist auch als Markdown verfügbar.
Inhalt
- 1. Einleitung
- 2. Base-URL & Versionierung
- 3. Authentifizierung
- 4. Scopes
- 5. Rate-Limits
- 6. Fehler-Format
- 7. Paginierung
- 8. Anonymitäts-Schutz
- 9. Endpoints:
/me - 10. Endpoints:
/campaigns - 11. Endpoints:
/responses - 12. Endpoints:
/analysis - 13. Endpoints:
/participants - 14. Endpoints:
/benchmarks - 15. End-to-End-Beispiele
- 16. Clients in 3 Sprachen
- 17. Changelog
1. Einleitung
Die Worksonar-API ist eine schlanke, REST-orientierte HTTP-API. Sie spricht ausschließlich JSON und nutzt Bearer-Tokens zur Authentifizierung. Alle Daten sind mandantenspezifisch isoliert: ein Token kann nur auf die Daten des Mandanten zugreifen, für den er ausgestellt wurde.
Verfügbar im Premium-Tarif. Tokens erzeugen Sie unter Tenant-Portal → API.
2. Base-URL & Versionierung
| Umgebung | Base-URL |
|---|---|
| Diese Instanz | https://beta.worksonar.de/api/v1 |
| Hosted (Beispiel) | https://app.worksonar.de/api/v1 |
Versionierung erfolgt über das URL-Präfix (/v1). Breaking Changes erscheinen ausschließlich in einer neuen Major-Version mit mindestens 6 Monaten Parallelbetrieb.
3. Authentifizierung
Jeder Request muss einen Bearer-Token im Authorization-Header tragen. Tokens haben das Format wsk_ + 32 base64-url-Zeichen.
Authorization: Bearer wsk_kpYHa3-Z9c1m...x9F
Token erzeugen Sie unter /tenant/api. Der Klartext-Token wird nur einmal angezeigt — danach kennt das System nur einen SHA-256-Hash. Vergessen Sie den Token, erzeugen Sie einen neuen und widerrufen den alten.
4. Scopes
Pro Token wählen Sie eine oder mehrere Scopes:
| Scope | Was erlaubt |
|---|---|
read | Alle GET-Endpoints (Lesezugriff) |
write | POST · PATCH · DELETE (Schreibzugriff) |
Fehlt ein erforderlicher Scope, antwortet der Server mit 403 insufficient_scope.
5. Rate-Limits
Pro Token gelten standardmäßig 60 Requests / Minute in einem rollenden Fenster. Bei Überschreitung kommt:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
{"error":{"code":"rate_limited","message":"Rate limit exceeded (60 requests / minute)."}}
Höhere Limits sind im Premium-Tarif vertraglich verhandelbar.
6. Fehler-Format
Alle Fehler folgen einem einheitlichen Schema:
{
"error": {
"code": "validation",
"message": "Required: title, module.",
"details": { "missing": ["title"] }
}
}
| HTTP | Typische Codes |
|---|---|
| 400 | validation |
| 401 | unauthenticated · invalid_token · expired_token |
| 402 | plan_required (kein Premium-Tarif) |
| 403 | insufficient_scope · plan_restriction · plan_limit · tenant_inactive |
| 404 | not_found |
| 422 | anonymity_protection (n<5) · campaign_inactive |
| 429 | rate_limited |
7. Paginierung
List-Endpoints akzeptieren ?limit=N&offset=M und liefern:
{
"items": [ /* objects */ ],
"total": 427,
"limit": 50,
"offset": 100
}
Defaults: limit=50 (Antworten/Kampagnen) bzw. 100 (Teilnehmer), Maximum 500 bzw. 1000.
8. Anonymitäts-Schutz
Der Endpoint /analysis/:campaign_id liefert keine aggregierten Ergebnisse, wenn nach Anwendung der Filter weniger als 5 Antworten übrigbleiben. Statt eines Teilergebnisses kommt:
HTTP/1.1 422
{"error":{"code":"anonymity_protection","message":"Less than 5 responses (n=3). Analysis withheld per anonymity rule n>=5."}}
Der /responses-Endpoint liefert dagegen alle Rohdaten — die Verantwortung für die Anonymisierung liegt dort beim Konsument.
9. /me
Liefert Identifikation des Tokens und des zugehörigen Mandanten. Ideal zum Smoke-Testen des Tokens.
Beispiel
curl -H "Authorization: Bearer $WSK" https://beta.worksonar.de/api/v1/me
Antwort
{
"tenant": { "id":"6f…","name":"Mustermann Logistik AG","slug":"mustermann-logistik","industry":"Logistik & Transport","plan":"enterprise" },
"token": { "name":"CRM-Sync","prefix":"wsk_kpYHa3","scopes":["read","write"],"created_at":"2026-05-29 09:12:00","last_used_at":"2026-05-29 09:33:11" },
"api_version":"v1"
}
10. /campaigns
Query-Parameter
module | Filter: gbu_psych | benefits | bgm |
status | Filter: draft | active | closed |
limit, offset | Paginierung |
curl -H "Authorization: Bearer $WSK" "https://beta.worksonar.de/api/v1/campaigns?status=active"
Body
{
"title": "GBU Q3 2026", // erforderlich
"module": "gbu_psych", // erforderlich
"description": "Quartals-Erhebung",
"start_date": "2026-07-01",
"end_date": "2026-08-15",
"access_code": "GBU-Q3-2026", // optional, sonst auto-generiert
"expected_participants": 150,
"requires_login": false
}
Status der neuen Kampagne ist immer draft. Aktivieren mit PATCH.
Update einer Kampagne. Erlaubte Felder: title, description, status, start_date, end_date, access_code, expected_participants.
curl -X PATCH -H "Authorization: Bearer $WSK" -H "Content-Type: application/json" \
-d '{"status":"active"}' https://beta.worksonar.de/api/v1/campaigns/CID
11. /responses
Query-Parameter
campaign_id | Auf eine Kampagne filtern |
module | Modul-Filter |
department · location | Segment-Filter |
from · to | Zeitraum (ISO 8601, YYYY-MM-DD oder vollständig) |
limit, offset | Paginierung (max 500) |
Antwort programmatisch einreichen (z. B. aus externem Befragungs-Tool).
Body
{
"campaign_id": "CID",
"department": "Lager",
"location": "Düsseldorf",
"is_anonymous": true, // default: true
"participant_email": null, // nur wenn !anonymous
"answers": { // erforderlich
"zd_1": 4, "zd_2": 3, "zd_3": 4, "zd_4": 4,
"vollst_1": 3, "vollst_2": 2, "vollst_3": 3
}
}
Die Kampagne muss status: "active" haben, sonst 422 campaign_inactive.
12. /analysis
Vorab berechnete Auswertung für die ganze Kampagne. Filter über ?department=…&location=… möglich (Anonymitäts-Schwelle n≥5 gilt auch hier).
Antwort (GBU Psych, gekürzt)
{
"campaign_id": "CID",
"module": "gbu_psych",
"n": 126,
"analysis": {
"dimensions": [
{ "key":"zeitdruck","label":"Zeitdruck","type":"burden","mean":3.04,"n":504,"ampel":"red" },
{ "key":"arbeitsumgebung","label":"Arbeitsumgebung","type":"burden","mean":2.79,"n":1260,"ampel":"red" },
…
],
"findings": [ … ],
"measures": [
{ "dimension":"Zeitdruck","ampel":"red","measure":"Aufgabenanalyse und Personalplanung prüfen" },
…
]
}
}
13. /participants
Optional Filter: ?invite_status=pending|invited|completed.
Body
{ "email":"mitarbeiter@firma.de", "name":"Anna A.", "department":"Lager", "location":"Düsseldorf", "campaign_id":"CID" }
Nutzt das MA-Limit des Mandanten-Tarifs — Premium unbegrenzt, andere Pläne entsprechend.
Antwortet mit 204 No Content bei Erfolg.
14. /benchmarks
Liefert Branchen-Benchmark-Werte. Default: Branche des Tokens-Mandanten.
Optional: ?industry=Logistik+%26+Transport&module=gbu_psych
15. End-to-End-Beispiele
Beispiel A — Antwort aus externem Tool importieren
# 1. Aktive Kampagne finden
curl -H "Authorization: Bearer $WSK" "https://beta.worksonar.de/api/v1/campaigns?status=active&module=gbu_psych" | jq '.items[0].id'
# 2. Antwort posten
curl -X POST -H "Authorization: Bearer $WSK" -H "Content-Type: application/json" \
-d '{"campaign_id":"CID","department":"Vertrieb","location":"Berlin","answers":{"zd_1":4,"zd_2":3,...}}' \
https://beta.worksonar.de/api/v1/responses
Beispiel B — Analyse für Dashboard ziehen
curl -H "Authorization: Bearer $WSK" \
"https://beta.worksonar.de/api/v1/analysis/CID?department=Lager" | jq '.analysis.findings[] | select(.ampel=="red")'
Beispiel C — Mitarbeiter aus HR-System synchronisieren
# Existierende holen
existing=$(curl -s -H "Authorization: Bearer $WSK" "https://beta.worksonar.de/api/v1/participants?limit=1000" | jq -r '.items[].email')
# Neue anlegen
while read email name dept loc; do
if ! grep -q "$email" <<< "$existing"; then
curl -X POST -H "Authorization: Bearer $WSK" -H "Content-Type: application/json" \
-d "{\"email\":\"$email\",\"name\":\"$name\",\"department\":\"$dept\",\"location\":\"$loc\"}" \
https://beta.worksonar.de/api/v1/participants
fi
done < mitarbeiter.tsv
16. Clients in 3 Sprachen
WSK="wsk_…"
# GET mit JSON-Pretty-Print
curl -s -H "Authorization: Bearer $WSK" https://beta.worksonar.de/api/v1/me | jq
# POST mit Body
curl -X POST -H "Authorization: Bearer $WSK" -H "Content-Type: application/json" \
-d @campaign.json https://beta.worksonar.de/api/v1/campaigns
// Node 18+ (globales fetch)
const BASE = 'https://beta.worksonar.de/api/v1';
const WSK = process.env.WSK;
async function ws(path, init = {}) {
const res = await fetch(BASE + path, {
...init,
headers: { 'Authorization': `Bearer ${WSK}`, 'Content-Type': 'application/json', ...(init.headers||{}) },
});
const data = await res.json().catch(() => null);
if (!res.ok) throw Object.assign(new Error(data?.error?.message || res.statusText), { status: res.status, data });
return data;
}
// Listen
const { items } = await ws('/campaigns?status=active');
// Posten
const newCamp = await ws('/campaigns', {
method: 'POST',
body: JSON.stringify({ title: 'Q3 2026', module: 'gbu_psych' }),
});
import os, requests
BASE = "https://beta.worksonar.de/api/v1"
WSK = os.environ["WSK"]
H = {"Authorization": f"Bearer {WSK}", "Content-Type": "application/json"}
def ws(method, path, **kw):
r = requests.request(method, BASE + path, headers=H, timeout=30, **kw)
if r.status_code >= 400:
raise RuntimeError(f"{r.status_code} {r.json().get('error', {}).get('message', r.text)}")
return r.json() if r.text else None
# Lesen
me = ws("GET", "/me")
print(me["tenant"]["name"])
# Schreiben
camp = ws("POST", "/campaigns", json={"title":"Q3 2026","module":"gbu_psych"})
ws("PATCH", f"/campaigns/{camp['id']}", json={"status":"active"})
17. Changelog
| Version | Datum | Änderungen |
|---|---|---|
v1.0 | 2026-05 | Erstveröffentlichung. Endpoints für me / campaigns / responses / analysis / participants / benchmarks. Bearer-Auth mit Read/Write-Scopes, 60 req/min Rate-Limit, n≥5 Anonymitätsschutz im Analysis-Endpoint. |