# Python

> Call a server from Python with the standard library, or with TypeSafe's SDK.

Source: https://opendecisio.com/docs/use-from/python/

A server is a JSON API, so Python needs nothing but its standard library. TypeSafe’s SDK, which speaks the same wire format, works too once it is pointed at the server.

 

[Figure: One POST out, one JSON answer back; the rest is reading it. A Python script posts the recorded support ticket to the server and prints the likeliest option of each question: urgent 0.16; queue billing 0.96; impact 1 0.65.]

 

## TL;DR

 

With a server running ([Get started](https://opendecisio.com/docs/) starts one), this script sends the recorded support ticket and prints each question’s likeliest option:

 

```
import json
import urllib.request

body = {
    "state": "I was charged twice for October. Both charges are 49.00 EUR, same day. Please refund one.",
    "questions": {
        "urgent": {
            "type": "noul",
            "instructions": "Does this ticket need a response within the hour?"
        },
        "queue": {
            "type": "choice",
            "instructions": "Which team should handle this ticket?",
            "criteria": {
                "billing": "Invoices, payments, refunds, prices, VAT",
                "access": "Login, passwords, two-factor, SSO, permissions, invitations, account security",
                "bug": "The product behaves wrongly: errors, crashes, wrong numbers, lost data",
                "feature": "A request for something the product does not do yet",
                "cancellation": "Cancelling, pausing or closing an account, deleting data",
                "other": "Press, partners, jobs, sales pitches, wrong address, thanks, general questions"
            }
        },
        "impact": {
            "type": "score",
            "instructions": "Rate the business impact using only the reported facts.",
            "criteria": [
                "No function impaired",
                "One user impaired, with a workaround",
                "Many users blocked from a core function",
                "Data loss or legal exposure"
            ]
        }
    }
}

request = urllib.request.Request(
    "http://127.0.0.1:8000/v1/systemone",
    data=json.dumps(body).encode(),
    headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(request) as response:
    answers = json.load(response)["answers"]

for name, answer in answers.items():
    if answer["type"] == "noul":
        print(name, round(answer["noul"], 3))
    else:
        top = max(answer["probabilities"], key=answer["probabilities"].get)
        print(name, top, round(answer["probabilities"][top], 3))
# urgent 0.159
# queue billing 0.962
# impact 1 0.648
```

 

Run against the CPU stand-in at v0.11.1 on 2026-10-14: the answers are the stand-in's, not the measured system's.

 

The comment lines show what the measured system answered (recorded; the stand-in prints other numbers).

 

Recorded decision: Gemma 4 31B, decisio 0.9.0, RTX PRO 6000 at 600 W, 2026-10-08; record 1 of 60 of decisio-examples' support-routing run; the printed probabilities are rounded to three places.

  

| Word | Meaning |
| --- | --- |
| `noul` | a yes/no question; its answer is the probability of yes |
| `probabilities` | a probability for each option of a choice, or each level of a score; they add up to 1 |
| the SDK | TypeSafe’s Python SDK, published under the MIT licence; Decisio does not include it |

 

## 1. With the standard library

 

The script posts the body with `urllib.request` and reads `answers`, which holds one answer per question under the name you gave it. A choice’s answer names its likeliest option (`choice`) and gives every option’s probability; read the probabilities, not only the name, to see how sure it is.

 

## 2. With TypeSafe’s SDK

 

The SDK is TypeSafe’s and installs on its own (`pip install typesafe-sdk`); the block below was run with version 0.7.2, against a decisio server. Give it the server’s address, a key (it requires one; the server ignores it) and the served model’s name:

 

```
from typesafe_sdk import TypeSafeClient

client = TypeSafeClient(
    base_url="http://127.0.0.1:8000",
    # the SDK requires one; the server ignores it
    api_key="unused",
    # the name GET /v1/models lists
    model="decisio-gemma-4-31b-it-letters",
)
body = {
    "state": "I was charged twice for October. Both charges are 49.00 EUR, same day. Please refund one.",
    "questions": {
        "urgent": {
            "type": "noul",
            "instructions": "Does this ticket need a response within the hour?"
        },
        "queue": {
            "type": "choice",
            "instructions": "Which team should handle this ticket?",
            "criteria": {
                "billing": "Invoices, payments, refunds, prices, VAT",
                "access": "Login, passwords, two-factor, SSO, permissions, invitations, account security",
                "bug": "The product behaves wrongly: errors, crashes, wrong numbers, lost data",
                "feature": "A request for something the product does not do yet",
                "cancellation": "Cancelling, pausing or closing an account, deleting data",
                "other": "Press, partners, jobs, sales pitches, wrong address, thanks, general questions"
            }
        },
        "impact": {
            "type": "score",
            "instructions": "Rate the business impact using only the reported facts.",
            "criteria": [
                "No function impaired",
                "One user impaired, with a workaround",
                "Many users blocked from a core function",
                "Data loss or legal exposure"
            ]
        }
    }
}

response = client.system_one(state=body["state"], questions=body["questions"])

for name, answer in response.nouls.items():
    print(name, round(answer.noul, 3))
for name, answer in response.choices.items():
    print(name, answer.choice, round(answer.probabilities[answer.choice], 3))
for name, answer in response.scores.items():
    top = max(answer.probabilities, key=answer.probabilities.get)
    print(name, top, round(answer.probabilities[top], 3))
# The server's name for the model, and the tokens it counted:
print(response.model, response.usage.input_tokens)
```

 

Run against the CPU stand-in at v0.11.1 on 2026-10-14: with TypeSafe's Python SDK 0.7.2, the version the page names. Its comments are shown above their commands.

 

Common mistake

The SDK’s default timeout is 10 s. The first request after a start warms the engine, and a long state seen for the first time takes longer on the larger bases, so raise `timeout` for those.

 

## 3. When the server says no

 

A request that fails validation gets 422 and one the engine cannot answer gets 400, each with a `detail` naming the problem. With the standard library these are an `HTTPError` whose body holds the detail; the SDK raises its own types (`TypeSafeUnprocessableEntityError`, `TypeSafeBadRequestError`). A server sets no rate limit, so the SDK’s retries on 429 never fire; it still retries 408 and 5xx.

 

```
import json
import urllib.error
import urllib.request

request = urllib.request.Request(
    "http://127.0.0.1:8000/v1/systemone",
    data=json.dumps({"state": "No questions asked.", "questions": {}}).encode(),
    headers={"Content-Type": "application/json"},
)
try:
    urllib.request.urlopen(request)
except urllib.error.HTTPError as error:
    print(error.code, "detail" in json.load(error))
# 422 True
```

 

Run against the CPU stand-in at v0.11.1 on 2026-10-14.

 

## What next

 

- [JavaScript and TypeScript](https://opendecisio.com/docs/use-from/javascript/): the same call from Node.
- [System One clients](https://opendecisio.com/docs/migrating/): point a client you already have at the server.
- [The HTTP API](https://opendecisio.com/docs/api/): every route, request field and response field.
