---
title: "Quickstart: first call in HTTP, Python, JS"
type: guide
tags: [quickstart, api-key, curl, python, javascript]
created: 2026-09-17
updated: 2026-09-21
confidence: high
sources:
  - raw/docs/introduction__quickstart.md
  - raw/docs/introduction.md
  - raw/github/typesafe-sdk-python/README.md
  - raw/github/typesafe-sdk-js/README.md
  - raw/docs/api.md
jev_version: "jev-1.13.0"
sdk_python: "0.7.1"
sdk_js: "0.6.0"
summary: "Get a key at console.typesafe.ai, export TYPESAFE_API_KEY, and make your first POST /v1/systemone call in curl, Python, or TypeScript."
---

# Quickstart: first call in HTTP, Python, JS

> **TL;DR** Get a key at https://console.typesafe.ai/keys, `export TYPESAFE_API_KEY=...`, then `POST https://api.typesafe.ai/v1/systemone` with `state`, `model` (`"jev-latest"`), and a `questions` map. Answers come back under the same keys you chose. Both SDKs read `TYPESAFE_API_KEY` from the environment and default to `jev-latest`.

## Step 1 — get an API key

1. Open the console at https://console.typesafe.ai and log in.
2. Create a key.

The quickstart page points at the **dashboard**, https://console.typesafe.ai/keys — the same path the agent-skill page uses. **Resolved 2026-09-21:** until this refresh the quickstart linked `https://console.typesafe.ai/settings/keys` and this wiki flagged the two pages as disagreeing. They now agree on `/keys`. See [[entities/typesafe-console]].

## Step 2 — set `TYPESAFE_API_KEY`

```bash
export TYPESAFE_API_KEY="sk-..."   # your key from the console
```

Both SDK READMEs say the same thing: "Set `TYPESAFE_API_KEY` in your environment, then instantiate and use the client." For the HTTP API the key goes in the `Authorization` header instead. See [[reference/environment-variables]] for every `TYPESAFE_*` variable.

## Step 3 (optional) — try it in the Playground first

