# API reference

[RightNow AI](https://www.rightnowai.co/) / [Decision model](https://www.rightnowai.co/docs/decision-model) / [API reference](https://www.rightnowai.co/docs/decision-model/decide)

Updated 2026-11-13

[Status](https://status.rightnowai.co/)

Send a model, state and named questions to the decide endpoint to receive structured answers.

**TypeScript (decide.ts)**

```typescript
import { SystemZeroClient } from '@rightnowai/system-zero';

const client = new SystemZeroClient({
  apiKey: process.env.SYSTEM_ZERO_API_KEY,
  baseURL: "https://api.rightnowai.co/v1",
});
const reply = await client.decide({
  "model": "rightnow-decision",
  "state": "The blender I ordered arrived with a cracked jar.",
  "questions": {
    "damaged": {
      "type": "boolean",
      "instructions": "Did the item arrive damaged?"
    }
  }
});
console.log(JSON.stringify(reply, null, 2));
const models = await client.models.list();
console.log(models);

// Set SYSTEM_ZERO_API_KEY to your API key before running this example.
// Run this example on your server.
// SDK defaults: The default is 5 retries, also the maximum, with 150 seconds
// per attempt and a 60-second total retry budget. A single wait longer than 60
// seconds raises the last error.
// Use Retry-After-Ms, or Retry-After when milliseconds are absent. The wait is
// never shorter than a random backoff floor: a random fraction of 0.5 seconds
// doubled for each attempt, capped at 8 seconds.
```

**Python (decide.py)**

```python
from system_zero import SystemZero

import os

with SystemZero(
    api_key=os.environ["SYSTEM_ZERO_API_KEY"],
    base_url="https://api.rightnowai.co/v1",
) as client:
    reply = client.decide({
        "model": "rightnow-decision",
        "state": "The blender I ordered arrived with a cracked jar.",
        "questions": {
            "damaged": {
                "type": "boolean",
                "instructions": "Did the item arrive damaged?"
            }
        }
    })
    print(reply)
    models = client.models.list()
    print(models)

# Set SYSTEM_ZERO_API_KEY to your API key before running this example.
# Run this example on your server.
# SDK defaults: The default is 5 retries, also the maximum, with 150 seconds per
# attempt and a 60-second total retry budget. A single wait longer than 60
# seconds raises the last error.
# Use Retry-After-Ms, or Retry-After when milliseconds are absent. The wait is
# never shorter than a random backoff floor: a random fraction of 0.5 seconds
# doubled for each attempt, capped at 8 seconds.
```

**Manual HTTP (decide.sh)**

```shell
# Set SYSTEM_ZERO_API_KEY to your API key before running this example.
# Generate once per logical decision; reuse this key when retrying.
idempotency_key="$(uuidgen 2>/dev/null \
  || cat /proc/sys/kernel/random/uuid 2>/dev/null \
  || openssl rand -hex 16 2>/dev/null \
  || python3 -c 'from uuid import uuid4; print(uuid4())' 2>/dev/null)"
cat > request.json <<'EOF'
{
  "model": "rightnow-decision",
  "state": "The blender I ordered arrived with a cracked jar.",
  "questions": {
    "damaged": {
      "type": "boolean",
      "instructions": "Did the item arrive damaged?"
    }
  }
}
EOF
# Headerless error responses require Python 3 to read error.code.
# Manual example policy: 130 seconds per attempt, a 600-second resend window
# with no retry-count cap. If x-should-retry is present, resend only when
# x-should-retry is true. If the header is absent, resend on 429, or on 409 and
# 5xx only with a retryable error.code. No answer also retries. Wait Retry-After
# plus up to 10 %, otherwise a random wait doubling from 1 s to 30 s. Stop if
# Retry-After is over 60 s. Also stop when a 429 has no Retry-After, or a 5xx
# has x-should-retry but no Retry-After.
# SDK defaults: The default is 5 retries, also the maximum, with 150 seconds per
# attempt and a 60-second total retry budget. A single wait longer than 60
# seconds raises the last error.
# Use Retry-After-Ms, or Retry-After when milliseconds are absent. The wait is
# never shorter than a random backoff floor: a random fraction of 0.5 seconds
# doubled for each attempt, capped at 8 seconds.
random() { od -An -N4 -tu4 /dev/urandom | tr -d ' '; }
# Stop resending after 10 minutes.
start=$(date +%s) attempt=0
while :; do
  : "${SYSTEM_ZERO_API_KEY:?set it first}"
  : "${idempotency_key:?could not make one}"
  rm -f reply.json
  out=$(curl https://api.rightnowai.co/v1/system-zero/decide \
    --max-time 130 -sS -o reply.json -D - -w '%{http_code}' \
    -H "Authorization: Bearer $SYSTEM_ZERO_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $idempotency_key" \
    -d @request.json)
  code=$(printf '%s' "$out" | tail -c 3)
  retry_header=$(printf '%s\n' "$out" \
    | grep -i '^x-should-retry:' | tail -n 1)
  retry=$(printf '%s\n' "$retry_header" \
    | sed 's/^[^:]*:[[:space:]]*//;s/[[:space:]]*$//')
  after=$(printf '%s\n' "$out" | grep -i '^retry-after:' \
    | tail -n 1 | sed 's/^[^:]*:[[:space:]]*//;s/[[:space:]]*$//')
  case $after in *[!0-9]*|'') after= ;; esac
  if [ -z "$retry_header" ]; then
    case $code in
      000|429) ;;
      409|5??)
        error_code=$(python3 -c 'import json, sys
try:
    print(json.load(sys.stdin).get("error", {}).get("code", ""))
except (ValueError, AttributeError):
    pass' < reply.json 2>/dev/null) || break
        case $code:$error_code in
          409:idempotency_conflict) ;;
          5??:service_unavailable) ;;
          5??:system_zero_unavailable) ;;
          5??:billing_settlement_error) ;;
          5??:idempotency_unavailable) ;;
          5??:limiter_unavailable) ;;
          5??:internal_error) ;;
          5??:maintenance) ;;
          *) break ;;
        esac ;;
      *) break ;;
    esac
  else
    [ "$retry" = true ] || break
    case $code in 4??|5??) ;; *) break ;; esac
  fi
  if [ -n "$after" ]; then
    after=$(printf '%s' "$after" | sed 's/^0*//')
    after=${after:-0}
    [ "$after" -le 60 ] || break
    ms=$(( after * 1000 + $(random) % (after * 100 + 1) ))
  else
    [ "$code" != 429 ] || break
    if [ -n "$retry_header" ]; then
      case $code in 5??) break ;; esac
    fi
    cap=$(( attempt < 5 ? 1000 << attempt : 30000 ))
    ms=$(( $(random) % (cap + 1) ))
  fi
  [ $(( $(date +%s) - start + ms / 1000 )) -lt 600 ] || break
  sleep "$(( ms / 1000 )).$(printf '%03d' $(( ms % 1000 )))"
  [ $(( $(date +%s) - start )) -lt 600 ] || break
  attempt=$(( attempt + 1 ))
done
echo "HTTP $code"
cat reply.json 2>/dev/null
```

## Request fields

`POST https://api.rightnowai.co/v1/system-zero/decide` accepts a JSON body with a RightNow decision model API key in the `Authorization: Bearer` header.

- **`model`**: `rightnow-decision` follows the alias. Use an attested pinned version to select a model ([Models](https://www.rightnowai.co/docs/decision-model/models)).
- **`state`**: The text, JSON object or JSON array to evaluate.
- **`questions`**: Named boolean, choice or score questions, each with its own instructions ([Decisions](https://www.rightnowai.co/docs/decision-model/decisions)).
- **`session_id`**: Optional routing affinity hint. It does not create a persistent session.
- **`prompt_cache_key`**: Optional routing hint used when `session_id` is absent.
- **`attachments`**: Optional inline files in the formats listed in [Inputs](https://www.rightnowai.co/docs/decision-model/attachments). The complete JSON request, including state, questions and base64 data, must fit the published caller body limit.
- **`policy`**: Optional confidence threshold and cost cap for this request ([Request policy](https://www.rightnowai.co/docs/decision-model/request-policy)).

Send `Content-Type: application/json`. Add an `Idempotency-Key` when you need replay protection ([Idempotency](https://www.rightnowai.co/docs/decision-model/idempotency)).

**One decide request, from key to answer.**

1. **Before the decision**: RightNow checks your key, the body and your limits before any work starts. [Key](https://www.rightnowai.co/docs/decision-model/authentication) missing or unknown: `401 invalid_api_key`. Body over 3,665,920 bytes: `413 payload_too_large`. Over 600 [requests a minute](https://www.rightnowai.co/docs/decision-model/rate-limits), by default: `429 rate_limit_exceeded`. Over 16 calls in flight, by default: `429 concurrency_limit_exceeded`. [Same key and body](https://www.rightnowai.co/docs/decision-model/idempotency) as a stored answer: that answer can return with `X-RightNow-Idempotent-Replay: true`.
2. **The decision**: RightNow answers each question you named. Unavailable or starting: `503 system_zero_unavailable` with `Retry-After`. Wait, then send the same request. No answer in time: `504 system_zero_timeout`. Do not resend it unchanged.
3. **After the decision**: HTTP 200 returns `answers` under your question names, and `usage`. A question can be refused inside a 200. Read every entry. See [Pricing](https://www.rightnowai.co/docs/decision-model/pricing) for what an answered call costs.

## Read the response

A successful request returns HTTP 200. Read each named entry in `answers`; it can hold an answer or a question refusal.

Read `answers.damaged` in your response. `probability` is the likelihood of true; `decision` is the selected boolean; `confidence` is its probability.

**The shape of a reply. Each type stands in for a value.**

**reply shape**

```text
id: string
model: string
served_by: string
created: integer
context:
  route: string
  tokens: integer
answers:
  damaged:
    type: "boolean"
    probability: number
    confidence: number
    calibration_version: string
    decision: boolean
    alternatives:
      decision: boolean
      confidence: number
    calibrated: boolean
usage:
  input_tokens: integer
  cached_input_tokens: integer
  media_tokens: integer
  long_input_tokens: integer
  media_items: integer
  thought_tokens: integer
```

- **`id`**: Identifies the decision.
- **`model`**: The answering model version.
- **`served_by`**: The answering version, matching `model`.
- **`created`**: When the decision was made, in Unix seconds.
- **`context`**: `route`, the context route, and `tokens`, the context's size in tokens.
- **`answers`**: Results grouped under the question names from the request.
- **`usage`**: Token counts (`input_tokens`, `cached_input_tokens`, `media_tokens`, `long_input_tokens` and `thought_tokens`) and `media_items`, the number of media items.
- **`policy`**: The request policy you sent, echoed as sent. Absent when you sent none.

Fresh answers include `decision`, `confidence`, `alternatives`, `calibrated` and `calibration_version`. `confidence` is the probability of the selected decision. Legacy idempotent replays retain their stored answer shape. See [Decisions](https://www.rightnowai.co/docs/decision-model/decisions) for the field rules and question refusals.

With a request policy, an answer below `min_confidence` has `abstained: true` and no `decision`. Never act on its other fields.

A successful reply can also include `provenance`, metadata about the execution that produced it. Read [Provenance](https://www.rightnowai.co/docs/decision-model/provenance) for its fields.

## Event stream reply

`POST /v1/system-zero/decide` can return the complete decision over Server-Sent Events. Send `Accept: text/event-stream` with exactly one media range. The match ignores case and allows parameters such as `charset=utf-8`. A list, `*/*` or any `q` parameter keeps the JSON reply. A `stream` member in the body returns HTTP 400 `invalid_request`.

This is the same answer at the same time and price as JSON. There are no partial answers. The model returns decisions with confidences, not generated text. Prices, receipts, request limits, rate limits and concurrency limits are unchanged. One stream counts as one request. There is no streaming or per-event fee.

**Event stream events**

| Event | Carries |
| --- | --- |
| `decision` | The complete JSON reply, byte for byte, on one `data:` line. |
| `done` | HTTP status, request id, replay flag, latency, server timing and rate limit. Always last. |

Only a completed HTTP 200 reply becomes an event stream. Every refusal keeps its HTTP status, JSON error envelope and headers. The WebSocket stream is separate and unchanged. Read [Streaming](https://www.rightnowai.co/docs/decision-model/streaming).

The reply has `Content-Type: text/event-stream; charset=utf-8` and `Cache-Control: no-store`. Accept a data line of up to 4,000,000 bytes. Ignore unknown event names and comment lines. There are exactly two events, with no progress events or keepalive comments.

Use `fetch` or another HTTP client that supports POST with a body and an Authorization header. Browser `EventSource` cannot send this request. There is no event id, retry field or resume. If the connection breaks, resend the same body with the same `Idempotency-Key` within 24 hours. A saved answer returns the same decision at no cost, with `replayed: true` in `done`.

Values are illustrative, not measured. Reply fields are shortened for display.

**Manual HTTP**

```bash
curl -N https://api.rightnowai.co/v1/system-zero/decide \
  -H "Authorization: Bearer $SYSTEM_ZERO_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: text/event-stream' \
  -H 'Idempotency-Key: ticket-8841-triage-1' \
  --data-binary @- <<'JSON'
{
  "model": "rightnow-decision-3.0.0",
  "state": "The charger arrived cracked. I want my money back.",
  "questions": {
    "wants_refund": {
      "type": "boolean",
      "instructions": "Is the customer asking for a refund?"
    }
  }
}
JSON

event: decision
data: {"answers":{"wants_refund":{"decision":true,"confidence":0.98}}}

event: done
data: {"http_status":200,"replayed":false,"latency_ms":149}
```

## Response headers

The key window is checked first. A key refusal skips the workspace check. Otherwise, the reset uses the window with fewer requests remaining; ties select the key. Console-created keys have equal limits for both windows.

- **`X-Request-Id`**: Keep this request identifier when contacting support.
- **`X-RateLimit-Limit`**: The lower key or workspace limit after both checks. A key refusal returns its own limit. Also includes `X-RateLimit-Remaining`.
- **`X-RateLimit-Reset`**: The Unix second when the window with fewer requests remaining resets; ties select the key. A key refusal returns its own reset.
- **`X-RightNow-Idempotent-Replay`**: `true` for a replay; `unavailable` when the settled answer cannot be read.
- **`X-Latency-Ms`**: Response timing. `Server-Timing` describes gateway stages; neither is a latency guarantee.
- **`Cache-Control`**: `no-store`. Call the API from a server over HTTPS.

On a rate refusal, `Retry-After` uses the refusing window's reset. The workspace's limit per minute is shared by all its keys.

## Validation

Use the decide schema. Unsupported fields such as `messages`, `temperature` and `stream` return `invalid_request`.

Inline input: text, JSON state (robot state), images, voice, PDF, video (preview). Only listed formats are supported. Read [Inputs](https://www.rightnowai.co/docs/decision-model/attachments) for input formats and availability and [Sessions](https://www.rightnowai.co/docs/decision-model/session-affinity) for the routing hints.

The 64k and 128k bands are served through Danger zone with SZ-Danger-Zone: long\_context\_64k or long\_context\_128k. Read [Danger zone](https://www.rightnowai.co/docs/decision-model/danger-zone) for access steps and limits.

For a refusal, inspect `error.code` and `error.retryable`. Follow the response's retry advice in [Errors](https://www.rightnowai.co/docs/decision-model/errors).

## Credit balance

`GET /v1/credits` reads the balance for a workspace using prepaid credits. Use your API key.

- **Read limit**: 60 per minute
- **Reply fields**: `object, balance_cents, available_cents, currency, as_of, promotional_held_cents`
- **`object`**: `credits`
- **`balance_cents`**: The balance in whole cents.
- **`available_cents`**: The balance after subtracting held funds, in whole cents.
- **`currency`**: `usd`
- **`as_of`**: The time of the reading as an ISO timestamp.
- **`promotional_held_cents`**: Optional. Promotional credit reserved by in-flight claims, in whole cents.

## Next

[Decision records](https://www.rightnowai.co/docs/decision-model/decision-records)
