One request. Every decision about a state.
Products are built from small decisions: which team owns a ticket, whether a payment looks like fraud, whether a loan application passes policy, which button an agent should press, what a receipt says. THX-02 makes these decisions and tells you how sure it is, so you can act automatically on confident answers and send the rest to a person.
The options are part of the request. A new category, a new check or a new routing target needs no training: you change the question. THX-02 only scores the options you give it, so it cannot answer with something that is not on your list.
| Property | Value |
|---|---|
| Model id | thx-02 (send it as "model"; without it the request goes to THX-01) |
| Base URL | https://api.hal-x.ai |
| Endpoint | POST /v1/decide, alias POST /v1/systemone; batches at /v1/decide/batch |
| Access | Free without a key (60 decisions per minute per IP), or with an API key for production traffic |
| Question types | choice, noul (yes / no), score (ordered scale) |
| Inputs | Text or JSON state, plus up to 4 images (photos, screenshots, charts, documents) |
| Thinking | "think": false | "auto" | true; thoughts can be returned with "return_thoughts": true |
| Latency | 50–150 ms direct; median about 80 ms with "auto"; 0.5–2 s with true (plus network) |
| Languages | Multilingual: Azerbaijani, Russian, English, Turkish, Kazakh, Uzbek, Chinese, Spanish and more |
Your first decision.
- Get an API key from your HAL-X contact or the HAL-X console. The key needs the THX decisions endpoint with model
thx-02. - Send a state and the questions you want answered, with
"model": "thx-02". - Read
answers: every question gets its choice and the full probability distribution.
curl https://api.hal-x.ai/v1/decide \ -H "Authorization: Bearer $HALX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "thx-02", "state": "Salam, kartımdan iki dəfə pul çıxılıb, geri qaytarın zəhmət olmasa.", "questions": { "team": { "type": "choice", "instructions": "Which team should handle this?", "criteria": { "billing": "Charges and refunds", "support": "Technical problems", "sales": "Buying a product" } }, "refund": { "type": "noul", "instructions": "Does the customer ask for a refund?" }, "anger": { "type": "score", "instructions": "How frustrated is the customer?", "criteria": ["calm", "annoyed", "angry"] } } }'
import os, requests r = requests.post( "https://api.hal-x.ai/v1/decide", headers={"Authorization": f"Bearer {os.environ['HALX_API_KEY']}"}, json={ "model": "thx-02", "state": "Salam, kartımdan iki dəfə pul çıxılıb, geri qaytarın zəhmət olmasa.", "questions": { "team": {"type": "choice", "instructions": "Which team should handle this?", "criteria": {"billing": "Charges and refunds", "support": "Technical problems", "sales": "Buying a product"}}, "refund": {"type": "noul", "instructions": "Does the customer ask for a refund?"}, "anger": {"type": "score", "instructions": "How frustrated is the customer?", "criteria": ["calm", "annoyed", "angry"]}, }, }, timeout=30, ) r.raise_for_status() a = r.json()["answers"] print(a["team"]["choice"], a["refund"]["noul"])
const res = await fetch("https://api.hal-x.ai/v1/decide", { method: "POST", headers: { Authorization: `Bearer ${process.env.HALX_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ model: "thx-02", state: "Salam, kartımdan iki dəfə pul çıxılıb, geri qaytarın zəhmət olmasa.", questions: { team: { type: "choice", instructions: "Which team should handle this?", criteria: { billing: "Charges and refunds", support: "Technical problems", sales: "Buying a product" } }, refund: { type: "noul", instructions: "Does the customer ask for a refund?" }, anger: { type: "score", instructions: "How frustrated is the customer?", criteria: ["calm", "annoyed", "angry"] }, }, }), signal: AbortSignal.timeout(30000), }); const { answers } = await res.json(); console.log(answers.team.choice, answers.refund.noul);
Response:
{
"model": "THX-02",
"answers": {
"team": { "type": "choice", "choice": "billing", "confidence": 0.968,
"probabilities": { "billing": 0.979, "support": 0.017, "sales": 0.004 } },
"refund": { "type": "noul", "noul": 0.957 },
"anger": { "type": "score", "score": 0.50, "confidence": 0.255,
"probabilities": { "0": 0.574, "1": 0.355, "2": 0.071 },
"legend": { "0": "calm", "1": "annoyed", "2": "angry" } }
},
"usage": { "input_tokens": 402, "output_tokens": 0, "thoughts": 0 },
"latency_ms": 118.4
}Free to try. A key for production.
Send your key in the Authorization header as a bearer token, or in X-API-Key. Keys start with sk-. Keep them on your server; never ship them in a browser or mobile app.
Authorization: Bearer sk-... Content-Type: application/json
A key grants endpoints and models separately. For THX-02 it needs the THX decisions endpoint (/v1/systemone, which covers all four decision paths) with the model thx-02 enabled. Keys can also carry a concurrency limit; requests beyond it are rejected with 429.
429 with Retry-After; every response carries X-RateLimit-Remaining-Decisions. Use a key for production traffic and higher volume.Four paths, one grant.
| Method | Path | Use |
|---|---|---|
| POST | /v1/decide | One state and its questions |
| POST | /v1/decide/batch | Up to 1,024 states in one call: {"model": "thx-02", "items": [...]} |
| POST | /v1/systemone, /v1/systemone/batch | The same endpoints under their TypeSafe-compatible names |
| GET | /v1/models | Models your key can use |
The model is chosen by the body's "model", not by the path. Every path works with both THX-01 and THX-02.
A state, and questions about it.
| Field | Type | Description | |
|---|---|---|---|
model | required | string | "thx-02". Without it the request goes to THX-01 |
state | required | string or object | What the decision is about: text, or a JSON record (ticket, transaction, form, agent trace). Use "" when the images are the whole state |
questions | required | object | 1 to 64 named questions. The names are yours and come back as keys of answers |
images | optional | string[] | Up to 4 images as data URIs (data:image/png;base64,...) or raw base64. See Images |
think | optional | false, "auto", true | Default false. See Thinking |
return_thoughts | optional | boolean | Include the thought text in each answer that used one |
tta | optional | 1 or 4 | Read choice options in several orders and average. Default 1. See Option-order averaging |
Each question is an object:
| Field | Description | |
|---|---|---|
type | required | "choice", "noul" or "score" |
instructions | required | The question, in plain language. "question" is accepted as an alias |
criteria | choice, score | For choice: {"key": "what this option means", ...}, 2 to 255 options. For score: an ordered list, lowest first |
Three shapes cover almost every decision.
choice
One option out of a list you define: routing, classification, next action, the right value on a chart.
noul
The probability that a statement is true: policy checks, fraud flags, "does this contain personal data".
score
A level on a scale: urgency, sentiment, risk, quality. Returns the expected level and the distribution.
choice
Give every option a key and a short description. The description is what the model reads, so write it as a definition, not a label.
// question "next_step": { "type": "choice", "instructions": "What should the agent do next?", "criteria": { "refund": "Issue a refund for the duplicate charge", "ask": "Ask the customer for the order number", "escalate": "Hand over to a human agent" } } // answer "next_step": { "type": "choice", "choice": "ask", "confidence": 0.625, "probabilities": { "refund": 0.22, "ask": 0.75, "escalate": 0.03 } }
confidence says how far the winning option is above a uniform guess: 0 when every option is equally likely, 1 when one option takes everything. For thresholds, use the winning option's probability.
noul
A yes / no question. No criteria. The answer is the probability of yes.
"has_pii": { "type": "noul", "instructions": "Does the message contain a card number?" } "has_pii": { "type": "noul", "noul": 0.993 }
score
An ordered scale, lowest level first. The answer has the probability of every level (keys are the level indexes), the legend, score (the expected level, a number between 0 and the last index) and confidence (how tightly the probability sits around the most likely level).
"risk": { "type": "score", "instructions": "How risky is this transaction?", "criteria": ["low", "medium", "high", "block"] } "risk": { "type": "score", "score": 2.31, "confidence": 0.38, "probabilities": { "0": 0.04, "1": 0.11, "2": 0.35, "3": 0.50 }, "legend": { "0": "low", "1": "medium", "2": "high", "3": "block" } }
Think only when it changes the answer.
Most decisions are recognition: the answer is visible in the state. Some need a step of work first, such as adding up debts against a policy limit, reading two values off a chart and subtracting them, or tracing what a piece of code returns. For those, THX-02 can think: it writes a short note with the facts or the calculation that decide the question, then answers again with the note in front of it.
think | Behaviour | Typical latency | Use it for |
|---|---|---|---|
false | Direct answer, no thoughts (default) | 50–150 ms | Routing, tagging, high-volume classification |
"auto" | Thinks only on questions that are unsure and need reasoning | median ~80 ms; 0.3–1 s when it thinks | Production default when some questions involve numbers or logic |
true | Thinks on almost every question, with longer thoughts (up to 320 tokens) | 0.5–2 s | Audits, reviews, cases where a person reads the reasoning |
Add "return_thoughts": true to see the thought on every answer that used one. usage.thoughts counts thoughts and usage.output_tokens their tokens.
curl https://api.hal-x.ai/v1/decide \ -H "Authorization: Bearer $HALX_API_KEY" -H "Content-Type: application/json" \ -d '{ "model": "thx-02", "state": "Applicant income $4,200/month; rent $1,500; car loan $450; cards $250; new loan payment $600. Policy: total debt payments (including rent) must not exceed 50% of income.", "questions": { "policy": { "type": "choice", "instructions": "Does the application pass the bank policy?", "criteria": { "pass": "Within the limit", "fail": "Over the limit" } } }, "think": true, "return_thoughts": true }'
r = requests.post(URL, headers=HEADERS, timeout=60, json={ "model": "thx-02", "state": application_text, "questions": {"policy": {"type": "choice", "instructions": "Does the application pass the bank policy?", "criteria": {"pass": "Within the limit", "fail": "Over the limit"}}}, "think": True, "return_thoughts": True, }) policy = r.json()["answers"]["policy"] print(policy["choice"], policy.get("thought"))
{
"model": "THX-02",
"answers": {
"policy": {
"type": "choice", "choice": "fail", "confidence": 0.509,
"probabilities": { "pass": 0.245, "fail": 0.755 },
"thought": "Total monthly debt payments = $1,500 (rent) + $450 (car) + $250 (cards) + $600 (new loan) = $2,800.
50% of income = $4,200 × 0.50 = $2,100. Since $2,800 > $2,100, the application exceeds the 50% limit."
}
},
"usage": { "input_tokens": 318, "output_tokens": 74, "thoughts": 1 },
"latency_ms": 667.2
}Questions that share one type and one set of criteria (for example ten tags on the same text) are read together in a single pass and are not thought about. Images are supported with thinking.
Photos, screenshots, charts and documents.
Send up to 4 images in images, as data URIs or raw base64 (PNG, JPEG or WebP). They are read together with state; use an empty state when the images say everything. Keep the whole body under 8 MB: resize large photos to about 1,500 px on the long side.
import base64, os, requests def data_uri(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://api.hal-x.ai/v1/decide", timeout=60, headers={"Authorization": f"Bearer {os.environ['HALX_API_KEY']}"}, json={ "model": "thx-02", "state": "", "images": [data_uri("receipt.jpg")], "questions": { "total": {"type": "choice", "instructions": "What is the total amount on the receipt?", "criteria": {"a": "8.85", "b": "8.56", "c": "6.56"}}, "legible": {"type": "noul", "instructions": "Is the receipt fully legible?"}, }, }) print(r.json()["answers"])
import { readFileSync } from "node:fs"; const img = "data:image/jpeg;base64," + readFileSync("receipt.jpg").toString("base64"); const res = await fetch("https://api.hal-x.ai/v1/decide", { method: "POST", headers: { Authorization: `Bearer ${process.env.HALX_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ model: "thx-02", state: "", images: [img], questions: { legible: { type: "noul", instructions: "Is the receipt fully legible?" } }, }), });
Good uses: quality control from a camera, reading receipts and forms, checking screenshots in an agent loop, answering questions about charts and diagrams.
tta: remove position bias.
With "tta": 4, every choice question is read with its options in four different orders and the results are averaged (geometric mean). Accuracy goes up on hard multiple-choice questions, and latency roughly doubles. noul and score questions are read once. Use it for evaluations and high-stakes decisions; keep the default 1 for high-volume traffic.
Answers, usage and timing.
| Field | Description |
|---|---|
model | "THX-02" |
answers | One entry per question name. Every answer has type; choice adds choice, probabilities, confidence; noul adds noul; score adds score, probabilities, legend, confidence. With return_thoughts, answers that used a thought add thought |
usage.input_tokens | Tokens THX-02 read, including option texts and image positions |
usage.output_tokens | Thought tokens written (0 without thinking) |
usage.thoughts | How many questions used a thought |
latency_ms | Time spent in the model, without network |
Every response carries an X-Request-ID header. Quote it when you contact support.
Up to 1,024 states in one call.
Send {"model": "thx-02", "items": [...]} to /v1/decide/batch. Each item is a request body without model (state, questions and the optional fields). Results come back in the same order. A failed item is returned as {"error": "..."} in its place; the other items are unaffected.
{
"model": "thx-02",
"items": [
{ "state": "My package never arrived",
"questions": { "team": { "type": "choice", "instructions": "Which team?",
"criteria": { "billing": "Charges", "shipping": "Deliveries" } } } },
{ "state": "I was charged twice",
"questions": { "team": { "type": "choice", "instructions": "Which team?",
"criteria": { "billing": "Charges", "shipping": "Deliveries" } } } }
]
}
// → { "results": [ { "model": "THX-02", "answers": {...}, "usage": {...} }, { ... } ] }Items are packed together on the GPU, so a batch is much faster than the same requests one by one. For very large jobs, send batches of 100 to 500 items from a few parallel workers.
Clear errors, safe retries.
Errors use one envelope: {"error": {"type": ..., "message": ...}}. The message says what to fix.
| Status | When | What to do |
|---|---|---|
400 | Malformed body: no state, empty questions, unknown type, a choice without two options, more than 64 questions, a body over 8 MB | Fix the request; do not retry |
401 | Invalid or revoked key | Check the key |
403 | The key does not grant THX decisions or the model thx-02 | Ask for the grant |
404 | Unknown model | Use thx-02 |
429 | The key's concurrency limit, or the free tier's per-IP limit, is reached | Retry after Retry-After |
502 / 503 / 504 | The model server is unavailable or timed out | Retry with exponential back-off; see status |
Limits.
| Limit | Value |
|---|---|
| Questions per state | 64 |
Options per choice | 2 to 255 |
| Images per state | 4 |
| Request body | 8 MB |
| Items per batch | 1,024 |
| Request timeout | 120 s |
| Concurrent requests | Set per key |
| Thought length | 96 tokens on "auto", 320 on true |
Getting the most out of THX-02.
- Describe options, don't just name them.
"billing": "Charges, refunds and invoices"beats"billing": "billing". Keep descriptions short and mutually exclusive, ideally in English; the state can be in any language. - Ask everything about a state in one request. Questions about the same state share one read, which is faster and cheaper than separate calls.
- Put options in
criteria, not in the state. The model chooses only among the criteria. - Use the probabilities. They are calibrated: act automatically above a threshold (for example 0.9) and send the rest to a person. Choose the threshold on a few hundred of your own labelled examples.
- Add an escape option such as
"other": "None of the above"when real inputs may fit nothing on the list. - Use
"think": "auto"when questions involve arithmetic, dates, thresholds or multi-step rules; keepfalsefor pure routing and tagging. - Send structured data as JSON. A JSON object in
statekeeps field names and values unambiguous. - Batch offline work. Back-fills, audits and evaluations should use
/v1/decide/batch.
A small client with retries.
import os, time, requests URL = "https://api.hal-x.ai/v1/decide" HEADERS = {"Authorization": f"Bearer {os.environ['HALX_API_KEY']}"} def decide(state, questions, think=False, images=None, retries=4): body = {"model": "thx-02", "state": state, "questions": questions, "think": think} if images: body["images"] = images for attempt in range(retries): r = requests.post(URL, headers=HEADERS, json=body, timeout=60) if r.status_code in (429, 502, 503, 504): time.sleep(0.5 * 2 ** attempt) continue r.raise_for_status() return r.json()["answers"] r.raise_for_status() a = decide("Is it possible to pay my bill in installments?", {"intent": {"type": "choice", "instructions": "What does the customer want?", "criteria": {"plan": "A payment plan or installments", "refund": "Money back", "other": "Anything else"}}}, think="auto") if a["intent"]["probabilities"][a["intent"]["choice"]] >= 0.9: route(a["intent"]["choice"]) else: send_to_human()
const URL = "https://api.hal-x.ai/v1/decide"; export async function decide(state, questions, { think = false, images } = {}) { const body = { model: "thx-02", state, questions, think, ...(images ? { images } : {}) }; for (let attempt = 0; attempt < 4; attempt++) { const res = await fetch(URL, { method: "POST", headers: { Authorization: `Bearer ${process.env.HALX_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify(body), signal: AbortSignal.timeout(60000), }); if ([429, 502, 503, 504].includes(res.status)) { await new Promise((r) => setTimeout(r, 500 * 2 ** attempt)); continue; } if (!res.ok) throw new Error(`THX-02 ${res.status}: ${await res.text()}`); return (await res.json()).answers; } throw new Error("THX-02 unavailable"); }
Moving from THX-01.
THX-02 uses the same endpoints and the same request and answer shapes. To switch, set "model": "thx-02" and make sure your key grants it. Differences:
| THX-01 | THX-02 | |
|---|---|---|
| Access | Key, or the free tier | Key, or the free tier |
| Question types | choice, noul, score, number, excerpt, cite | choice, noul, score |
| Images | No | Up to 4 per state |
| Thinking | No | think, return_thoughts |
| Question text field | question | instructions (question also accepted) |
| Latency | ~10 ms | 50–150 ms direct |
| Best at | Very high-volume text routing | Harder decisions: knowledge, reasoning, documents, vision, agents |
Keep number, excerpt and cite questions on THX-01; THX-02 rejects them with 400.
Status and support.
Live uptime, response time and incidents for THX-01 and THX-02 are on api.hal-x.ai/status/thx. For keys, higher limits or help, contact your HAL-X representative and include the X-Request-ID of the request in question.
