System One
System One
DocumentationSDK documentationModelsAPI referenceOpen System OneDocumentation index

SDK & API

Get started with the TypeScript SDK

Build with decisions

Choose a modelDecision primitivesAgent integration: typed tool routingSystem One for coding agents (Claude Code, Cursor, Codex)

Usage & data

Billing and creditsPrivacy and zero data retention
Connect SDK to hosted API

Get started with the TypeScript SDK

Install the System One TypeScript SDK, connect to the hosted API, pick a model, and read a typed decision.

This guide connects a server-side TypeScript application to the System One hosted decision API. You will install the SDK, configure a platform API key and catalog model, and evaluate a support message as a team choice, urgency score, and duplicate-charge probability. The API returns decisions; it does not route a ticket, issue a refund, or execute tools.

Answer-ready

Q: How do I get started with the System One TypeScript SDK?
A: Install the System One TypeScript SDK, set your API key, choose a model such as Clef Flash or Jev, and call Choice, Score, or Noul to receive a typed decision from the hosted System One API.

Q: Do new accounts get free credits?
A: Yes. New accounts get 100 credits on signup, and credits never expire.

SDK setup and API pricing are separate

The open-source TypeScript SDK provides the client, adapters, and typed results. Installing it does not create a hosted account or buy inference. Calls to the hosted decision API use your System One account's credits at the selected model's current price. Check that price and the request size before sending a decision; SDK installation is not a pricing plan.

This guide uses a System One platform API key, not a direct provider key. If you want to connect the SDK directly to another service, follow SDK installation and direct-service adapters instead: those integrations use their own credentials and billing. Do not put a provider key in SYSTEM_ONE_API_KEY.

1. Create an API key and select a model

Sign in to System One, complete email verification, and open API keys in your console. Create a named key and copy the secret when shown. The list displays only a prefix; the complete secret cannot be retrieved later. Keep it on your server, outside prompts, browser code, logs, and version control.

New accounts get 100 free credits on sign-up. Check the console balance. Creating more keys does not create more welcome credits.

Choose a model using the current directory or Playground selector. Click Model information beside the selector to review the selected model's rate and limits. Copy its exact name and check that it supports Choice, Score, and Noul. You can also retrieve the directory with public GET /v1/models, or browse /en/models. YOUR_MODEL_ID below is a placeholder, not a supported alias. Neither SDK defaults nor a model ID used by another service are a safe substitute.

2. Install and configure

The SDK supports Node.js 20+. The commands below use Node.js 22.18+, which can run these .mts TypeScript files directly.

npm install @system-one-ai/core@0.8.0 @system-one-ai/adapter-system-one@0.7.0 @system-one-ai/transport-fetch@0.8.0

This quickstart uses the current hosted trio: core 0.8.0, adapter-system-one 0.7.0, and transport-fetch 0.8.0. Packages release independently and all resolve core@^0.8.0. systemOneAdapter encodes Clairic extract() (including images); it still does not encode evaluate() images. For hosted Clef/Clef Flash/Clef Omni decision images, use the standard Fetch example. For Clairic extraction, see Clairic extraction.

Create a local .env file excluded from version control:

SYSTEM_ONE_API_KEY=replace-with-your-platform-key
SYSTEM_ONE_BASE_URL=https://system-one.dev/v1
SYSTEM_ONE_MODEL=YOUR_MODEL_ID

The API uses the main website origin, not the documentation host. The API key is your System One platform credential. An operation key is different: create a fresh random UUID for each new logical evaluation, not a shared environment setting. Retrying that operation keeps the same key and complete request.

Replace YOUR_MODEL_ID in .env with the selected catalog entry's exact name before continuing. Keep the file and decision.mts in the same working directory for the command below. The System One adapter uses https://system-one.dev/v1 as its base URL and sends decisions to /v1/systemone; https://docs.system-one.dev serves documentation, not API requests. See the HTTP API authentication and endpoint reference when configuring a different HTTP client.

3. Send your first decision

The example supplies one shared support message as state and three named questions. choice() declares the allowed teams, score() declares an ordered urgency rubric, and booleanQuestion() asks for a probability rather than a true/false action. See Choice, Score, and Noul decision primitives for how to design those questions.

Save as decision.mts:

import { createSystemOne, choice, score, booleanQuestion } from '@system-one-ai/core';
import { systemOneAdapter } from '@system-one-ai/adapter-system-one';
import { createFetchTransport } from '@system-one-ai/transport-fetch';

const apiKey = process.env.SYSTEM_ONE_API_KEY;
const baseURL = process.env.SYSTEM_ONE_BASE_URL;
const model = process.env.SYSTEM_ONE_MODEL;
if (!apiKey || !baseURL || !model || model === 'YOUR_MODEL_ID') {
  throw new Error('Set the API key, base URL, and catalog model.');
}

const client = createSystemOne({
  apiKey,
  baseURL,
  model,
  adapter: systemOneAdapter,
  transport: createFetchTransport(),
  timeoutMs: 30_000,
  maxRetries: 0,
});

const operation = {
  idempotencyKey: crypto.randomUUID(),
  request: {
    model,
    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 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?', {
        true: 'The same order was charged more than once',
        false: 'No duplicate charge is reported',
      }),
    },
  },
};

const result = await client.evaluate(operation.request, {
  headers: { 'Idempotency-Key': operation.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,
  model: result.model,
  requestId: result.response.requestId,
});
node --env-file=.env decision.mts

Every run of this script starts a new billable operation, even when the input is unchanged. The operation exists only in memory; rerunning the script is not recovery after a timeout or crash. Its three questions and full request size affect cost at the selected model's current rate. Check the estimate and account balance first; no fixed answer, cost, or latency is promised. The sample uses non-null state, explicit instructions, ordinary question IDs, non-null criteria, three Choice options, and three Score levels. Check the selected model's current capabilities rather than assuming every future model accepts the same request.

4. Understand the result

  • team.choice is one of the declared option names, with its literal type preserved.
  • urgency.score can be fractional between 0 and 2 for this three-level rubric.
  • duplicate.probability is P(true) from 0 to 1, not a JavaScript boolean.
  • Optional statistics and token counts remain unknown when absent. Do not replace them with zero.

Core normalizes the result across adapters. On the wire, booleanQuestion() becomes type: 'noul', with an answer field named noul; core exposes type: 'boolean' and probability instead. This typed result is not the exact HTTP response body and does not expose all response headers. Use the explicit HTTP adapter and Fetch transport when connecting to this hosted API; direct-service adapters described in SDK documentation are different integrations.

Inspect result.model alongside the typed answers to identify the returned model, and keep the request ID for diagnostics without logging credentials or sensitive state. A well-typed answer does not prove the judgment is correct. Your application decides whether to route the ticket or require human review; see agent integration and execution boundaries before connecting answers to actions.

The first example prints SDK metadata, not a billing receipt. If your integration needs the platform accounting ID, charged credits, or replay status, use the response-header example below instead of running a second request just to inspect the first. Each example creates its own operation.

Official TypeSafe client and response headers

The official @typesafe-ai/sdk is another supported HTTP client, with different constructor options, types, and result wrappers. Its base URL is the API root without /v1: it appends /v1/systemone itself.

npm install @typesafe-ai/sdk@0.6.0

Save as official-decision.mts. Use the same API key, base URL, and selected model as above. This independent example creates its own new operation and asks one non-null Noul question; it does not depend on nullable-input support.

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

const apiKey = process.env.SYSTEM_ONE_API_KEY;
const baseURL = process.env.SYSTEM_ONE_BASE_URL;
const model = process.env.SYSTEM_ONE_MODEL;
if (!apiKey || !baseURL || !model || model === 'YOUR_MODEL_ID') {
  throw new Error('Set the API key, base URL, and catalog model.');
}

const client = new TypeSafeClient({
  apiKey,
  baseURL: baseURL.replace(/\/v1\/?$/, ''),
  defaultModel: model,
  timeout: 30_000,
  retry: { maxRetries: 0 },
  logLevel: 'off',
});
const operation = {
  idempotencyKey: crypto.randomUUID(),
  request: {
    model,
    state: { message: 'I was charged twice for the same order.' },
    questions: {
      duplicate: {
        type: 'noul' as const,
        instructions: 'Does the message report a duplicate charge?',
        criteria: {
          true: 'The same order was charged more than once',
          false: 'No duplicate charge is reported',
        },
      },
    },
  },
};
const { data, response, requestId: typeSafeRequestId } = await client.systemOne(
  operation.request,
  { headers: { 'Idempotency-Key': operation.idempotencyKey } },
).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({
  duplicateProbability: data.answers.duplicate.noul,
  model: data.model,
  inputTokens: data.usage?.input_tokens,
  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',
});
node --env-file=.env official-decision.mts

Every run starts another billable evaluation, including repeated runs with identical input. It is not an offline alternative to the first call, nor restart-safe recovery: this operation also exists only in memory.

withResponse() returns parsed data and a Response whose body has already been consumed. Its requestId is from X-TypeSafe-Request-Id; platform accounting uses X-Request-Id. Read charge and replay information from X-System-One-Credits and X-Idempotency-Replayed. Missing credits remain unknown, not zero. Body properties such as billing or request_id must not override these headers. A successful replay reports zero charge in the header.

For exact response text, keep the same client and operation above, but replace the entire block from the withResponse() call through its console.log() with this alternative; do not append it as a second evaluation:

const response = await client.systemOne(operation.request, {
  headers: { 'Idempotency-Key': operation.idempotencyKey },
}).asResponse();
const text = await response.text();
console.log(text);

asResponse() leaves body reading to you; do not also await the parsed result on that promise. Both methods reject non-2xx responses. Official APIError exposes status, headers, and body; read its headers for stable error classification, not arbitrary error text. The official models.list() unwraps the array, while raw GET /v1/models returns { models }.

Official 0.6.0 permits null state, omitted/null instructions, and null Noul criteria in its TypeScript contract. Core 0.8.0 requires non-null state, explicit instructions, and non-null boolean criteria when supplied. Neither client's types override the selected model's capabilities. Official timeout is per attempt; core timeoutMs is a total evaluation budget. Both examples disable retries. Keep browser credential exposure disabled and avoid debug logs containing submitted content. See the official SDK source.

Recover interrupted requests

These quickstarts create an operation once before sending and keep it only in memory. For an in-process retry, reuse that same operation.idempotencyKey and operation.request; do not rerun the creation block. For recovery after an application crash or restart, your real application must durably save the whole operation in its job record before the first send, then load that record instead of creating a new UUID or rebuilding the request. The quickstarts do not implement durable recovery.

For an uncertain result, retain the same effective model, state, questions, extensions, numeric values, and key within the recovery window. A timeout or cancellation does not prove that a charge was avoided. Do not silently substitute a different model or start over with a fresh UUID. Intentionally evaluating the same input again is a new operation and needs a new key. Keys share an account-wide namespace across API keys; changing the API credential does not create a new operation namespace.

409 request_in_progress means wait according to Retry-After and recover the same request. 409 idempotency_failed means the prior attempt failed and was refunded; a deliberate new attempt requires a new key. See idempotency and retries for retention, expiry, conflicts, and unresolved charges.

Verify the integration and troubleshoot setup

Work through these checks without repeatedly sending a decision. Installing packages and reviewing configuration do not require inference; an authenticated model catalog request checks access and available models without consuming decision credits. It does not prove that an evaluation will succeed or that you have enough balance.

CheckWhat to inspect or correct
Runtime and packagesUse Node.js 22.18+ for the documented .mts commands and the matched core 0.8.0 / adapter-system-one 0.7.0 / transport-fetch 0.8.0 packages.
Missing environment settingRun from the directory containing .env and the script; replace both the key and model placeholders. Keep secrets on the server and out of logs.
Authentication failsUse an active System One API key, not a provider key or a browser session. Check the key in the console without posting its value in a support request.
Wrong endpoint or HTML responseUse https://system-one.dev/v1 for the core adapter, not the docs host. The official TypeSafe client takes the root without /v1, as shown above.
Model or request rejectedCopy the exact current catalog name; check supported question types and request limits. Keep the three-question example text-only.
Typed result receivedInspect the named answers, returned model, and available usage metadata. Missing optional values remain unknown; a probability is not an instruction to execute an action.
Charge needs reconciliationUse X-Request-Id, X-System-One-Credits, and X-Idempotency-Replayed from a header-aware integration, then compare with console activity. Token counts and X-TypeSafe-Request-Id do not replace platform accounting metadata.
Timeout, interrupted request, or API errorDo not rerun either quickstart to recover. Preserve the original operation, follow idempotency and retry rules, and use the stable API error reference rather than guessing from error text.

Only make the first real decision after reviewing the current price and balance. Completing setup is not confirmation of a paid evaluation; inspect the actual response and account activity when you choose to send one.

Optional: try the Playground without code

Open the Playground after signing in. It uses your browser session, so no API key or SDK is needed. Select a model and click Model information beside the selector to review its price and limits, then load a customer-support example. State is the context; Questions describe the judgments and available outcomes. You can run the example unchanged after reviewing the estimated credit cost.

This is still a real evaluation. Read the answers, optional distributions, charge, measured time, and request ID, then check recent activity in the console. Missing statistics remain unknown. If the connection fails, do not repeatedly press Run: another run may be charged separately. Check usage first, and contact support@system-one.dev with the request ID if needed, never a key or sign-in code.

Next steps

  • SDK guide and migration from the former package.
  • Decision primitives, API reference, and model selection.
  • Agent integration, versioning, and data handling.

Extract typed fields with Clairic

Call extract({ state, fields }) with Clairic — field types, images, and hosted HTTP body examples.

API reference

HTTP decisions, auth, response contracts, and TypeScript examples for Choice, Score, and yes-no.

On this page

Answer-readySDK setup and API pricing are separate1. Create an API key and select a model2. Install and configure3. Send your first decision4. Understand the resultOfficial TypeSafe client and response headersRecover interrupted requestsVerify the integration and troubleshoot setupOptional: try the Playground without codeNext steps