Jev の判定 API は POST https://api.typesafe.ai/v1/systemone の1本です。state(材料)、model、questions(質問のマップ)を JSON で送ると、質問と同じキーで答えが返ります。SDK を使わなくても、curl や fetch だけで呼べます。
最小の呼び出し
API キーは環境変数に入れます。コードやコマンド履歴に直接書かないでください。
curl https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"state": "Help! My payouts have been failing for 3 days.",
"model": "jev-latest",
"questions": {
"is_urgent": { "type": "noul", "instructions": "Does this convey urgency?" }
}
}'
{
"model": "jev-1.13.0",
"answers": { "is_urgent": { "type": "noul", "noul": 0.95 } },
"usage": { "input_tokens": 283, "output_tokens": 23 }
}
(2026-09-21 に実際に返ってきた値です。usage の数字は公式ドキュメントの例と少し違いました。)
リクエスト
| 項目 | 型 | 説明 |
|---|---|---|
state | 文字列 / オブジェクト / 配列 | 判定の材料。→ state の設計 |
model | 文字列 | jev-latest か、jev-1.13.0 のようなバージョン ID。→ 料金と制限 |
questions | マップ | キーは自分で付ける名前。答えは同じキーで返る。キーはモデルには渡らず、判定に影響しない |
質問の書き方は 3つの質問タイプの書き方 にまとめました。
レスポンス
| 項目 | 説明 |
|---|---|
model | 実際に答えたバージョン ID。エイリアスを指定しても、ここには jev-1.13.0 のような実体が入る。ログに残す |
answers | 質問と同じキーの答え。型ごとの形は下の表 |
usage | input_tokens と output_tokens。課金は入力だけ |
| 型 | 答えの項目 |
|---|---|
noul | noul(0〜1) |
choice | choice、probabilities(合計 1)、confidence |
score | score、legend、probabilities(合計 1)、confidence |
Node.js(fetch)での例
3種類の質問を一度に送る例です。手元で実行し、約 0.7 秒で返りました(1回の計測)。
const res = await fetch('https://api.typesafe.ai/v1/systemone', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TYPESAFE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
state: { message: '助けてください!3日前から入金がずっと失敗しています。', plan: 'business' },
model: 'jev-latest',
questions: {
is_urgent: { type: 'noul', instructions: 'Does `message` convey urgency?' },
department: {
type: 'choice',
instructions: 'Which team should handle `message`?',
criteria: {
billing: 'Payments, invoicing, refunds',
technical: 'Bugs, outages, integrations',
sales: 'Pricing, upgrades, new accounts',
},
},
frustration: {
type: 'score',
instructions: 'How frustrated is the customer?',
criteria: ['Calm', 'Frustrated', 'Very angry'],
},
},
}),
});
if (!res.ok) throw new Error(`Jev API ${res.status}: ${await res.text()}`);
const { model, answers } = await res.json();
if (answers.is_urgent.noul > 0.8 && answers.department.confidence > 0.7) {
console.log(`${answers.department.choice} の優先キューへ(${model})`);
}
エラー
公式の表にあるのは次の4つです。
| ステータス | 意味 | 対処 |
|---|---|---|
401 Unauthorized | API キーが無い・誤っている | Authorization ヘッダを確かめる。再試行しない |
422 Unprocessable Entity | リクエストの形が不正 | 本文に問題の箇所が出る。直して送り直す。再試行しない |
429 Too Many Requests | レート制限の超過 | 間隔を伸ばしながら再試行 |
529 Overloaded | サーバー側の過負荷 | 間隔を伸ばしながら再試行 |
実際に返ってきたエラーの形(2026-09-21 に確認)
401 — 誤ったキーで呼んだ場合:
{ "detail": { "error_type": "authentication_error",
"message": "Cannot authenticate with the server. Please check your API key and try again." } }
422 — Choice に criteria を付け忘れた場合。loc が問題の場所を指します。
{ "detail": [ { "type": "missing",
"loc": ["body", "questions", "q", "choice", "criteria"],
"msg": "Field required" } ] }
400 — 存在しないモデル名を指定した場合。公式のエラー表には無いステータスですが、実際にはこう返りました。
{ "detail": { "error_type": "api_usage_error", "message": "Unknown model: jev-does-not-exist" } }
detail は、401 と 400 ではオブジェクト、422 では配列です。エラー処理で detail.message を決め打ちで読むと 422 で壊れるので、形を見てから読みます。
通ってしまう入力に注意
公式は「Score の段階は少なくとも2つ」としていますが、段階が1つだけの Score を送っても 422 にはならず、score: 0、confidence: 1 が 200 で返りました。 配列を組み立てる処理の不具合で段階が1つになっても、API は止めてくれません。送る前に自分のコードで確かめてください。
再試行の書き方
再試行するのは 429 と 529(と、ネットワークの一時的な失敗)だけです。400 / 401 / 422 は何度送っても同じなので、すぐに失敗させます。
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// 0.5s, 1s, 2s, 4s … に揺らぎを足す
const backoff = (attempt) => 500 * 2 ** attempt + Math.random() * 250;
// Retry-After は「秒数」か「HTTP の日付」のどちらかで返る
function retryAfterMs(value) {
if (!value) return null;
const seconds = Number(value);
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);
const date = Date.parse(value);
return Number.isNaN(date) ? null : Math.max(0, date - Date.now());
}
async function askJev(body, { retries = 4 } = {}) {
for (let attempt = 0; ; attempt++) {
let res;
try {
res = await fetch('https://api.typesafe.ai/v1/systemone', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TYPESAFE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
} catch (err) {
// 接続できない、途中で切れた、などの一時的な失敗
if (attempt >= retries) throw err;
await sleep(backoff(attempt));
continue;
}
if (res.ok) return res.json();
const retryable = res.status === 429 || res.status === 529;
if (!retryable || attempt >= retries) {
throw new Error(`Jev API ${res.status}: ${await res.text()}`);
}
await sleep(retryAfterMs(res.headers.get('retry-after')) ?? backoff(attempt));
}
}
通信の失敗で再試行すると、1回目のリクエストが実はサーバーに届いていた場合、同じ判定を2回行うことになります。Jev の判定は何かを書き換える操作ではないので結果に害はありませんが、入力トークンは2回ぶん数えられる可能性があります。
公式 SDK(Python の typesafe-sdk、JS の @typesafe-ai/sdk)は、この再試行を既定で行い、retry-after ヘッダがあれば従う、と書かれています。自前で書く理由が無ければ SDK を使うほうが簡単です。
このコードで確かめたこと(2026-09-21):
- 実際の API に対して: 正常に答えが返ること、
401では再試行せずすぐに例外になること fetchを差し替えた模擬で: 「通信の失敗 →429(Retry-Afterが秒数)→529(Retry-Afterが日付)→200」の順に進んで答えが返ること、再試行の上限で諦めること、通信の失敗が続いたら元の例外を投げること、Retry-Afterが秒数・日付・過去の日付・壊れた値のそれぞれで期待どおりの待ち時間になること
実際の API に 429 や 529 を起こさせることはしていません。 本物の Retry-After がどちらの形式で返るかは未確認です。
使えるモデルの一覧
curl https://api.typesafe.ai/v1/models -H "Authorization: Bearer $TYPESAFE_API_KEY"
現在はエイリアス(jev-latest、jev-preview)が返ります。バージョン ID は一覧に無くても model に指定できます。
日本語で使うとき
state も instructions も日本語で送れますが、公式は英語が最も精度が高く、日本語を含む CJK は同等ではないと明記しています。選択肢のキー(billing など)はコードで分岐に使うので、英数字にしておくと扱いやすくなります。
よくある質問
エンドポイントはいくつありますか?
判定は POST https://api.typesafe.ai/v1/systemone の1本です。ほかに、使えるモデル名を返す GET https://api.typesafe.ai/v1/models があります。
429 や 529 が返ったらどうすればよいですか?
すぐに再送せず、待ち時間を指数的に伸ばしながら再試行します。429 はレート制限の超過、529 はサーバー側の過負荷です。公式 SDK は既定でこの再試行を行います。REST を直接呼ぶ場合は自分で実装します。
ストリーミングはありますか?
公式の API リファレンスにストリーミングの記述はありません。Jev は文章を生成しないので、レスポンスは質問ごとの答えが入った JSON が1つ返るだけです。
出典・参考リンク
- 一次情報 API reference
- 一次情報 Models