TypeScript guide

OpenAI Decisions API in TypeScript

OpenAI has not published an SDK for its Decisions API — there is no client.decisions.create to copy. This page shows working, fully typed TypeScript for this site's endpoint — a callable OpenAI Decisions API alternative that serves decisions-1 and follows the same constrained-decision pattern.

Updated

Define the contract

Type the request once and reuse it everywhere. model is a literal union so the pinned id never drifts into a typo. questions is a record keyed by your ids, each noul, choice, or score.

Type the answers as a discriminated union on type. That is what makes response handling safe: a noul answer has noul, a choice answer has choice and probabilities — narrowing on type gives you the right fields.

TypeScript

interface DecisionQuestion {
  type: 'noul' | 'choice' | 'score'
  instructions: string
  criteria?: Record<string, string> | string[]
}

interface DecisionRequest {
  model: 'decisions-1' | 'decisions-latest'
  state: string | Record<string, unknown> | unknown[]
  questions: Record<string, DecisionQuestion>
}

interface DecisionAnswers {
  [questionId: string]:
    | { type: 'noul'; noul: number }
    | { type: 'choice'; choice: string; probabilities: Record<string, number>; confidence: number }
    | { type: 'score'; score: number; legend: string[]; probabilities: Record<string, number>; confidence: number }
}

Make the call

A single POST with a Bearer key. satisfies DecisionRequest checks the body at compile time; AbortSignal.timeout keeps hung calls from stalling the pipeline. Narrow answers with the type field before reading them.

TypeScript

const res = await fetch('https://decisions-api.net/api/v1/decisions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.DECISIONS_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(request satisfies DecisionRequest),
  signal: AbortSignal.timeout(30000),
})
const { answers } = (await res.json()) as { answers: DecisionAnswers }

const team = answers.team
if (team.type === 'choice' && team.confidence >= 0.7) {
  routeTo(team.choice)
}

Timeouts and retries

429 and 502 are worth a short backoff retry — failed calls are not billed. 402 means the balance is empty: top up, do not retry. 422 is a validation error; the message names the field, so fix the body instead of retrying.

TypeScript

async function decide(body: DecisionRequest, attempts = 3): Promise<DecisionAnswers> {
  for (let i = 0; i < attempts; i++) {
    const res = await fetch('https://decisions-api.net/api/v1/decisions', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.DECISIONS_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body),
      signal: AbortSignal.timeout(30000),
    })
    if (res.status === 429 || res.status === 502) {
      await new Promise(r => setTimeout(r, 2 ** i * 1000))
      continue
    }
    if (res.status === 402) throw new Error('out of credits')
    if (!res.ok) throw new Error(`decision call failed: ${res.status}`)
    return (await res.json() as { answers: DecisionAnswers }).answers
  }
  throw new Error('decision call failed')
}

Keep the provider behind one function

Callers should see a typed function that takes text and returns a label — not HTTP details. When OpenAI opens its Decisions API you swap the inside of decide() and keep every call site unchanged. The question text, options, and thresholds all carry over.

TypeScript

// Keep the decision behind one typed function. Swap the HTTP
// layer when OpenAI publishes its schema — callers never change.
export async function routeTicket(text: string): Promise<string> {
  const answers = await decide({
    model: 'decisions-1',
    state: text,
    questions: {
      team: {
        type: 'choice',
        instructions: 'Which team should own this ticket?',
        criteria: {
          payments: 'Checkout or billing.',
          frontend: 'Rendering or browser behavior.',
          account: 'Login or permissions.',
        },
      },
    },
  })
  const team = answers.team
  return team.type === 'choice' && team.confidence >= 0.7 ? team.choice : 'triage'
}

FAQ

Is there an official OpenAI SDK example for the Decisions API?

No. OpenAI has not published SDK methods or a request schema for its Decisions API — anything showing client.decisions.create is invented. This page uses plain HTTP, which is what any provider SDK would wrap anyway.

Do I need a code generator for the types?

No — the interfaces on this page cover the whole contract. Copy them into your project; they are small enough to audit and stable across providers.

How do I send structured context?

state accepts a JSON object or array, not just a string — pass dicts directly in the json body and the model reads them as context.

Run a decision from the browser

Skip the setup — run a real call in the playground with 1 free credit for new visitors.