TOD answers typed decisions over a state and returns a calibrated probability for every option. The API follows the Jev /v1/systemone contract: several questions per request, each a choice (2 to 512 labels), a noul (yes/no) or a score (ordinal levels), optionally over images. Nothing is generated, so you pay for input tokens only.
Quickstart
curl https://tod.parseclab.ai/v1/systemone \
-H "Authorization: Bearer $TOD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"state": "My card was stolen yesterday and I need it cancelled now.",
"questions": {
"intent": {"type": "choice", "instructions": "Which intent is this?",
"criteria": {"card_lost_or_stolen": "Card lost or stolen",
"cancel_subscription": "Cancel a subscription",
"card_arrival": "Waiting for a new card"}},
"urgent": {"type": "noul", "criteria": {"true": "Needs action today", "false": "Can wait"}},
"severity": {"type": "score", "criteria": ["none", "minor", "major"]}
}
}'import os, requests
r = requests.post(
"https://tod.parseclab.ai/v1/systemone",
headers={"Authorization": f"Bearer {os.environ['TOD_API_KEY']}"},
json={
"state": "...",
"questions": {
"action": {"type": "choice",
"criteria": {"refund": "Give the money back", "replace": "Ship a new unit"}},
},
},
timeout=60,
)
r.raise_for_status()
a = r.json()["answers"]["action"]
print(a["choice"], a["probabilities"], a["confidence"])Request: POST /v1/systemone
state: a string, a JSON object, or a list of{text}/{image}segments.questions: an object of question id →{type, instructions, criteria}.choice: criteria is label → description; returnschoice.noul: criteria is{true, false}; returnsnoul= P(yes).score: criteria is a list of level descriptions; returnsscore(the most likely level) and alegend.
images: optional, up to 8, appended after the state (see Images).model: optional; echoed back.
Context is up to 49,152 tokens; a longer state is refused with state_too_long.
Images
The model is multimodal. Images travel inline in the JSON body, in either form:
- a data URL:
{"image": "data:image/png;base64,…"} - raw base64:
{"image_b64": "…"}
Put image segments anywhere in a state list to interleave them with text, or pass them in images to append them after the state. PNG, JPEG, WebP and GIF are accepted; the type is read from the bytes, not the data URL. Each image may be up to 5 MB decoded, and a request may carry up to 8. File paths and http(s) URLs are not fetched. Images count toward input tokens like text.
import base64, os, requests
def data_url(path):
mime = "image/png" if path.endswith(".png") else "image/jpeg"
return f"data:{mime};base64," + base64.b64encode(open(path, "rb").read()).decode()
r = requests.post(
"https://tod.parseclab.ai/v1/systemone",
headers={"Authorization": f"Bearer {os.environ['TOD_API_KEY']}"},
json={
"state": [
{"text": "Customer says the parcel arrived like this:"},
{"image": data_url("parcel.jpg")},
],
"questions": {
"damaged": {"type": "noul", "criteria": {"true": "Visibly damaged", "false": "Intact"}},
},
},
timeout=60,
)
print(r.json()["answers"]["damaged"]["noul"])A malformed, oversized, or unsupported image is refused with 422 bad_image and not billed.
Request: POST /v1/pick
A simpler single-choice form: state, 2 or more options (a list of labels, or label → description), an optional question, and optional images in the same forms as above. Returns choice and probs.
curl https://tod.parseclab.ai/v1/pick \
-H "Authorization: Bearer $TOD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"state": "Which screenshot shows the checkout error?",
"options": ["first", "second"],
"images": [{"image": "data:image/png;base64,iVBORw0…"}, {"image": "data:image/png;base64,iVBORw0…"}]
}'Response
{
"id": "req_…",
"model": "utod-picker-12b-v1",
"answers": {
"intent": {"type": "choice", "choice": "card_lost_or_stolen",
"probabilities": {"card_lost_or_stolen": 0.94, "cancel_subscription": 0.05, "card_arrival": 0.01},
"confidence": 0.91, "confidence_kind": "policy_max",
"policy_max_confidence": 0.91, "entropy_confidence": 0.71, "latency_ms": 212.0},
"urgent": {"type": "noul", "noul": 0.88, "probabilities": {"yes": 0.88, "no": 0.12}, …},
"severity": {"type": "score", "score": 2.0, "probabilities": {"0": 0.03, "1": 0.31, "2": 0.66},
"legend": {"0": "none", "1": "minor", "2": "major"}, …}
},
"usage": {"input_tokens": 1236, "output_tokens": 0, "cost_usd": 0.000124},
"latency_ms": 655.2
}Probabilities sum to 1 across each question's options. confidence is the normalised max, (K·pmax− 1)/(K − 1). Every answered question counts as one decision against the account's free allowance. Usage is billed once per request, on total input tokens.
Errors
{"error": {"code": "bad_labels", "message": "question 'q': choice needs a criteria dict with >=2 labels"}}401 unauthorized: missing, unknown or revoked key.402 insufficient_credit: the account's free decisions are used up.413 state_too_long: the state exceeds the context window.422:no_questions,bad_question,bad_type,bad_labels,missing_state,bad_image,bad_request.503 overloaded: at capacity; retry after theRetry-Afterheader.502 backend_unavailable: the model backend is down.
Failed requests are never charged.