Skip to content
TIAPartner

API docs

One decision endpoint in the System One wire format. Call it over plain HTTP, or use typesafe-sdk by changing the base URL.

Endpoint

POST https://staging-j97-api-9tm2.encr.app/v1/systemone

Base URL: https://staging-j97-api-9tm2.encr.app

No key? POST /demo/systemone takes the same body with no auth and no charge, limited to 10 requests per minute per IP and 2,000 input tokens.

Authentication

Authorization: Bearer tia_live_<32 url-safe characters>

Send your API key in the Authorization header. Keys look like tia_live_ followed by 32 characters, are created in your account and shown once. Only a SHA-256 hash is stored; if you lose a key, revoke it and create a new one.

Request body

The body is a JSON object:

fieldRequiredDescription
modeloptionalLeave it out: the API answers with the currently served build. Names starting with "jev" are accepted for typesafe-sdk compatibility. Other values return 400.
staterequiredText or any JSON except null. This is what the model reads.
questionsrequiredObject from question name to question definition, 1 to 64 questions.
{
  "state": {
    "kenh": "zalo",
    "goi_dich_vu": "Pro năm",
    "tin_nhan": "Chào shop, tháng này em bị trừ tiền 2 lần cho gói Pro, 499.000đ mỗi lần, hoá đơn thứ hai vẫn đang treo. Nhờ shop hoàn lại khoản bị trừ trùng trong hôm nay giúp em, mai kế toán bên em chốt sổ rồi."
  },
  "questions": {
    "hoan_tien": {
      "type": "noul",
      "instructions": "Khách có đang yêu cầu trả lại tiền không?",
      "criteria": {
        "true": "đòi hoàn tiền, huỷ giao dịch hoặc bù tiền",
        "false": "không đòi lại tiền"
      }
    },
    "doi_xu_ly": {
      "type": "choice",
      "instructions": "Đội nào nên nhận ticket này?",
      "criteria": {
        "thanh_toan": "hoá đơn, trừ tiền, hoàn tiền, gói cước",
        "ky_thuat": "lỗi, sự cố, tích hợp",
        "tai_khoan": "đăng nhập, quyền truy cập, bảo mật",
        "ban_hang": "mua mới, nâng cấp, báo giá"
      }
    },
    "muc_do_gap": {
      "type": "score",
      "instructions": "Ticket này gấp đến mức nào?",
      "criteria": [
        "để tuần sau được",
        "bình thường, trả lời trong 2 ngày",
        "ảnh hưởng tiền hoặc công việc hôm nay",
        "sự cố diện rộng, rủi ro pháp lý hoặc bảo mật"
      ]
    }
  }
}

Question types

typecriteriaAnswer
noul (or boolean)Optional: {"true": description, "false": description}{"type":"noul","noul": P(yes)}
choiceRequired: {label: description}, 2 to 10 labelschoice (top label), confidence, probabilities per label
scoreRequired: array of descriptions, lowest first, 2 to 10 levelsscore (expected level, a float), confidence (top level probability), legend, probabilities per level

Every question needs instructions: a string stating the question.

Define a decision like a tool call

If you have written a tool for an agent, you already know how to ask TIA. The same three parts: a name, a description, and the options, each with a line of description.

Agent tool (function calling)
{
  "name": "route_ticket",
  "description": "Which team should handle this support ticket?",
  "parameters": {
    "type": "object",
    "properties": {
      "team": {
        "oneOf": [
          { "const": "billing",
            "description": "invoices, double charges, refunds" },
          { "const": "technical",
            "description": "bugs, crashes, login errors" },
          { "const": "sales",
            "description": "pricing questions, upgrades, new contracts" }
        ]
      }
    },
    "required": ["team"]
  }
}
The same decision sent to TIA (POST /v1/systemone)
{
  "state": "I was charged twice for the Pro plan this month, please refund one of the payments.",
  "questions": {
    "route_ticket": {
      "type": "choice",
      "instructions": "Which team should handle this support ticket?",
      "criteria": {
        "billing": "invoices, double charges, refunds",
        "technical": "bugs, crashes, login errors",
        "sales": "pricing questions, upgrades, new contracts"
      }
    }
  }
}
Agent toolTIA decision
namethe question name, a key in questions
descriptioninstructions
enum / oneOf values, each with a descriptioncriteria: {label: description}, 2 to 10 labels (required for choice)
a true/false parametertype "noul" (or "boolean"), optional criteria {"true": ..., "false": ...}
an ordered scale (say 0 to 3)type "score", criteria is an array of descriptions, lowest level first
A saved real answer
"route_ticket": {
  "type": "choice",
  "choice": "billing",
  "confidence": 0.915,
  "probabilities": {
    "billing": 0.915,
    "technical": 0.002,
    "sales": 0.083
  }
}

Saved output of POST /demo/systemone for exactly the request above, Sep 30, 2026, req_f81f2e470af3c572eb1d054d. Rounded to 3 digits; not live output from the currently served build.

Here the model chose the wrong label (ky_thuat), with a highest probability of only 0.48. A threshold of 0.8 would escalate the ticket to a person. This illustrates thresholding, not a validated operating threshold.

A structural guarantee

Every answer is a probability distribution over exactly the options you defined. The model cannot return free text or an option you did not define, so the rate of out-of-schema answers is 0%. The guarantee is about the shape of the answer, not about correctness: the top option can still be wrong.

How much the question and the option descriptions matter

Removing either part raises KL (worse) in all four cells, on both sets. Accuracy drops in three cells; the fourth, the neutral question on typed-decisions (-1.3 points, 95% CI [-3.2; +0.5]), is not settled. Which part matters more depends on the task. When option labels carry no meaning by themselves (true/false, 1 to 5), the option descriptions decide it (-20.4 accuracy points on typed-decisions). When labels are self-explanatory, the question text decides it (-13.9 points on the general set). So define every decision like a tool: a clear name and question, and a description for every option.

How much the question and the option descriptions matter, TIA 1.1 mini
Requesttyped-decisions dev set (930)general dev set (8,875)
AccuracyKL (lower is better)AccuracyKL (lower is better)
Full: the question and a description for every option0.7590.1340.8780.173
Option descriptions removed, labels only-20.4 points [-24.0; -16.8]+0.246 [+0.217; +0.277]-2.6 points [-2.9; -2.2]+0.064 [+0.057; +0.073]
The question replaced by a neutral word-1.3 points [-3.2; +0.5], not settled+0.029 [+0.021; +0.039]-13.9 points [-14.8; -13.0]+0.236 [+0.220; +0.254]

Rows below the first show the change against the full request, with a 95% confidence interval. The model was built to read the question and the option descriptions, so this measures how much it relies on them, not a rule for every model. Always describe your options.

Source: measured by TIA on Sep 30, 2026, TIA 1.1 mini (the model behind the System One API at the time, before TIA 1.2 mini), internal report on option descriptions; paired bootstrap, 2,000 resamples, 95% confidence intervals. typed-decisions dev set: 930 decisions, the same "dev" set as on the Benchmark page. general dev set: 8,875 decisions, a broader mix of decision tasks, easier than typed-decisions.

Response

A 200 response has this shape (TypeScript types):

type Response = {
  id: string;                    // "req_…", quote it for support
  object: "systemone";
  model: string;
  answers: Record<string,        // one entry per question name
    | { type: "noul"; noul: number }                      // P(yes), 0..1
    | { type: "choice"; choice: string; confidence: number;
        probabilities: Record<string, number> }           // sums to 1
    | { type: "score"; score: number; confidence: number; // score = expected level
        legend: Record<string, string>;                   // "0" → lowest description
        probabilities: Record<string, number> }
  >;
  cached: boolean;               // served from a stored result: cached price
  usage: { input_tokens: number; output_tokens: 0;
           cached_input_tokens: number; // input_tokens if cached, else 0
           free_tokens: number;         // free quota used (1/10 per cached token)
           cost_micro_vnd: number; cost_display: string };
  balance_display: string;       // e.g. "12.345 đ"; absent on /demo/systemone
};

Errors

Every error looks like {"code": ..., "message": ..., "details": ...}. message is for humans, code is for branching. Model 400s carry details.type = input_too_long or invalid_request.

HTTPcodeWhenWhat to do
400invalid_argumentBad body: missing state, wrong type, labels outside 2 to 10, over 64 questions, unknown model (details.type = invalid_request).Fix the request per message.
400invalid_argumentTotal input tokens over the limit (details.type = input_too_long).Shorten the state or split the request.
401unauthenticatedMissing, wrong or revoked key.Check the Authorization header.
402payment_requiredNo free quota left this month and no balance. Checked before calling the model.Top up and resend.
429rate_limitedOver one of your account tier limits (minute, hour, day, concurrent requests) or the demo per-IP limit. The body names which.Wait the seconds in Retry-After.
503unavailableThe model is temporarily unavailable. details.reason = "warming": the model is starting after idle (usually 1 to 4 minutes).Retry after Retry-After. Not charged.

Billing and limits

  • Every month (Vietnam time) includes 100,000,000 free input tokens, spent first; after that 500 đ per 1M input tokens from your wallet. Output is free. Prices apply from 1 October 2026.
  • A repeated request answered from a stored result ("cached": true; same account, same JSON content, same model revision) costs 50 đ per 1M tokens and uses 1/10 of the free tokens; usage.cached_input_tokens counts the tokens billed at that price.
  • Input tokens are the state, instructions and criteria descriptions. usage.input_tokens and usage.cost_display in the response belong to that request.
  • Request limits apply per account (all its keys share them) and rise with the tier. The tier is your lifetime total of completed top-ups.
  • Every response carries an id (req_…). Quote it when asking for support.
TierLifetime top-upRequests/min (burst)Requests/hourRequests/dayConcurrentInput tokens/request
Free0 đ30 (10)1,00010,00024,096
Tier 1≥ 20,000 đ300 (60)15,000300,000164,096
Tier 2≥ 500,000 đ1,200 (200)60,0001,200,000644,096
Tier 3≥ 5,000,000 đ3,000 (500)150,0003,000,0001284,096

The Free tier is also capped per IP: every free account on one IP shares one daily quota (window ip_day).

Every tier has the same input limit: 4,096 tokens per request, which is the model’s own hard limit. GET /v1/models reports it as max_input_tokens. Over the limit, the response is 400 with details.type input_too_long.

A 429 response carries these headers (use them to pace your calls):

headerDescription
X-RateLimit-LimitRequests per minute allowed on your tier.
X-RateLimit-RemainingRequests left in this minute.
X-RateLimit-ResetSeconds until the minute quota refills.
X-RateLimit-Limit-DayRequests per day allowed (Vietnam time).
X-RateLimit-Remaining-DayRequests left today.
Retry-AfterOnly on 429 and 503: seconds to wait.

A 429 body names the window that was hit (minute, hour, day, concurrency, ip_day):

{
  "code": "rate_limited",
  "message": "rate limit exceeded",
  "window": "minute",
  "limit": 30,
  "retry_after_seconds": 2,
  "tier": "free",
  "upgrade_url": "https://tia.8syncdev.com/pricing"
}

Other endpoints

endpointDescription
GET /v1/modelsServed model and limits (max_input_tokens, max_questions, option counts).
GET /v1/requests/{id}Key required. One request of the key’s account; kept 7 days.
GET /statusNo key. How many model servers are ready, the model and its revision; waking: true while the model is starting after idle (poll again shortly).
POST /demo/systemoneNo key. Same body, per-IP limit, not charged.

Examples

The examples read the base URL and key from environment variables; never put a key in code.

export TIA_API_URL="https://staging-j97-api-9tm2.encr.app"
export TIA_API_KEY="tia_live_..."

curl -sS "$TIA_API_URL/v1/systemone" \
  -H "Authorization: Bearer $TIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"state":{"kenh":"zalo","goi_dich_vu":"Pro năm","tin_nhan":"Chào shop, tháng này em bị trừ tiền 2 lần cho gói Pro, 499.000đ mỗi lần, hoá đơn thứ hai vẫn đang treo. Nhờ shop hoàn lại khoản bị trừ trùng trong hôm nay giúp em, mai kế toán bên em chốt sổ rồi."},"questions":{"hoan_tien":{"type":"noul","instructions":"Khách có đang yêu cầu trả lại tiền không?","criteria":{"true":"đòi hoàn tiền, huỷ giao dịch hoặc bù tiền","false":"không đòi lại tiền"}},"doi_xu_ly":{"type":"choice","instructions":"Đội nào nên nhận ticket này?","criteria":{"thanh_toan":"hoá đơn, trừ tiền, hoàn tiền, gói cước","ky_thuat":"lỗi, sự cố, tích hợp","tai_khoan":"đăng nhập, quyền truy cập, bảo mật","ban_hang":"mua mới, nâng cấp, báo giá"}},"muc_do_gap":{"type":"score","instructions":"Ticket này gấp đến mức nào?","criteria":["để tuần sau được","bình thường, trả lời trong 2 ngày","ảnh hưởng tiền hoặc công việc hôm nay","sự cố diện rộng, rủi ro pháp lý hoặc bảo mật"]}}}'

typesafe-sdk

The example below reads TIA_API_URL and TIA_API_KEY, then passes them through the TYPESAFE_BASE_URL and TYPESAFE_API_KEY configuration variables used by @typesafe-ai/sdk. It sets no model, so the API answers with the currently served build, not experimental candidate TIA 1.5.

TYPESAFE_BASE_URL=https://staging-j97-api-9tm2.encr.app
TYPESAFE_API_KEY=tia_live_...