vigotime vigotime / developers

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.

REST / JSON Stateless Async Job-Modell Token-Auth (mandantengebunden) Tarif-Limits

Überblick

Drei Endpoints, asynchron: absenden → Status pollen → Ergebnis holen.

POST /api/v1/solve — Job absenden
GET /api/v1/solve/{job_id} — Status
GET /api/v1/solve/{job_id}/result — Ergebnis

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

UmgebungBasis-URL
Productionhttps://api.vigotime.com
Sandboxhttps://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

X-API-Key: vt_live_<dein-token>     # Produktion — kostet Token
X-API-Key: vt_test_<dein-token>     # Sandbox — kostenlos, isolierte Test-Organisation
Ein Admin deiner Organisation stellt Tokens aus (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).

Schick pseudonyme IDs. Was das Haus verlässt, enthält dann keine personenbezogenen Daten im Sinne der DSGVO — die Zuordnung ID → Person bleibt bei dir. Die Antwort referenziert dieselben IDs zurück (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:

KITransportAuth-StandardEinrichtung
Claude (Code/Desktop)Remote-HTTP oder lokalCustom-Header (X-API-Key)Einzeiler bzw. Config — siehe unten
Gemini (CLI)Remote-HTTPCustom-Header (X-API-Key)settings.json — siehe unten
ChatGPTnur Remote-HTTPOAuth 2.1 (Discovery, Dynamic Client Registration, PKCE)URL eintragen, Key auf unserer Autorisierungsseite einfügen — siehe unten

Werkzeuge & eingebaute Prompts

ToolZweck
example_payloadSofort lauffähiger Beispiel-Job (simple oder wishes mit Social Points)
solve_submitSolve-Job einreichen (self-contained Payload)
solve_statusStatus/Fortschritt pollen
solve_resultFertigen Plan abholen (assignments, unbesetzte Schichten mit Begründung)
account_balanceToken-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:

  1. Einstellungen → Plugins/Connectors → „+" → URL https://api.vigotime.com/mcp eintragen.
  2. ChatGPT registriert sich automatisch und öffnet die vigotime-Autorisierungsseite.
  3. Dort deinen Key (vt_test_… oder vt_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.
Wichtig — Regeln reisen im Payload: Der Solver erzwingt ausschließlich die Regeln, die du übergibst (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).

ConsequenceParams (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.

Abgrenzung: Dieser MCP-Server exponiert ausschließlich die öffentliche, token-authentifizierte Solve-API. Er ist getrennt vom KI-Assistenten innerhalb der vigotime-App und hat keinen Zugriff auf App-Daten. Und wie überall gilt die Datenminimierung: Schick dem Solver IDs, keine Klarnamen — der 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
}
Drei IDs, sieben Frühdienste, 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
}
120 Zuweisungen über den Monat, 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_rulesmit 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
Statusoptimaloptimal — 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
…
Achtung, Param-Namen sind exakt: 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-StatusSolver-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.

OperationKosten
solve1 + ⌈(MA × Tage × Schichten) / 1000⌉
validate, export1 (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)).

Tarifmax. Problemgröße
(MA × Tage × Schichten)
Solver-ZeitReq/minparallel
free84015 s101
standard15 50030 s303
pro99 20060 s603
app (Vollanwendung)620 000120 s1202
enterprise∞120 s3004
Überschreitet ein Payload die Problemgröße, antwortet die API mit 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 }
} }
CodeHTTPBedeutung / was tun
RATE_LIMIT_EXCEEDED429Rate-Limit — Retry-After abwarten, dann erneut.
CONCURRENCY_LIMIT429Zu viele Solves gleichzeitig für den Tarif. Warten.
INSUFFICIENT_BALANCE402Token kaufen — erneutes Senden ohne Aufladen scheitert wieder.
PROBLEM_TOO_LARGE422Payload über dem Tarif-Limit — Fenster/Team verkleinern oder Tarif hochstufen.
WEBHOOK_URL_INVALID400Webhook-Ziel abgelehnt (nur https, keine privaten Hosts).
JOB_NOT_FOUND404Unbekannter oder fremder Job (nicht unterscheidbar — Absicht).
Codes sind additiv: neue kommen jederzeit dazu. Ein Client, der einen unbekannten Code trifft, fällt auf den HTTP-Status zurück. Niemals hart auf einen unbekannten Code scheitern.

Vollständige API-Referenz

Alle Felder, Schemas und Beispiele interaktiv: