SovrGPT Dokumentation
API

Decision Engine API (POST /v1/decisions)

Decision Engine API – Text oder JSON und Fragen mit festen Optionen senden, Wahrscheinlichkeiten und eine Empfehlung in Millisekunden zurückbekommen.

Die Decision Engine beantwortet Fragen mit festen Antwortmöglichkeiten, etwa: Welches Team ist zuständig, wie dringend ist das, muss ein Mensch prüfen? Sie erzeugt dafür keinen Text. Für jede Frage rechnet das Modell einen Vorwärtsdurchlauf, und wir lesen die Wahrscheinlichkeit jeder erlaubten Option direkt ab. Deshalb kommt eine Entscheidung in Millisekunden zurück, wo eine Chat-Antwort Sekunden braucht.

Base-URL: https://sovrgpt.com/api/v1 · Scope: decisions · Auth: Authorization: Bearer sov_…

Geändert am 23.09.2026: Ohne policy gilt jetzt der Modus threshold (vorher review_only), und die Doppelprüfung ist an. Wie Sie das alte Verhalten behalten, steht unter Automatik oder Mensch. Grenzen, Preise und Modellversionen können sich noch ändern.

Eine Entscheidung anfragen

curl https://sovrgpt.com/api/v1/decisions \
  -H "Authorization: Bearer $SOVR_KEY" -H "content-type: application/json" \
  -d '{
    "preset": "ticket-routing-v1",
    "state": "Seit heute früh kann ich mich nicht mehr anmelden. Außerdem wurde meine Rechnung doppelt abgebucht — bitte prüfen Sie das dringend."
  }'

Ohne policy gilt die Vorgabe { "mode": "threshold", "threshold": 0.85, "stability_check": true }.

Antwort (gekürzt). Die Wahrscheinlichkeiten stammen aus einer Messung auf sovr-decision-v2; Empfehlung, Warnsignale, Doppelprüfung, Verbrauch und Zeiten sind zur Veranschaulichung ergänzt:

{
  "object": "decision",
  "id": "dec_3f1c…",
  "request_id": null,
  "status": "completed",
  "model": { "id": "sovr-decision-v2", "runtime": "…", "operator": "…", "country": "IE", "prompt_version": "…", "via": "platform" },
  "preset": { "id": "ticket-routing-v1", "version": "1.0.0" },
  "answers": {
    "team": {
      "type": "choice",
      "value": "support",
      "probabilities": { "support": 0.6512, "billing": 0.0367, "sales": 0.0044, "unclear": 0.3076 },
      "max_probability": 0.6512,
      "margin": 0.75,
      "margin_is_lower_bound": false,
      "normalized_entropy": 0.58,
      "calibration": { "status": "uncalibrated" },
      "recommendation": "review",
      "reasons": ["below_threshold"],
      "stability": { "rotations": 2, "stable": true, "alternatives": [] },
      "abstain": true,
      "abstain_reasons": ["below_threshold", "calibration_unavailable"],
      "diagnostics": { "label_mass": 0.9967, "low_label_mass": false, "missing_labels": [] }
    },
    "urgency": { "type": "score", "value": 3, "expected_index": 3.29, "normalized_score": 0.82, "max_probability": 0.7042, "recommendation": "review", "reasons": ["below_threshold"], "stability": null, "…": "…" },
    "needs_human": { "type": "binary", "value": true, "probability_true": 0.9933, "recommendation": "act", "reasons": [], "stability": null, "…": "…" },
    "language": { "type": "choice", "value": "de", "max_probability": 0.9993, "recommendation": "act", "reasons": [], "stability": { "rotations": 2, "stable": true, "alternatives": [] }, "…": "…" }
  },
  "policy": { "mode": "threshold", "threshold": 0.85, "stability_check": true },
  "usage": { "input_tokens": 1290, "output_tokens": 6, "evaluated_questions": 4, "forward_passes": 6 },
  "timing_ms": { "total": 430, "inference_max": 405, "inference_sum": 1030 }
}

So liest sich das: team und urgency liegen unter der Schwelle 0,85 und gehen an einen Menschen, needs_human und language sind unauffällig. Zwei der vier Fragen sind Auswahlfragen, deshalb sechs Durchläufe statt vier.

recommendation bewertet die Verlässlichkeit der Antwort, nicht ihren Inhalt. needs_human: true mit "act" heißt: Die Engine hat ohne Warnsignal entschieden, dass ein Mensch auf das Ticket schauen soll. Ihre Anwendung kann es ohne weitere Prüfung dorthin leiten.

Anfrage

FeldPflichtBedeutung
state oder messagesgenau einesDer zu beurteilende Zustand: Text, JSON-Objekt/-Array oder ein Gesprächsverlauf ([{role, content}], nur Text). Wird als Daten behandelt, nie als Anweisung.
questions oder presetgenau einesEigene Fragen (siehe unten) oder eine hinterlegte Vorlage (GET /v1/decisions/presets).
modelneinsovr-decision-v1 oder sovr-decision-v2. Ohne Angabe: Vorgabe der Organisation.
imagesneinBis zu 2 Bilder als [{ "data_url": "data:image/png;base64,…" }], je ≤ 4 MB. Nur data:-Adressen; der Dienst lädt keine fremden URLs.
policynein{ "mode": …, "threshold": …, "stability_check": … }, jedes Feld einzeln weglassbar. Vorgabe: threshold, 0,85, Doppelprüfung an. Siehe Automatik oder Mensch.
request_idneinFrei wählbar, wird gespiegelt (Korrelation).

Fragetypen

{
  "team":  { "type": "choice", "instructions": "Welches Team zuerst?", "options": [ { "id": "support", "description": "Technik" }, { "id": "billing" }, { "id": "unclear", "description": "nicht zuordenbar" } ] },
  "urgency": { "type": "score", "instructions": "Wie dringend?", "levels": ["nicht", "wenig", "normal", "dringend", "sofort"] },
  "needs_human": { "type": "binary", "instructions": "Muss ein Mensch prüfen?", "true_description": "ja", "false_description": "nein" }
}
TypErgebnis
choice — 2–26 Optionenvalue = Options-Id mit der höchsten Wahrscheinlichkeit, probabilities über alle Optionen
score — 2–10 geordnete Stufenvalue = wahrscheinlichste Stufe (0-basiert), expected_index = Σ p·i, normalized_score = expected/(n−1). Ein Score ist keine physikalische Skala.
binaryvalue = true/false, probability_true

Die Reihenfolge zählt: Optionen und Fragen werden in Quellreihenfolge bewertet, und ein Modell kann auf die Reihenfolge reagieren. Bei Auswahlfragen prüft die Engine das mit der Doppelprüfung. Bei Stufen ist die Reihenfolge die Bedeutung und bleibt, wie Sie sie schicken.

Vorlagen (Presets)

Eine Vorlage bindet Fragen und Optionen an eine Kennung; Sie schicken nur preset und state. preset und questions schließen sich aus (400). Zielgruppe, Situation, Beispielaufruf und Umgang mit der Antwort für alle zehn Vorlagen stehen unter Anwendungsfälle.

presetNameFragen
ticket-routing-v1Ticket-Routingteam, urgency, needs_human, language
inbox-document-v1Posteingang-Klassifikationdoc_type, has_deadline, action_level, language
customer-feedback-v1Kundenfeedback-Analysesentiment, churn_risk, topic, needs_response
lead-qualification-v1Lead-Qualifizierungfit, timeline, budget_signal, decision_maker
behoerden-posteingang-v1Behörden-Posteingangart, zustaendigkeit, fristrelevant, eskalation
antrag-vollstaendigkeit-v1Antrag vollständig?vollstaendig, hauptmangel, widerspruch, naechster_schritt
veroeffentlichung-check-v1Freigabe-Check Veröffentlichungpersonenbezug_dritter, art9_daten, tonalitaet, freigabe
agent-handoff-v1Agenten-Weicheaction, risk, sensitive_data
grounding-check-v1Quellentreue-Prüfungsupport, unsupported_claims, contradiction, verdict
agent-step-risk-v1Agenten-Schritt freigebenirreversible, effect, risk
  • GET /v1/decisions/presets liefert je Vorlage die vollständigen Fragen, dazu audience (Zielgruppe) und example_state (einen realistischen Beispielzustand).
  • Drei Vorlagen erwarten ein JSON-Objekt als state: antrag-vollstaendigkeit-v1 (Antrag mit pflichtfelder, erforderliche_unterlagen, eingereichte_unterlagen, unterschrift_vorhanden, anmerkung), grounding-check-v1 (question, sources, answer) und agent-step-risk-v1 (Ziel, aktuelle Seite und geplanter Schritt des Agenten, im Beispiel ziel, seite, schritt). Ihr example_state ist deshalb ein JSON-Text; beim Aufruf schicken Sie das Objekt direkt, nicht als Zeichenkette.
  • Jede Auswahlfrage einer Vorlage hat als letzte Option eine fachliche Enthaltung (unclear oder other). Wählt das Modell sie, steht chose_abstain_option in reasons, und die Empfehlung ist "review", außer im Modus autonomous.
  • Dieselbe Engine mit denselben Vorlagen gibt es im Chat als Werkzeug „Entscheiden“ (decision_engine) und im MCP-Server als sovrgpt_decide / sovrgpt_list_decision_presets.

Modelle

ModellBetriebBilderCharakter
sovr-decision-v1 (Vorgabe)Deutschland (STACKIT)jasehr schnell, sehr entschieden: Wahrscheinlichkeiten fast immer nahe 1,0 oder 0
sovr-decision-v2EU, europäisches Partner-Rechenzentrum (Irland/Finnland), Zero Retention vertraglichjafeiner aufgelöste Wahrscheinlichkeiten, geeignet für Schwellenwert-Logik

Beide laufen auf offenen Grundmodellen; welche Laufzeit rechnet, legen wir fest. Welche Version ohne Angabe gilt und wo sie rechnet, liefert GET /v1/decisions/models je Organisation. Owner und Admins ändern die Vorgabe unter Decision Engine → Einstellung dieser Organisation.

Wartezeit. Gemessen von Jena aus, warm, Ticket-Text mit vier Optionen, ohne Doppelprüfung: 65 ms je Frage auf sovr-decision-v1, 198 ms auf sovr-decision-v2; vier Fragen gleichzeitig 123 bzw. 397 ms. Eine Chat-Antwort zum selben Text braucht auf denselben Modellen 5–30 s. Diese Werte sind keine Zusage; sie hängen von Anbieter, Last und Länge des Zustands ab. Der zweite Durchlauf der Doppelprüfung läuft gleichzeitig mit dem ersten. Wir erwarten deshalb nur einen geringen Anstieg, nachgemessen haben wir ihn noch nicht. Die tatsächliche Dauer steht in jeder Antwort unter timing_ms: inference_max ist der längste einzelne Durchlauf, inference_sum die Summe aller.

Automatik oder Mensch: policy und recommendation

Jede Antwort trägt eine Empfehlung: recommendation: "act" heißt, Ihre Anwendung kann mit der Antwort weitermachen; "review" heißt, vorher sollte ein Mensch prüfen. Den Grund nennt reasons. Die Bewertung selbst ist in beiden Fällen vollständig. Die Engine handelt nie selbst: Verbindliche Freigaben, Zugriffsrechte und Aktionen bleiben bei Ihrer Anwendung.

Drei Modi (policy.mode)

ModusrecommendationWofür
threshold (Vorgabe)"act", solange reasons leer ist; sonst "review"Automatik mit Empfehlung: Unauffällige Antworten laufen durch, auffällige gehen an einen Menschen.
review_onlyimmer "review"; reasons enthält review_only_policyAlles prüfen, etwa in der Einführungsphase oder wo eine menschliche Prüfung Pflicht ist.
autonomousimmer "act"Es gibt keinen Menschen, zum Beispiel bei einer Figur in einem Computerspiel oder einer Maschine, die selbst handelt. Die Warnsignale stehen trotzdem in reasons. Ihre Anwendung kann darauf vorsichtiger reagieren: ein sicheres Standardverhalten wählen, langsamer werden, protokollieren.
policy-FeldWerteVorgabe
modethreshold, review_only, autonomousthreshold
thresholdZahl größer 0 bis einschließlich 10.85
stability_checktrue / falsetrue

Jedes Feld ist einzeln weglassbar; was fehlt, nimmt die Vorgabe. threshold wirkt im Modus threshold als Grenze, in autonomous nur als Warnsignal (below_threshold in reasons), in review_only gar nicht. Die angewendete Richtlinie kommt in jeder Antwort zurück: "policy": { "mode": "threshold", "threshold": 0.85, "stability_check": true }.

Umstieg vom Verhalten vor dem 23.09.2026: Genau das alte Verhalten zu den alten Kosten (jede Antwort "review", ein Durchlauf je Frage) bekommen Sie mit "policy": { "mode": "review_only", "stability_check": false }. Mit "policy": { "mode": "review_only" } allein bleibt die Doppelprüfung an. Dann geht weiter jede Antwort an einen Menschen, der bei einer kippenden Auswahl zusätzlich unstable_under_reordering in reasons sieht; dafür kostet jede Auswahlfrage doppelt Eingabetoken.

Warnsignale (reasons)

reasons listet alle Warnsignale einer Antwort, in jedem Modus:

SignalBedeutung
review_only_policyModus review_only: Jede Antwort wird vorgelegt.
low_label_massWeniger als die Hälfte der Wahrscheinlichkeit lag auf den erlaubten Optionen (diagnostics.label_mass unter 0,5). Das Modell wollte eigentlich etwas anderes antworten; die Verteilung ist nicht belastbar.
labels_missing_from_top_logprobsMindestens eine Option stand nicht unter den Kandidaten des Modells (diagnostics.missing_labels), und die beste Option liegt unter 0,99. Fehlende Optionen bekommen Wahrscheinlichkeit 0; das ist angenommen, nicht gemessen.
below_thresholdmax_probability liegt unter policy.threshold. Kommt im Modus review_only nicht vor.
chose_abstain_optionGewählt wurde eine fachliche Enthaltung, also eine Auswahloption mit der Kennung unclear, other, unknown, none oder unsure.
unstable_under_reorderingDie Doppelprüfung hat mit umsortierten Optionen eine andere Option gewählt. Als einziges Signal markiert es eine sonst unauffällige Wahl (genug Label-Masse, keine Enthaltung) auch bei max_probability 1 als unsicher.
stability_unavailableDie Doppelprüfung war angefordert, ihr zweiter Durchlauf ist aber gescheitert (etwa gedrosselt oder zu spät). Die Antwort ist vollständig, aber unbestätigt: stability ist null, und im Modus threshold lautet die Empfehlung "review".

Ein leeres reasons heißt: Nichts Auffälliges gefunden. Eine Garantie, dass die Antwort stimmt, ist das nicht; die Werte sind unkalibriert (calibration.status: "uncalibrated").

Doppelprüfung (stability_check)

Mit stability_check: true (Vorgabe) stellt die Engine jede Auswahlfrage ein zweites Mal, gleichzeitig mit dem ersten Durchlauf. Die Optionen sind dabei zyklisch um die halbe Liste verschoben (gerundet; bei zwei Optionen also vertauscht). Bewertet wird die Antwort in Ihrer Reihenfolge. Der zweite Durchlauf fließt nicht ins Ergebnis ein, er zeigt nur, ob die Wahl kippt. Wählt er eine andere Option, ist stability.stable false, stability.alternatives nennt die abweichende Wahl, und reasons enthält unstable_under_reordering.

Fragetypstability
choice mit Doppelprüfung{ "rotations": 2, "stable": true, "alternatives": [] } oder stable: false mit der abweichenden Option
choice, zweiter Durchlauf gescheitertnull, dazu stability_unavailable in reasons
score, binarynull: Sie werden nicht doppelt gestellt; bei Stufen ist die Reihenfolge die Bedeutung
jede Frage mit stability_check: falsenull

Wenn nur der Prüfdurchlauf scheitert (die Laufzeit drosselt, antwortet zu spät oder unbrauchbar), kommt die Antwort trotzdem: bewertet in Ihrer Reihenfolge, mit stability: null und dem Warnsignal stability_unavailable. Weil nichts bestätigt ist, empfiehlt der Modus threshold dann "review". Scheitert der erste Durchlauf, also die bewertete Antwort selbst, scheitert die ganze Anfrage (siehe Fehler).

Warum es das gibt. Eine Auswahlantwort kann allein durch die Reihenfolge der Optionen kippen. Auf unserem internen Prüfsatz kam das selten vor, bei 3 von 86 Auswahlantworten je Version. Auf sovr-decision-v1 geschah es aber ausgerechnet bei angezeigter Sicherheit um 100 %. Beispiel: Ein vollständiger Antrag (antrag-vollstaendigkeit-v1) bekam in Quellreihenfolge naechster_schritt: "bearbeiten" mit 1,00, mit umsortierten Optionen "nachfordern" mit 0,97. Weder die Wahrscheinlichkeit noch margin oder eine Schwelle warnen davor; das zeigt nur der zweite Durchlauf. Auf sovr-decision-v2 kippten Antworten nur dort, wo sie ohnehin unsicher waren.

So sieht dieser Fall in der Antwort aus (nachgestellt, gekürzt):

"naechster_schritt": {
  "type": "choice",
  "value": "bearbeiten",
  "max_probability": 1,
  "recommendation": "review",
  "reasons": ["unstable_under_reordering"],
  "stability": { "rotations": 2, "stable": false, "alternatives": ["nachfordern"] },
  "abstain": true,
  "abstain_reasons": ["unstable_under_reordering", "calibration_unavailable"],
  "…": "…"
}

Im Modus autonomous bliebe recommendation bei "act" und abstain bei false; reasons enthielte trotzdem unstable_under_reordering.

Abschalten: "policy": { "stability_check": false }. Dann gibt es einen Durchlauf je Frage, stability ist null, und das Warnsignal unstable_under_reordering entfällt. Tun Sie das nur, wenn Sie die Reihenfolge anders absichern. Was die Doppelprüfung kostet, steht unter Kosten.

Altfelder abstain und abstain_reasons

Sie bleiben für bestehende Integrationen erhalten:

  • abstain ist genau dann true, wenn recommendation "review" ist.
  • abstain_reasons ist bei abstain: true gleich reasons plus calibration_unavailable, sonst leer.
  • Im Modus autonomous ist abstain also immer false und abstain_reasons leer; die Warnsignale stehen nur in reasons.

Neue Integrationen benutzen recommendation und reasons.

Was die Zahlen bedeuten

  • probabilities ist die Verteilung über Ihre Optionen. max_probability: 0.92 heißt: Das Modell gibt dieser Option 92 % der Masse. Es heißt nicht, dass sie zu 92 % richtig ist; calibration.status ist heute immer uncalibrated. Auf unserem internen Prüfsatz mit 122 Fällen lagen beide Versionen über 95 % richtig. Das ist eine Momentaufnahme auf unseren eigenen Beispielen und keine Zusage für Ihre Daten.
  • diagnostics.label_mass sagt, wie viel Wahrscheinlichkeit überhaupt auf den erlaubten Optionen lag. Liegt sie unter 0,5, wollte das Modell etwas anderes schreiben. Die Verteilung ist dann ein Artefakt, diagnostics.low_label_mass ist true, und reasons enthält low_label_mass.
  • sovr-decision-v1 antwortet meist mit 1,0 / 0 / 0, also mit wenig Zwischentönen. Eine Prozent-Schwelle greift dort selten. Verlässlicher sind zwei andere Netze: die fachliche Enthaltung (unclear, other), die unabhängig von der Prozentzahl chose_abstain_option auslöst, und die Doppelprüfung.

margin — die Zahl, auf die eine Schwelle gehört

margin ist der Abstand zwischen der besten und der zweitbesten Option, gemessen als log p(beste) − log p(zweitbeste) in nat. Anders als max_probability hat er nach oben keine Decke: Zwei Antworten, die beide als max_probability: 1 erscheinen, können Abstand 8,5 oder 19,2 haben.

Auf unserem internen Prüfsatz (463 Antworten je Version) trennt max_probability bei sovr-decision-v1 richtige von falschen Antworten mit AUROC 0,777. Eine Schwelle, die alle Fehler abfängt, gibt es dort nicht: Vier der elf falschen Antworten tragen max_probability von exakt 1. Über margin sind es AUROC 0,867, und ab einem Abstand von 20 lief in dieser Messung die Hälfte aller Antworten ohne einen einzigen Fehler durch.

Mehr Nachkommastellen bei max_probability würden das nicht lösen. Ein Grund ist die Rundung auf vier Stellen. Der andere: Bei rund einem Viertel der Antworten steht überhaupt nur eine Option unter den Kandidaten des Modells. Dann ist die Wahrscheinlichkeit rechnerisch exakt 1, und es gibt keine Auflösung, die man zurückgewinnen könnte. Für diesen Fall meldet margin_is_lower_bound: true: Der Abstand wurde gegen die Kandidatengrenze gerechnet und ist eine Untergrenze. Der wahre Abstand ist mindestens so groß, nie kleiner.

Eine brauchbare Schwelle ist keine Zusage. In einer Gegenprobe mit 400 zufälligen Halbierungen unseres Prüfsatzes rutschte in 72 % der Fälle kein Fehler durch, im schlimmsten Fall aber zehn. margin ist ein zweites Netz; das erste bleibt die fachliche Enthaltungsoption. Welcher Abstand bei Ihren Daten trägt, müssen Sie an Ihren Daten bestimmen. Unsere 20 gelten für unseren Prüfsatz.

recommendation (und damit das Altfeld abstain) wird aus max_probability, threshold und den übrigen Warnsignalen gebildet. margin wird ausgeliefert, fließt aber noch nicht in die Empfehlung ein. Wer auf margin schalten will, tut das in seiner Anwendung.

Fehler

StatuscodeBedeutung
400invalid_request_error, invalid_json, images_unsupportedRumpf ungültig, auch ein unbekanntes Feld in policy
401—Schlüssel fehlt, ungültig oder widerrufen
402org_spend_limit, user_spend_limitAusgabenlimit erreicht
403missing_scopeSchlüssel ohne Scope decisions
404unknown_preset, model_not_found
413request_too_large
429upstream_rate_limitedDie Laufzeit drosselt gerade (type: "rate_limit_error"). Kurz warten und wiederholen; berechnet wird nichts. Bis zum 23.09.2026 kam das als 502.
502 / 504upstream_error, upstream_timeoutDie bewertete Antwort (erster Durchlauf) kam nicht brauchbar oder nicht rechtzeitig. Es gibt keine Teilantwort, berechnet wird nichts. Scheitert nur der zweite Durchlauf der Doppelprüfung, ist das kein Fehler: Die Antwort kommt mit stability: null und stability_unavailable.
503runtime_unavailableIn dieser Umgebung ist keine gemessene Laufzeit erreichbar. Es wird nie still ein anderes Modell genommen.

Grenzen und Kosten

Grenzen

1–16 Fragen · Zustand ≤ 16 000 Zeichen · Frage ≤ 1 000 Zeichen · Beschreibung ≤ 512 Zeichen · Rumpf ≤ 64 KiB. Zu groß → 413. Unbekannte Felder werden mit 400 abgelehnt, auch innerhalb von policy. So sieht etwa ein tenant_id oder tools im Rumpf nie aus, als wirke es.

Kosten

  • Abgerechnet werden Eingabetoken und ein Ausgabetoken je Vorwärtsdurchlauf, und zwar nur für Durchläufe, die die Laufzeit verwertbar beantwortet hat.
  • Mit Doppelprüfung verbraucht jede Auswahlfrage doppelt Eingabetoken (zwei Durchläufe mit demselben Zustand) und zwei Ausgabetoken. Stufen- und Ja/Nein-Fragen kosten dasselbe wie ohne Doppelprüfung.
  • usage.forward_passes zählt alle beantworteten Durchläufe, usage.input_tokens und usage.output_tokens enthalten beide. usage.evaluated_questions zählt die Fragen. Beispiel ticket-routing-v1: vier Fragen, davon zwei Auswahlfragen → sechs Durchläufe.
  • Ein gescheiterter Prüfdurchlauf kostet nichts und fehlt in forward_passes. Scheitert die Anfrage insgesamt (Fehlercode), wird nichts berechnet.

Datenschutz

Zustand, Fragen und Antworten werden nicht gespeichert; in Ihrem Verbrauch stehen nur die abgerechneten Tokenzahlen. Bilder werden nicht nachgeladen; erlaubt sind nur data:-Adressen. Der Verarbeitungsort steht in jeder Antwort (model.operator, model.country).

Weitere Endpunkte

  • GET /v1/decisions/presets: die zehn hinterlegten Vorlagen mit Zielgruppe (audience), Beispielzustand (example_state) und Fragen.
  • GET /v1/decisions/models: die Versionen dieser Organisation, Vorgabe, Betreiber, Land, Messwerte, Limits.

Einen Playground mit echter Inferenz finden Sie nach der Anmeldung unter Decision Engine in der Seitenleiste.

Decision Engine API (POST /v1/decisions)