1. **Open the [Playground](https://console.typesafe.ai/playground)** and log in.
2. **Paste any text** as the state:

```plaintext title="Sample state"
Hi, I've been trying to connect my Stripe account for 3 days and the integration keeps failing. I'm losing sales. Please help ASAP.
```

3. **Add a question.** The docs suggest a Noul: `"Does this message express urgency?"`

```json
{
  "urgency": {
    "type": "noul",
    "instructions": "Does this message express urgency?"
  }
}
```

4. **Add more questions.** "Mix Noul, Choice, and Score in one call and see all results at once."

## Step 4 — the first call

The endpoint:

```http
POST https://api.typesafe.ai/v1/systemone
Authorization: Bearer <API_KEY>
Content-Type: application/json
```

Three required body fields: `state` (string, object, or array), `model` (string), `questions` (map of id → question). See [[reference/http-api]] for every field.

### curl

Verbatim from the quickstart, complete and runnable once `TYPESAFE_API_KEY` is exported:

```bash
curl -X POST https://api.typesafe.ai/v1/systemone \
  -H "Authorization: Bearer $TYPESAFE_API_KEY" \
  -H "Content-Type: application/json" \
  -d @- <<'EOF'
  {
    "state": "Hi, I've been trying to connect my Stripe account for 3 days and the integration keeps failing. I'm losing sales. Please help ASAP.",
    "model": "jev-latest",
    "questions": {
      "urgency": {
        "type": "noul",
        "instructions": "Does this message express urgency?"
      }
    }
  }
EOF
```

The fuller request body the quickstart documents — one of each primitive in a single call:

```json
{
  "state": "Hi, I've been trying to connect my Stripe account for 3 days and the integration keeps failing. I'm losing sales. Please help ASAP.",
  "model": "jev-latest",
  "questions": {
    "department": {
      "type": "choice",
      "instructions": "Which team should handle this",
      "criteria": {
        "billing": "Payment or subscription issues",
        "technical": "Bugs or integration problems",
        "sales": "Pricing or account questions"
      }
    },
    "frustration": {
      "type": "score",
      "instructions": "How frustrated the customer appears",
      "criteria": [
        "Calm, just stating facts",
        "Frustrated but civil",
        "Very angry, strong language"
      ]
    },
    "is_urgent": {
      "type": "noul",
      "instructions": "The message conveys urgency or time-sensitivity"
    }
  }
}
```

### Python

Install (requires Python >= 3.10):

```bash
pip install typesafe-sdk
```

```bash
uv add typesafe-sdk
```

> **Use the SDK.** The client reads `TYPESAFE_API_KEY` from the environment and calls `jev-latest` by default.

```python
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient

client = TypeSafeClient()

ticket = "Hi, I've been trying to connect my Stripe account for 3 days and the integration keeps failing. I'm losing sales. Please help ASAP."

response = client.system_one(
    state=ticket,
    questions={
        "department": Choice(
            instructions="Which team should handle this",
            criteria={
                "billing": "Payment or subscription issues",
                "technical": "Bugs or integration problems",
                "sales": "Pricing or account questions",
            },
        ),
        "frustration": Score(
            instructions="How frustrated the customer appears",
            criteria=[
                "Calm, just stating facts",
                "Frustrated but civil",
                "Very angry, strong language",
            ],
        ),
        "is_urgent": Noul(
            instructions="The message conveys urgency or time-sensitivity",
        ),
    },
)

print(response.answers["department"].choice)  # "technical"
print(response.answers["frustration"].score)  # 1.0
print(response.answers["is_urgent"].noul)     # 1.0
```

The repo README shows the same call as a context manager, which closes the underlying HTTP client:

```python
from typesafe_sdk import Choice, TypeSafeClient

with TypeSafeClient() as client:
    response = client.system_one(
        state={"document": "I was charged twice. Please fix this ASAP."},
        questions={
            "category": Choice(
                instructions="What is this ticket about?",
                criteria={"billing": None, "technical": None, "other": None},
            ),
        },
    )

print(response.choices["category"].choice)
```

Two things to notice: `criteria` values may be `null`/`None` for options that need no description, and the README reads the answer through `response.choices[...]` while the docs read `response.answers[...]`. Both exist on `SystemOneResponse` — `answers` is the mixed map, and `choices` / `nouls` / `scores` are per-type views. See [[reference/python-sdk-responses]]. `Score.criteria` is an ordered sequence in the current Python SDK (0.7.1), not an int-keyed dict.

### TypeScript / JavaScript

Install (Node.js 20 or newer):

```bash
npm install @typesafe-ai/sdk
```

Verbatim from the SDK README:

```ts
import { choice, TypeSafeClient } from "@typesafe-ai/sdk";

const client = new TypeSafeClient();
const response = await client.systemOne({
  state: { document: "I was charged twice. Please fix this ASAP." },
  questions: {
    category: choice("What is this ticket about?", {
      billing: null,
      technical: null,
      other: null,
    }),
  },
});

console.log(response.answers.category.choice);
```

> Answer types are inferred from your questions. The package includes ESM, CommonJS, and TypeScript declarations.

The same three-question call as the curl and Python samples, using the documented `choice(instructions, criteria)`, `score(instructions, criteria)`, and `noul(instructions?, criteria?)` helpers (adapted from the README, which shows only `choice`):

```ts
import { choice, noul, score, TypeSafeClient } from "@typesafe-ai/sdk";

const client = new TypeSafeClient();

const ticket =
  "Hi, I've been trying to connect my Stripe account for 3 days and the integration keeps failing. I'm losing sales. Please help ASAP.";

const response = await client.systemOne({
  state: ticket,
  questions: {
    department: choice("Which team should handle this", {
      billing: "Payment or subscription issues",
      technical: "Bugs or integration problems",
      sales: "Pricing or account questions",
    }),
    frustration: score("How frustrated the customer appears", [
      "Calm, just stating facts",
      "Frustrated but civil",
      "Very angry, strong language",
    ]),
    is_urgent: noul("The message conveys urgency or time-sensitivity"),
  },
});

console.log(response.answers.department.choice);
console.log(response.answers.frustration.score);
console.log(response.answers.is_urgent.noul);
```

See [[reference/javascript-sdk]] for client options and [[reference/javascript-sdk-types]] for the inferred answer types.

## Step 5 — read the answer

The response body for the three-question request, verbatim from the quickstart:

```json
{
  "model": "jev-1.13.0",
  "answers": {
    "department": {
      "type": "choice",
      "choice": "technical",
      "confidence": 0.78,
      "probabilities": {
        "technical": 0.85,
        "sales": 0.0,
        "billing": 0.15
      }
    },
    "frustration": {
      "type": "score",
      "score": 1.0,
      "confidence": 1.0,
      "legend": {
        "0": "Calm, just stating facts",
        "1": "Frustrated but civil",
        "2": "Very angry, strong language"
      },
      "probabilities": {
        "0": 0.0,
        "1": 1.0,
        "2": 0.0
      }
    },
    "is_urgent": {
      "type": "noul",
      "noul": 1.0
    }
  },
  "usage": {
    "input_tokens": 392,
    "output_tokens": 65
  }
}
```

What each field means:

| Field | Applies to | Meaning |
|---|---|---|
| `model` | response | "The model that performed the evaluation." |
| `answers.<id>` | response | One answer per question, "keyed by the same ids you used in questions." |
| `type` | every answer | Matches the question's type. |
| `choice` | Choice | "The highest-probability option." |
| `score` | Score | "The probability-weighted answer across the levels; can land between levels." |
| `noul` | Noul | "The yes/no answer on a scale from 0 (no) to 1 (yes)." |
| `probabilities` | Choice, Score | Every option or level mapped to its probability ("floats that sum to 1"). |
| `legend` | Score | "Each level number mapped back to its description." |
| `confidence` | Choice, Score | "How certain the model is, derived from probabilities." Noul has no `confidence`. |
| `usage.input_tokens` / `usage.output_tokens` | response | Token usage for the request. |

Branch on these values in code — do not re-parse prose. See [[concepts/confidence]] before you pick thresholds.

**What changed on 2026-09-21.** The quickstart was re-run and its sample state slightly reworded ("and the integration keeps failing"). Three things moved, and all three matter if you copied the old page:

- The Score answer now **includes `probabilities`**. The old sample omitted it, and this page used to flag that as an inconsistency with raw/docs/api.md. All sources now agree the field is always present.
- `model` in the response reads `jev-1.13.0`, the resolved version, not the `jev-latest` alias that was sent. That is the documented behaviour of the field ([[reference/models-and-pricing]]); only the example changed.
- The answers themselves changed: `department` flipped from `billing` (0.84, confidence 0.596) to `technical` (0.85, confidence 0.78), `frustration` from 1.035 to 1.0, `is_urgent` from 0.999 to 1.0. **Do not treat any documented answer as an expected output** — see [[guides/testing-and-evaluation]].

## Step 6 — errors and retries

| Status | Meaning |
|---|---|
| `401 Unauthorized` | "Missing or invalid API key. Check the `Authorization` header." |
| `422 Unprocessable Entity` | "The request body failed validation — for example a missing required field or a malformed question." |
| `429 Too Many Requests` | "You have exceeded your rate limit. Back off and retry after a short delay." |
| `529 Overloaded` | "TypeSafe is temporarily overloaded. Retry after a short delay." |

> When you receive a `429 Too Many Requests` or `529 Overloaded` response, retry the request with exponential backoff instead of retrying immediately. Our client SDKs handle this automatically, so no extra handling is needed if you use one of our SDKs with its default retry policy.

See [[reference/rate-limits-and-errors]].

## Step 7 (optional) — install the agent skill

> **[Install the TypeSafe skill](/agent-skill#installation)** using the Claude Code plugin or `npx skills add typesafe-ai/skills --skill typesafe-ai`.

```bash
claude plugin marketplace add typesafe-ai/skills
claude plugin install typesafe@typesafe-ai
```

```bash
npx skills add typesafe-ai/skills --skill typesafe-ai
```

Then "tell your coding agent to use the TypeSafe skill as you build." The docs' starter prompt:

```plaintext title="Coding agent prompt"
Let's build a simple CLI that uses the TypeSafe API to evaluate a set of supplied documents on multiple dimensions. Use the TypeSafe skill to understand how to use the TypeSafe API and how to structure the system. Ask me questions about what kinds of documents I want to evaluate and on what dimensions.
```

Full details in [[reference/agent-skill]].

## Checklist

- [ ] Key created at console.typesafe.ai
- [ ] `TYPESAFE_API_KEY` exported (or passed to the client explicitly)
- [ ] One call returning `200` with answers under your own question ids
- [ ] Questions and thresholds kept in one file, not scattered through handlers
- [ ] Model pinned (`jev-1.13.0`) if you have tuned thresholds — `jev-latest` moves

## Next steps

- [[concepts/primitives]] — "How to define questions, choose between Choice, Score, and Noul, and ask several at once"
- [[guides/choosing-a-primitive]] — picking the right question type
- [[concepts/confidence]] — "How TypeSafe reports certainty, and how to use it architecturally"
- [[patterns/overview]] — "Common patterns for building systems with TypeSafe"
- [[concepts/machine-learning-primer]] — "Why TypeSafe trains models for calibrated decisions instead of generated text"
- [[reference/http-api]] — every request and response field
- [[reference/python-sdk]] / [[reference/javascript-sdk]] — installation options and detailed usage
- [[reference/models-and-pricing]] — aliases, price, rate limits, context window
- [[cookbooks/overview]] — worked end-to-end examples
- [[guides/agent-integration-playbook]] — if you are an agent building with Jev

## Related

- [[concepts/system-one]] — what Jev is and why the output is typed
- [[concepts/state]] — what to put in `state`
- [[entities/typesafe-console]] — the console and playground
- [[reference/environment-variables]] — `TYPESAFE_*` variables
- [[guides/smart-home-demo]] — a worked application

## Sources

- raw/docs/introduction__quickstart.md (https://docs.typesafe.ai/introduction/quickstart)
- raw/docs/introduction.md (https://docs.typesafe.ai/introduction)
- raw/github/typesafe-sdk-python/README.md (https://github.com/typesafe-ai/typesafe-sdk-python)
- raw/github/typesafe-sdk-js/README.md (https://github.com/typesafe-ai/typesafe-sdk-js)
- raw/docs/api.md (https://docs.typesafe.ai/api)
