Vigotime Planner API
Integriere die Dienstplanung headless: Du schickst einen self-contained Payload (Mitarbeiter, Schichten, Regeln, Abwesenheiten) — und bekommst einen fertigen Dienstplan zurück. Kein Wissen über die interne Datenhaltung nötig, kein Zwang zu unserer Oberfläche.
Überblick
Drei Endpoints, asynchron: absenden → Status pollen → Ergebnis holen.
Self-contained
Alle Entitäten reisen im Request mit — keine Vor-Synchronisation, kein Datenbankzugriff bei uns.
Asynchron
Der Solver läuft im Hintergrund; du pollst den Job-Status und holst das Ergebnis.
Pro Paket limitiert
Problemgröße, Solver-Zeit, Rate-Limit und Parallelität sind je Tarif konfiguriert.
Basis-URLs
| Umgebung | Basis-URL |
|---|---|
| Production | https://api.vigotime.com |
| Sandbox | https://api.vigotime.com mit vt_test_-Token — kostenlos, isolierte Test-Organisation |
Authentifizierung
Jeder Aufruf trägt dein API-Token im X-API-Key-Header. Das Token ist
an deine Organisation gebunden — der Mandant kommt aus dem Token, nie aus einem Header.
Fünf Tokens einer Organisation teilen sich dieselben Limits.
Sandbox-Key holen — kostenlos, in einer Minute
E-Mail eintragen, Code bestätigen, losplanen: Du bekommst eine isolierte
Sandbox-Organisation und einen vt_test_-Key (Free-Tier-Limits, wird nie abgerechnet).
Dein Sandbox-Key — wird genau einmal angezeigt, jetzt sicher ablegen:
X-API-Key: vt_live_<dein-token> # Produktion — kostet Token
X-API-Key: vt_test_<dein-token> # Sandbox — kostenlos, isolierte Test-Organisation
POST /api/v1/tokens).
Der Klartext wird genau einmal angezeigt — leg ihn in einem Secret-Manager ab, niemals im
Code. Widerruf ist sofort wirksam.Präfix vt_live_ vs. vt_test_: Sandbox-Tokens laufen gegen eine Test-Organisation,
werden nie abgerechnet und sind an der ersten Zeichenfolge erkennbar (auch für Secret-Scanner).
Datenschutz & Datenminimierung
Der Solver rechnet mit IDs, nicht mit Personen. Er braucht keine Namen,
keine Geburtsdaten, keine Klarnamen — nur eine id je Mitarbeiter und die fachlichen
Attribute (Qualifikationen, Vertragsstunden, Verfügbarkeit).
employee_id), du mappst sie lokal auf deine Mitarbeiter.Konkret: ein employees-Eintrag braucht id, qualificationIds,
contractHoursPerWeek — kein firstName/lastName.
Solve-Jobs werden zeitlich begrenzt aufbewahrt (Ergebnis-Retention) und sind an deine Organisation
gebunden; ohne Namen ist selbst dieser Bestand frei von Klardaten.
KI-Assistenten (MCP)
Der Planner ist ein MCP-Server: Claude, ChatGPT und Gemini können den
Solver direkt bedienen — „Plane den September für diese 6 Leute" wird zum Tool-Call. Endpoint:
https://api.vigotime.com/mcp,
Auth wie bei der REST-API über X-API-Key (zum Ausprobieren: vt_test_-Token). Es gelten dieselben Limits, Kosten und
Mandanten-Grenzen — der MCP-Zugang ist kein zweiter Weg an der Abrechnung vorbei.
Ein Server, drei Anbindungswege
Das Protokoll (MCP) ist überall gleich — Transport und Auth-Standard unterscheiden sich je KI-Plattform. Derselbe Endpoint bedient alle drei:
| KI | Transport | Auth-Standard | Einrichtung |
|---|---|---|---|
| Claude (Code/Desktop) | Remote-HTTP oder lokal | Custom-Header (X-API-Key) | Einzeiler bzw. Config — siehe unten |
| Gemini (CLI) | Remote-HTTP | Custom-Header (X-API-Key) | settings.json — siehe unten |
| ChatGPT | nur Remote-HTTP | OAuth 2.1 (Discovery, Dynamic Client Registration, PKCE) | URL eintragen, Key auf unserer Autorisierungsseite einfügen — siehe unten |
Werkzeuge & eingebaute Prompts
| Tool | Zweck |
|---|---|
example_payload | Sofort lauffähiger Beispiel-Job (simple oder wishes mit Social Points) |
solve_submit | Solve-Job einreichen (self-contained Payload) |
solve_status | Status/Fortschritt pollen |
solve_result | Fertigen Plan abholen (assignments, unbesetzte Schichten mit Begründung) |
account_balance | Token-Guthaben der Organisation |
Dazu drei mitgelieferte Prompts: plan_woche, plan_mit_wuenschen
(Social-Points-Economy) und datenschutz_check (ersetzt Klarnamen durch IDs, bevor
etwas das Haus verlässt).
Claude einbinden
Claude Code (Terminal) — ein Befehl:
claude mcp add --transport http vigotime https://api.vigotime.com/mcp \
--header "X-API-Key: vt_test_<dein-sandbox-token>"
Claude Desktop (oder jeder Client ohne Header-Support) über die mcp-remote-Brücke —
in claude_desktop_config.json:
{
"mcpServers": {
"vigotime": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://api.vigotime.com/mcp",
"--header", "X-API-Key:${VIGOTIME_API_KEY}"],
"env": { "VIGOTIME_API_KEY": "vt_test_<dein-sandbox-token>" }
}
}
}
ChatGPT einbinden
ChatGPT verbindet sich per OAuth — du brauchst nur deinen API-Key bereitzuhalten:
- Einstellungen → Plugins/Connectors → „+" → URL
https://api.vigotime.com/mcpeintragen. - ChatGPT registriert sich automatisch und öffnet die vigotime-Autorisierungsseite.
- Dort deinen Key (
vt_test_…odervt_live_…) einfügen → Zugriff erlauben. Der Key wird nur geprüft, nie gespeichert.
Danach stehen die Solver-Tools direkt in ChatGPT bereit — mit denselben Limits, Kosten und Mandanten-Grenzen wie über den Key selbst (Sandbox-Zugriff läuft 4 Wochen, Live 90 Tage; danach einfach neu verbinden).
Gemini einbinden
Gemini CLI — in ~/.gemini/settings.json:
{
"mcpServers": {
"vigotime": {
"httpUrl": "https://api.vigotime.com/mcp",
"headers": { "X-API-Key": "vt_test_<dein-sandbox-token>" }
}
}
}
Zum Losspielen: drei Prompts
# 1 — Erster Plan in 2 Minuten
Hole dir mit example_payload ein Beispiel, löse es mit solve_submit,
polle solve_status bis completed und erkläre mir das Ergebnis:
Wer arbeitet wann, was blieb unbesetzt und warum?
# 2 — Eigener Mini-Plan
Plane den nächsten Monat für 5 Mitarbeiter (IDs m1–m5, je 38,5 h/Woche)
mit Früh- (06–14) und Spätdienst (14–22), mindestens 1 Person je Schicht.
m3 wünscht sich die ersten beiden Samstage frei (Priorität hoch).
# 3 — Social Points ausprobieren
Hole example_payload(kind='wishes') und erkläre mir, wie social_points
und wish_costs das Wunsch-Honorieren steuern. Ändere das Guthaben von
mitarbeiter-01 auf 20 und zeige den Unterschied im Ergebnis.
planning_rules). Ohne sie gilt
nur „max. eine Schicht pro Tag" — keine Ruhezeiten, keine Folgetage-Grenzen, keine
Wochenstunden-Kappen. Für rechtssichere Pläne (z. B. ArbZG: 11 h Ruhe, max.
6 Folgetage) die Regeln explizit mitgeben, etwa
{"consequences":[{"type":"limitConsecutiveDays","params":{"maxDays":6,"requiredRestDays":1}}],"priority":"legal","status":"active"}
und minRestTime. Das ist Absicht: DU bestimmst das Regelwerk, nicht wir.Regel-Referenz: die wichtigsten Consequence-Typen
Jede Regel ist {"conditions": [...], "consequences": [...], "priority": "...", "status": "active"}.
Die Param-Namen sind exakt — falsche Namen machen eine Regel still wirkungslos.
priority steuert hart/weich: legal/operational/critical = hart,
fairness/preference/wish = weich (Ausnahmen vermerkt).
| Consequence | Params (exakt) | Wirkung |
|---|---|---|
requireMinRest | {"hours": 11} | Mindestruhe zwischen Diensten — bindet auch über Plangrenzen (Rand-Kontext) |
limitConsecutiveDays | {"maxDays": 5, "requiredRestDays": 2} | Max. Folgetage + Pflicht-Ruhetage danach, grenzüberschreitend |
limitWorkingHours | {"maxHours": 40, "period": "week"} | Stunden-Kappe je Tag/Woche (Woche inkl. Vorplan-Rand) |
blockDate / worksOnDate | {"dateType": "specific", "specificDates": ["2027-05-16"]} | Datums-Kopplungen, z. B. „Ostern gearbeitet → Pfingsten frei" — künftige Abwesenheiten mitgeben (vacations darf über end_date hinaus) |
requireMinWorkBlock | {"minDays": 2} | Keine Ein-Tages-Arbeitsinseln — Dienste kommen in Blöcken |
requireMinFreeBlock | {"minDays": 2} | Freie Zeit in Blöcken — kein einzelner freier Tag |
keepShiftTypeBlocks | {"minBlock": 2} | Kein Schichttyp-Wechsel an aufeinanderfolgenden Arbeitstagen |
preferForwardRotation | {"order": ["early","late","night"]} | Ergonomische Vorwärtsrotation — immer weich |
requireShiftBlocks | {"shiftType": "night", "minBlock": 2, "minIsHard": true, "targetBlock": 3, "maxBlock": 4} | Schichttyp-Blöcke, dreistufig: minBlock + minIsHard = harte Untergrenze (ohne das Flag ein weiches Ziel), targetBlock = weiches Ziel, maxBlock = immer harte Obergrenze. Es gilt minBlock ≤ targetBlock ≤ maxBlock, sonst wird die Regel verworfen und gemeldet. |
keepWeekendsWhole | {} | Wochenende ganz frei ODER ganz Dienst |
limitConsecutiveWorkingWeekends | {"max": 2} | Max. Arbeits-Wochenenden in Folge, zählt über Plangrenzen |
limitShiftTypesPerWeek | {"max": 2} | Max. verschiedene Schichttypen je Kalenderwoche |
Vollständige, generierte Referenz aller Conditions/Consequences folgt (in Arbeit) —
bis dahin liefert example_payload(kind='rules') im MCP ein verifiziertes Beispiel.
datenschutz_check-Prompt hilft dabei.Quickstart
Job absenden, Status pollen, Ergebnis holen — mit curl und jq.
BASE=https://api.vigotime.com
KEY=vt_test_<dein-sandbox-token> # Sandbox: kostenlos; vt_live_ für Produktion
# 1) Job absenden
JOB=$(curl -s -X POST "$BASE/api/v1/solve" \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{
"start_date": "2026-07-01",
"end_date": "2026-07-07",
"department": { "id": "d1", "name": "Pflege Station 1" },
"employees": [
{ "id": "e1", "primaryDepartmentId": "d1", "contractHoursPerWeek": 40, "qualificationIds": ["q_exam"] }
],
"shifts": [
{ "id": "s_frueh", "departmentId": "d1", "name": "Frühdienst",
"startTime": "06:00", "endTime": "14:00", "shiftType": "early", "minStaff": 1 }
],
"config": { "timeout_seconds": 30 }
}' | jq -r .job_id)
# 2) Status pollen
curl -s "$BASE/api/v1/solve/$JOB" -H "X-API-Key: $KEY" | jq .status
# 3) Ergebnis holen
curl -s "$BASE/api/v1/solve/$JOB/result" -H "X-API-Key: $KEY" | jq .
Beispiel-Antwort des Ergebnisses:
{
"success": true,
"status": "optimal",
"assignments": [
{ "employee_id": "e1", "shift_id": "s_frueh", "date": "2026-07-01" }
],
"unassigned_shifts": [],
"execution_time_seconds": 0.8
}
Tutorial: Vom Job zum Plan
Drei vollständige, real durchgerechnete Beispiele — beide Payloads liefen durch den echten Solver, die Antworten sind sein Ergebnis. Alle Mitarbeiter nur als ID (siehe Datenschutz).
Einfach: drei Mitarbeiter, ein Frühdienst, eine Woche
Ein Frühdienst pro Tag (minStaff: 1), drei Mitarbeiter, eine gesetzliche Regel
(11 Stunden Mindestruhe). Der Solver verteilt die sieben Tage fair.
curl -X POST "$BASE/api/v1/solve" -H "X-API-Key: $KEY" -H "Content-Type: application/json" -d '{
"start_date": "2026-09-01",
"end_date": "2026-09-07",
"department": {
"id": "station-1",
"name": "Pflege Station 1"
},
"employees": [
{
"id": "mitarbeiter-01",
"status": "active",
"primaryDepartmentId": "station-1",
"departmentIds": [
"station-1"
],
"qualificationIds": [],
"contractHoursPerWeek": 40
},
{
"id": "mitarbeiter-02",
"status": "active",
"primaryDepartmentId": "station-1",
"departmentIds": [
"station-1"
],
"qualificationIds": [],
"contractHoursPerWeek": 40
},
{
"id": "mitarbeiter-03",
"status": "active",
"primaryDepartmentId": "station-1",
"departmentIds": [
"station-1"
],
"qualificationIds": [],
"contractHoursPerWeek": 40
}
],
"shifts": [
{
"id": "frueh",
"name": "Frühdienst",
"departmentId": "station-1",
"startTime": "06:00",
"endTime": "14:00",
"shiftType": "early",
"minStaff": 1,
"requiredQualificationIds": [],
"isActive": true
}
],
"planning_rules": [
{
"id": "ruhe-11h",
"name": "ruhe-11h",
"status": "active",
"priority": "legal",
"conditionLogic": "AND",
"conditions": [],
"consequences": [
{
"type": "requireMinRest",
"params": {
"hours": 11
}
}
]
}
],
"config": {
"timeout_seconds": 20
}
}'
Antwort nach dem Abholen (/result):
{
"status": "optimal",
"assignmentCount": 7,
"assignments": [
{
"employee_id": "mitarbeiter-01",
"shift_id": "frueh",
"date": "2026-09-03"
},
{
"employee_id": "mitarbeiter-01",
"shift_id": "frueh",
"date": "2026-09-04"
},
{
"employee_id": "mitarbeiter-01",
"shift_id": "frueh",
"date": "2026-09-05"
},
{
"employee_id": "mitarbeiter-02",
"shift_id": "frueh",
"date": "2026-09-01"
},
{
"employee_id": "mitarbeiter-02",
"shift_id": "frueh",
"date": "2026-09-02"
},
{
"employee_id": "mitarbeiter-03",
"shift_id": "frueh",
"date": "2026-09-06"
}
],
"unassignedShifts": 0,
"executionTimeSeconds": 1.01
}
optimal, nichts unbesetzt — kein Tag ohne
Besetzung, keine Regel verletzt.Komplex: 6 Mitarbeiter, Früh/Spät/Nacht, ein ganzer Monat
Sechs Mitarbeiter (drei examiniert), drei Schichten — der Nachtdienst verlangt eine examinierte Kraft —, ein voller Monat, drei Regeln (Ruhezeit, höchstens 5 Folgetage, max. 10 h/Tag) und ein Wunsch: mitarbeiter-01 will den 3. September frei.
{
"start_date": "2026-09-01",
"end_date": "2026-09-30",
"department": {
"id": "station-1",
"name": "Pflege Station 1"
},
"employees": [
{
"id": "mitarbeiter-01",
"status": "active",
"primaryDepartmentId": "station-1",
"departmentIds": [
"station-1"
],
"qualificationIds": [
"examiniert"
],
"contractHoursPerWeek": 40
},
{
"id": "mitarbeiter-04",
"status": "active",
"primaryDepartmentId": "station-1",
"departmentIds": [
"station-1"
],
"qualificationIds": [],
"contractHoursPerWeek": 40
},
/* … insgesamt 6 Mitarbeiter … */
],
"shifts": [
{
"id": "frueh",
"name": "Frühdienst",
"departmentId": "station-1",
"startTime": "06:00",
"endTime": "14:00",
"shiftType": "early",
"minStaff": 2,
"requiredQualificationIds": [],
"isActive": true
},
{
"id": "spaet",
"name": "Spätdienst",
"departmentId": "station-1",
"startTime": "14:00",
"endTime": "22:00",
"shiftType": "late",
"minStaff": 1,
"requiredQualificationIds": [],
"isActive": true
},
{
"id": "nacht",
"name": "Nachtdienst",
"departmentId": "station-1",
"startTime": "22:00",
"endTime": "06:00",
"shiftType": "night",
"minStaff": 1,
"requiredQualificationIds": [
"examiniert"
],
"isActive": true
}
],
"planning_rules": [
{
"id": "ruhe-11h",
"name": "ruhe-11h",
"status": "active",
"priority": "legal",
"conditionLogic": "AND",
"conditions": [],
"consequences": [
{
"type": "requireMinRest",
"params": {
"hours": 11
}
}
]
},
{
"id": "max-5-folgetage",
"name": "max-5-folgetage",
"status": "active",
"priority": "legal",
"conditionLogic": "AND",
"conditions": [],
"consequences": [
{
"type": "limitConsecutiveDays",
"params": {
"maxDays": 5,
"requiredRestDays": 1
}
}
]
},
{
"id": "max-10h-tag",
"name": "max-10h-tag",
"status": "active",
"priority": "legal",
"conditionLogic": "AND",
"conditions": [],
"consequences": [
{
"type": "limitWorkingHours",
"params": {
"maxHours": 10,
"period": "day"
}
}
]
}
],
"wishes": {
"mitarbeiter-01": [
"2026-09-03"
]
},
"config": {
"timeout_seconds": 45
}
}
→ vollständiger Request (alle 6 Mitarbeiter)
Ergebnis:
{
"status": "optimal",
"assignmentCount": 120,
"assignments": [
{
"employee_id": "mitarbeiter-01",
"shift_id": "frueh",
"date": "2026-09-10"
},
{
"employee_id": "mitarbeiter-01",
"shift_id": "frueh",
"date": "2026-09-11"
},
{
"employee_id": "mitarbeiter-01",
"shift_id": "frueh",
"date": "2026-09-12"
},
{
"employee_id": "mitarbeiter-01",
"shift_id": "frueh",
"date": "2026-09-20"
},
{
"employee_id": "mitarbeiter-01",
"shift_id": "frueh",
"date": "2026-09-21"
},
{
"employee_id": "mitarbeiter-01",
"shift_id": "spaet",
"date": "2026-09-01"
}
],
"unassignedShifts": 0,
"executionTimeSeconds": 8.84
}
optimal. Die Qualifikations-Anforderung der
Nacht wird eingehalten, Folgetage- und Ruhezeit-Regeln greifen — und mitarbeiter-01 hat am
3. September keine Schicht, der Wunsch wurde erfüllt. Wünsche, die mit einer Regel kollidieren,
verlieren gegen die Regel; das Ergebnis bleibt regelkonform.Was, wenn es keine Lösung gibt?
Zu wenig Personal für die geforderte Besetzung? Dann ist status nicht optimal,
sondern infeasible — kein Fehler, sondern die ehrliche Antwort „so geht es nicht". Das Feld
unassigned_shifts nennt die nicht besetzbare Anforderung, damit du die Eingabe anpassen kannst.
Beispiel 3: Planungsregeln entfalten ihre Wirkung (ArbZG)
Gleicher Bedarf wie oben — 3 Mitarbeiter, Früh + Spät, 14 Tage — einmal ohne und einmal mit Regeln. Beide Läufe sind real durchgerechnet; der Unterschied ist messbar:
ohne planning_rules | mit Regeln | |
|---|---|---|
| Spät→Früh am Folgetag (nur 8 h Ruhe) | 2× (z. B. m3: 01.09. Spät → 02.09. Früh) | 0× — die 11-h-Regel verbietet den Übergang hart |
| Status | optimal | optimal — der Solver findet die regelkonforme Rotation |
Die beiden Regeln im Payload — priority: "legal" macht sie hart
(unverletzlich); fairness/preference wären weich (Strafe statt Verbot).
Ohne conditions gilt eine Regel bedingungslos:
"planning_rules": [
{ "id": "ruhe-11h", "name": "Mind. 11 h Ruhe zwischen Schichten",
"status": "active", "priority": "legal",
"consequences": [ { "type": "requireMinRest", "params": { "hours": 11 } } ] },
{ "id": "max-5-folgetage", "name": "Max. 5 Folgetage, danach 1 Ruhetag",
"status": "active", "priority": "legal",
"consequences": [ { "type": "limitConsecutiveDays",
"params": { "maxDays": 5, "requiredRestDays": 1 } } ] }
]
Auszug aus dem regelkonformen Ergebnis (kein einziger Spät→Früh-Wechsel mehr):
01.09. Di Früh: m3 Spät: m1
02.09. Mi Früh: m3 Spät: m1 ← m3 bleibt Früh (11 h eingehalten)
03.09. Do Früh: m2 Spät: m3 ← m3 wechselt Früh→Spät (16 h Ruhe, erlaubt)
04.09. Fr Früh: m1 Spät: m2
…
requireMinRest
erwartet { "hours": 11 } — ein falscher Name (z. B. minRestHours)
wird derzeit still ignoriert und die Regel bleibt wirkungslos. Im Zweifel im Ergebnis
rule_effectiveness prüfen: dort steht, ob eine Regel im Plan tatsächlich gebunden hat.Konzepte
Payload-Form
Mitarbeiter, Schichten und Abteilung werden als JSON-Objekte im vigotime-Entitätsformat
(camelCase) übergeben. Pflichtfeld je Entität ist id;
alles Weitere hat sinnvolle Defaults. Daten-Maps wie wishes, vacations oder
sick_leaves haben die Form { "employeeId": ["2026-07-03", …] }.
Asynchrones Job-Modell
POST /solve liefert sofort 202 mit einer job_id und dem aufgelösten
tier. Pollen über GET /solve/{job_id} bis status =
completed (oder failed); dann /result abrufen.
Status-Werte
| Job-Status | Solver-Status (im Ergebnis) |
|---|---|
pending, running, completed, failed, dead |
optimal, feasible, infeasible, unknown, model_invalid |
dead heißt: der Job wurde nach mehreren Fehlversuchen aufgegeben (nicht stille Endlosschleife).
infeasible ist kein Fehler, sondern ein gültiges Ergebnis — es gibt keinen regelkonformen Plan;
unassigned_shifts nennt die nicht besetzbare Anforderung.
Token-Kosten
Jede Aktion kostet Token nach Komplexität — ein Guthaben, keine getrennten Zähler. Nur der Solve skaliert mit der Größe, alles andere ist günstig; Lesezugriffe kosten nichts.
| Operation | Kosten |
|---|---|
solve | 1 + ⌈(MA × Tage × Schichten) / 1000⌉ |
validate, export | 1 (fix) |
Lesezugriffe (GET) | 0 |
Die Kosten stehen vor dem Lauf fest (Problemgröße), sind also vorhersagbar. Jede
Antwort trägt die tatsächlichen Kosten im Header X-Token-Cost; das erledigte Job-Dokument
trägt zusätzlich Metriken (Dauer, Kosten, Komplexität, Ergebnis).
Job-Metriken
Ein abgeschlossener Job dokumentiert sich selbst — im Feld metrics:
{
"durationSeconds": 4.2, // Wall-Clock inkl. Wartezeit
"solverSeconds": 3.8, // reine Rechenzeit
"problemSize": 7750, // MA × Tage × Schichten
"tokenCost": 9,
"solverStatus": "optimal",
"assignments": 412,
"unassignedShifts": 0,
"attempts": 1 // >1 = hat einen Worker-Ausfall überlebt
}
Limits pro Tarif
Der API-Key wird einem Tarif zugeordnet; dieser bestimmt die Grenzen. Die Solver-Laufzeit
wird serverseitig hart gedeckelt (min(config.timeout_seconds, Tarif-Cap)).
| Tarif | max. Problemgröße (MA × Tage × Schichten) | Solver-Zeit | Req/min | parallel |
|---|---|---|---|---|
free | 840 | 15 s | 10 | 1 |
standard | 15 500 | 30 s | 30 | 3 |
pro | 99 200 | 60 s | 60 | 3 |
app (Vollanwendung) | 620 000 | 120 s | 120 | 2 |
enterprise | ∞ | 120 s | 300 | 4 |
422
bevor der Solver startet. Bei Überschreiten des Rate-Limits kommt 429 mit
Retry-After.Guthaben & Abrechnung
Sieh, was du hast und was du ausgegeben hast — jede Abbuchung ist ein Beleg mit Job-Bezug.
Guthaben abfragen
curl "$BASE/api/v1/account/balance" -H "X-API-Key: $KEY"
# → { "balance": 291 }
Verbrauch (Ledger)
Ein append-only Ledger: jede abgerechnete Operation als eigener Beleg — wann, was, wie viel, welcher Job, mit Metadaten. Damit lässt sich eine Rechnung Zeile für Zeile nachrechnen.
curl "$BASE/api/v1/account/usage?limit=50" -H "X-API-Key: $KEY"
{
"count": 2,
"records": [
{
"at": "2026-09-01T10:14:22Z",
"operation": "solve",
"cost": 9,
"balanceAfter": 291,
"reference": "3f9c…", // die Job-ID des Solves
"metadata": { "problemSize": 7750, "tier": "pro" }
},
{
"at": "2026-09-01T09:58:03Z",
"operation": "solve",
"cost": 6,
"balanceAfter": 300,
"reference": "1a2b…",
"metadata": { "problemSize": 4500, "tier": "standard" }
}
]
}
reference verweist auf den Job — von jeder Ledger-Zeile kommst du zurück
zum konkreten Solve und seinen Metriken (Dauer, Komplexität, Ergebnis). balanceAfter ist der
Saldo direkt nach der Abbuchung. Guthaben und Ledger sind an deine Organisation gebunden; ein
Parameter kann den Mandanten nicht wechseln.Fehler
Fehler sind maschinenlesbar: ein stabiler code (der Vertrag — darauf verzweigst du),
die auslösenden Parameter in data, und eine englische Default-Meldung. 402 heißt „Token
kaufen", 429 heißt „warten" — verschiedene Handlungsanweisungen, nie zusammenwerfen.
HTTP/1.1 429 Too Many Requests
Retry-After: 60
{ "detail": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded for tier 'standard' (30/min)",
"data": { "tier": "standard", "limit": 30, "windowSeconds": 60, "retryAfter": 60 }
} }
| Code | HTTP | Bedeutung / was tun |
|---|---|---|
RATE_LIMIT_EXCEEDED | 429 | Rate-Limit — Retry-After abwarten, dann erneut. |
CONCURRENCY_LIMIT | 429 | Zu viele Solves gleichzeitig für den Tarif. Warten. |
INSUFFICIENT_BALANCE | 402 | Token kaufen — erneutes Senden ohne Aufladen scheitert wieder. |
PROBLEM_TOO_LARGE | 422 | Payload über dem Tarif-Limit — Fenster/Team verkleinern oder Tarif hochstufen. |
WEBHOOK_URL_INVALID | 400 | Webhook-Ziel abgelehnt (nur https, keine privaten Hosts). |
JOB_NOT_FOUND | 404 | Unbekannter oder fremder Job (nicht unterscheidbar — Absicht). |
Vollständige API-Referenz
Alle Felder, Schemas und Beispiele interaktiv: