GodEngine decision api

Private beta

Quick start · curl and Python

Your first decision

Write a request, send it, read the record, fetch it again by id and retry it safely. The example is the one in the service's own documentation: a made-up machining business deciding whether to buy, lease or outsource.

Before you start

BetaThe Decision API is a private beta and is not switched on for outside callers yet. By design it answers only direct calls from allowed private networks, so these steps work only for an invited service whose access has been set up. How access works.

From your invitation you need two things:

  • A base URL. Routes on this site are written relative to it, for example POST /v1/decisions.
  • A gst1 service token with the scope decisions:write (to make decisions) and decisions:read (to read them back). It names your service and, usually, your tenant. Tokens last 24 hours by default and at most 7 days. See authentication.

Keep the token out of source code, logs, URLs and anything that runs in a browser. Put both values in your shell:

Shell setupsh
# Both values come with your invitation
export GE_BASE="<base URL from your invitation>"
export GE_TOKEN="<gst1 service token>"

Write the request

Save this as decision.json. It is plain JSON: a question, the evidence you have, two to twenty options with the facts you know about each, an optional constraint and an idempotency key you choose.

decision.jsonjson
{
  "question": "Should we buy a CNC machine, lease one, or outsource machining over the next 2 years?",
  "evidence": [
    {"source": "finance", "text": "We have a 10 lakh budget for this."},
    {"source": "vendor", "text": "Lease quote is ₹30,000 per month."},
    {"source": "cfo", "value": 10, "unit": "lakh"}
  ],
  "options": [
    {"id": "buy", "label": "Buy outright",
     "params": {"cost": "45 lakh", "upside": 0.9, "risk": "low", "time_months": 2}},
    {"id": "lease", "label": "Lease",
     "params": {"cost": "1.5 lakh", "monthly_cost": "30k", "upside": 0.6, "risk": "medium", "time_months": 1}},
    {"id": "outsource", "label": "Outsource",
     "params": {"monthly_cost": "25k", "upside": 0.35, "risk": "medium", "time_months": 0.5}}
  ],
  "constraints": {"budget": "10 lakh"},
  "task_id": "task-7f3a",
  "idempotency_key": "order-42"
}
  • Amounts can be written the way people write them: "45 lakh", "30k", "₹1.2 cr", "$2.5M" or {"value": 10, "unit": "lakh"}. Lakh and crore mean rupees.
  • The horizon (over the next 2 years) is read from the question and becomes 24 months. You can set it yourself with constraints.horizon_months.
  • task_id is optional: the id of your own task that this decision serves.
  • With a token that names your tenant, leave tenant_id out. A broker token (one that acts for many tenants) must send "tenant_id" in the body.

Send it

POST/v1/decisionsdecisions:write
Make a decisionsh
curl -sS -i -X POST "$GE_BASE/v1/decisions" \
  -H "Authorization: Bearer $GE_TOKEN" \
  -H "Content-Type: application/json" \
  --data-binary @decision.json

A new record answers 201. Two headers matter:

Response headers (first call)http
HTTP/1.1 201 Created
content-type: application/json
idempotent-replay: false
cache-control: no-store

Read the record

The body is a DecisionRecord. Start with these fields; the DecisionRecord page covers all of them.

DecisionRecord (abridged; … marks cuts)json
{
  "id": "6f1c…",
  "tenant_id": "org_a",
  "task_id": "task-7f3a",
  "created_at": "2026-10-04T09:12:44.120Z",
  "recommended_option": {"option_id": "lease", "label": "Lease", "score": 0.5756, "feasible": true,
    "reasons": ["Costs ₹8.70 lakh over 24 months, within the ₹10 lakh budget (87% of it).", "…"]},
  "alternatives": [
    {"option_id": "outsource", "score": 0.5408, "feasible": true, "reasons": ["…"]},
    {"option_id": "buy", "score": 0.3074, "feasible": false,
     "reasons": ["Costs ₹45 lakh over 24 months, over the ₹10 lakh budget by ₹35 lakh.", "…"]}
  ],
  "assumptions": ["Horizon of 24 months read from the question (\"2 year\").", "…"],
  "feasibility": {"status": "feasible", "recommended_option_feasible": true,
                  "feasible_option_ids": ["lease", "outsource"],
                  "infeasible_options": {"buy": ["over_budget"]}},
  "authorization": {"status": "not_authorized", "executable": false, "reason": "…"},
  "reversal_conditions": [
    {"kind": "budget_at_least", "threshold": 4500000, "currency": "INR", "switches_to": "buy",
     "condition": "Budget rises to at least ₹45 lakh", "then": "'Buy outright' becomes the recommendation."}
  ],
  "inputs_digest": "…",
  "engine_version": "ge-decisions/1.1.0"
}
  • recommended_option is the best feasible option. Buying is ranked last because it breaks the budget: an infeasible option is ranked below every feasible one, and its score is halved.
  • A score ranks options under the inputs you gave. It is not a probability.
  • reversal_conditions tell you how settled the choice is: how far the budget, the horizon or one option's figures would have to move for the answer to change.
  • Store the id. It is the durable handle for this decision.

Fetch it by id

GET/v1/decisions/{id}decisions:read
Read a record backsh
curl -sS "$GE_BASE/v1/decisions/$DECISION_ID" \
  -H "Authorization: Bearer $GE_TOKEN"

# With a broker token, name the tenant on reads:
#   -H "X-Tenant-Id: org_a"

You get the record exactly as it was issued. An id that does not exist and an id that belongs to another tenant both answer 404, because whether a record exists is itself tenant data.

Retry safely

Networks fail. Send the same request again with the same idempotency_key:

Response headers (same key, same inputs)http
HTTP/1.1 200 OK
content-type: application/json
idempotent-replay: true
cache-control: no-store

The body is the stored record, byte for byte; the engine does not run again. Change the inputs but keep the key, and the service refuses rather than overwrite:

409 Conflict (same key, different inputs)json
{
  "detail": {
    "error": "idempotency_key_conflict",
    "message": "This idempotency key was already used with different inputs.",
    "decision_id": "6f1c…"
  }
}

The key goes in the JSON body as idempotency_key; there is no Idempotency-Key header. Keys belong to your service within your tenant, so another service that picks the same key never collides with you. Use one key per real-world decision, for example your order or task id. Details: idempotency.

When something fails

Errors and what to do
StatusUsually meansDo this
400A broker token did not name the tenantSend tenant_id in the body (POST) or X-Tenant-Id (GET)
401Token missing, malformed, forged, expired or revokedGet a fresh token; check the Bearer prefix
403Token lacks the scope, or names a different tenantAsk for the scope you need; drop or fix tenant_id
404The service is not reachable from where you called, or the record is not in your tenantCheck the base URL and the network route from your invitation; check the id
409Idempotency key reused with different inputsUse a new key, or GET the record named in decision_id
413Body larger than 1 MiBTrim the evidence
422Invalid JSON, unknown field, a value that cannot be read, or a limit exceededRead detail[0].loc: it names the field
429Over your write limitWait for Retry-After seconds, then retry with the same key
503The rate limiter is unavailableRetry after the Retry-After header (5 seconds)

A 422 looks like this. The message never echoes your value back:

422, a typo in a constraint namejson
{
  "detail": [
    {"type": "value_error", "loc": ["body", "constraints", "budgte"], "msg": "…"}
  ]
}

The same in Python

There is no SDK, and none is needed: it is plain HTTP and JSON. This sketch uses the requests package.

make_decision.pypython
import json
import os

import requests  # pip install requests

base = os.environ["GE_BASE"]
headers = {"Authorization": "Bearer " + os.environ["GE_TOKEN"]}

with open("decision.json", encoding="utf-8") as f:
    body = json.load(f)

# The optional review step can take up to about 25 s, so allow time.
r = requests.post(base + "/v1/decisions", json=body, headers=headers, timeout=60)

if r.status_code in (200, 201):
    record = r.json()
    print("recommended:", record["recommended_option"]["option_id"])
    print("replay:", r.headers.get("Idempotent-Replay"))
    for rc in record["reversal_conditions"]:
        print("-", rc["condition"], "->", rc["then"])
elif r.status_code in (429, 503):
    print("retry after", r.headers.get("Retry-After"), "seconds, with the same key")
else:
    print(r.status_code, r.json().get("detail"))

Next: the full API reference, or every field of the record.