System One
System One
DocumentationAPI referenceDeploySDK on GitHubSystem One
SDK quickstart
Decision primitives
Credits and billingData handlingDeploy on Cloudflare Workers
SDK quickstart

SDK quickstart

Use the project SDK or official TypeSafe client and read platform metadata from headers.

Create an API key

Sign in to the main System One website using the existing account flow. Verify your email, open API keys, and create a named key. Copy the secret when it is shown: the list displays only a prefix, and the full secret cannot be retrieved later. Keep the key on your server.

Verified accounts are eligible for a one-time welcome grant of 100 credits by default. The operator can configure or disable this grant. Check your actual balance in the console; creating additional keys does not grant more credits.

Install the published SDK

This example uses Node.js 22 or later. The SDK itself supports Node.js 20 or later and standard Web API runtimes.

This first example uses the project client @system-one-ai/sdk@0.3.0. The official client example below uses @typesafe-ai/sdk@0.6.0; the two clients have different constructors, question types and result wrappers.

npm install @system-one-ai/sdk@0.3.0

Create a local .env file that is excluded from version control. SYSTEM_ONE_API_KEY is your platform key, not the operator’s upstream provider key.

SYSTEM_ONE_API_KEY=replace-with-your-platform-key
SYSTEM_ONE_BASE_URL=https://your-deployment.example.com/v1

Replace the origin with the main application deployment. The documentation host does not serve inference. A local main application uses http://localhost:7001/v1.

Send your first decision

Save this as decision.mjs. The same factories preserve literal option types in a TypeScript application.

import { SystemOne, choice, score, booleanQuestion } from '@system-one-ai/sdk';

const apiKey = process.env.SYSTEM_ONE_API_KEY;
const baseURL = process.env.SYSTEM_ONE_BASE_URL;
if (!apiKey || !baseURL) throw new Error('Set SYSTEM_ONE_API_KEY and SYSTEM_ONE_BASE_URL.');

const client = new SystemOne({
  apiKey,
  baseURL,
  model: 'jev-latest',
  timeoutMs: 30_000,
  maxRetries: 0,
});

// Keep this value with the operation if its response needs to be recovered.
const idempotencyKey = crypto.randomUUID();
const result = await client.evaluate({
  state: { message: 'I was charged twice for the same order.' },
  questions: {
    team: choice('Choose the team that should review this request.', {
      billing: 'Payments, invoices, and refunds',
      support: 'Technical troubleshooting',
      review: 'More context is needed',
    }),
    urgency: score('Rate the urgency using this ordered rubric.', [
      'Can wait for normal review',
      'Needs a timely response',
      'Needs immediate human attention',
    ]),
    duplicate: booleanQuestion('Does the message report a duplicate charge?'),
  },
}, {
  headers: { 'Idempotency-Key': idempotencyKey },
});

console.log({
  team: result.answers.team.choice,
  distribution: result.answers.team.probabilities,
  urgency: result.answers.urgency.score,
  duplicateProbability: result.answers.duplicate.probability,
  inputTokens: result.usage.inputTokens,
});
node --env-file=.env decision.mjs

This command makes a real, billable request. There is no fixed expected answer or latency. A provider must be configured and your account must have enough credits.

Read the result

team.choice is one of the declared names. urgency.score can be fractional between 0 and 2 for this rubric. duplicate.probability is a number from 0 to 1, not a JavaScript boolean. Native TypeSafe Choice requires probabilities and confidence; Score also requires legend. Missing usage/token counts remain unknown. SDK 0.3.0 can add defaults such as rounding/warnings to its normalized result, so that result is not the exact native body.

On the wire, booleanQuestion() becomes type: "noul" and the response field is noul. The SDK maps this to its public boolean answer and probability. You do not need a custom adapter when connecting to this platform, even if the operator uses OpenRouter upstream.

Provide a model alias or fixed version ID in the client/request. TypeSafe receives an explicit model unchanged; an omitted model uses the gateway default only if the client has not already filled its own default. jev-latest is an example alias, not the only accepted value. Available names are fetched through authenticated GET /v1/models with your platform key, returning the actual upstream { models } body. Version IDs need not appear in that list.

Official TypeSafe client and response headers

The official SDK supports required-but-nullable state, optional/null instructions, and nullable Noul criteria; SDK 0.3.0 rejects some of these forms before sending. Native HTTP and Playground keep them for the provider to accept or reject. This example uses nullable input without rewriting it to an empty string.

npm install @typesafe-ai/sdk@0.6.0

Save as official-decision.mjs and use the same server environment. The official client's base URL is the API root without /v1, because it appends /v1/systemone itself.

import { TypeSafeClient } from '@typesafe-ai/sdk';

const apiKey = process.env.SYSTEM_ONE_API_KEY;
const baseURL = process.env.SYSTEM_ONE_BASE_URL;
if (!apiKey || !baseURL) throw new Error('Set SYSTEM_ONE_API_KEY and SYSTEM_ONE_BASE_URL.');

const client = new TypeSafeClient({
  apiKey,
  baseURL: baseURL.replace(/\/v1\/?$/, ''),
  defaultModel: 'jev-latest',
  timeout: 30_000,
  retry: { maxRetries: 0 },
  logLevel: 'off',
});
const operationKey = crypto.randomUUID(); // retain this key with the operation
const { data, response, requestId: typeSafeRequestId } = await client.systemOne({
  model: 'jev-latest',
  state: null,
  questions: { ready: { type: 'noul', criteria: null } },
}, { headers: { 'Idempotency-Key': operationKey } }).withResponse();

const reportedCredits = response.headers.get('X-System-One-Credits');
const credits = reportedCredits !== null && /^\d+$/.test(reportedCredits)
  && Number.isSafeInteger(Number(reportedCredits)) ? Number(reportedCredits) : undefined;
console.log({
  platformRequestId: response.headers.get('X-Request-Id'),
  upstreamRequestId: response.headers.get('X-Upstream-Request-Id'),
  typeSafeRequestId,
  credits,
  replayed: response.headers.get('X-Idempotency-Replayed') === 'true',
  mode: response.headers.get('X-System-One-Response-Mode'),
  model: data.model,
  inputTokens: data.usage?.input_tokens,
});
node --env-file=.env official-decision.mjs

This is another real, billable evaluation when explicitly run, not an offline fixture or an assertion that every provider accepts null state. withResponse().requestId comes from X-TypeSafe-Request-Id; platform accounting uses X-Request-Id. A missing credit header remains unknown, especially during refund reconciliation. Body fields named billing or request_id belong to the provider and must not override the headers. A successful replay has a zero charge header and no injected body fields.

For exact response text, choose .asResponse() instead of .withResponse() on the same call, then read response.text() yourself. Both reject non-2xx responses. For the model list, the official models.list() returns an unwrapped array, while raw HTTP retains { models }. Never use the operator's upstream key in client code or enable browser key exposure.

Native TypeSafe mode preserves valid success text/status/extensions without default usage, warnings or rounding. openrouter-adapted is explicit protocol conversion, and legacy-cache serves older cached data with documented limitations. Valid upstream errors retain 4xx/5xx status and JSON except for safe replacement and validation exceptions; the stable code/source are headers, not arbitrary provider text.

Recover interrupted requests

SDK 0.3.0 does not generate idempotency keys for you. Both examples disable retries and explicitly retain a key. After a network timeout, retry the same effective model, state, questions, extensions and key. Do not generate a fresh key merely because no response arrived. Exact high-precision numeric inputs must also stay the same; use raw HTTP/editor text when JavaScript Number would round them before serialization.

A pending request returns 409 request_in_progress; wait according to Retry-After. A recorded failure returns 409 idempotency_failed on reuse and has been refunded; only then use a new key for a new attempt. See idempotency and retries.

Next, review the three primitives, request limits, and credits. The SDK source documents its separate direct-provider adapters.

System One

A typed decision API for applications using Jev.

Decision primitives

Preserve the meaning of choices, weighted rubric scores, and boolean probabilities.

On this page

Create an API keyInstall the published SDKSend your first decisionRead the resultOfficial TypeSafe client and response headersRecover interrupted requests