---
title: "Quickstart: first call in HTTP, Python, JS"
type: guide
tags: [quickstart, api-key, curl, python, javascript]
created: 2026-09-17
updated: 2026-09-17
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.6.0"
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/settings/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/settings/keys. The agent-skill page links https://console.typesafe.ai/keys for the same thing — the sources disagree on the exact path; both are under `console.typesafe.ai`. Use https://console.typesafe.ai and navigate to the API keys page; both paths are cited upstream and the console may redirect. 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 it 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 it 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 it 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 it 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)  # "billing"
print(response.answers["frustration"].score)  # 1.035
print(response.answers["is_urgent"].noul)     # 0.999
```

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 v0.6.0, 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 it 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-latest",
  "answers": {
    "department": {
      "type": "choice",
      "choice": "billing",
      "probabilities": {
        "billing": 0.84,
        "technical": 0.159,
        "sales": 0.001
      },
      "confidence": 0.596
    },
    "frustration": {
      "type": "score",
      "score": 1.035,
      "legend": {
        "0": "Calm, just stating facts",
        "1": "Frustrated but civil",
        "2": "Very angry, strong language"
      },
      "confidence": 0.842
    },
    "is_urgent": {
      "type": "noul",
      "noul": 0.999
    }
  },
  "usage": {
    "input_tokens": 312,
    "output_tokens": 48
  }
}
```

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. |

Note that the quickstart's example answers omit `probabilities` on the Score answer even though `raw/docs/api.md` marks it required; `raw/docs/api.md` is the contract. Branch on these values in code — do not re-parse prose. See [[concepts/confidence]] before you pick thresholds.

## 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)
