---
name: rightnow-system-zero
description: Call the RightNow decision model API with a key from SYSTEM_ZERO_API_KEY.
---

# RightNow decision model API

The RightNow decision model makes structured decisions from text and inline files and returns named answers with probabilities.

## Install an SDK

Read the API key from the SYSTEM_ZERO_API_KEY environment variable. Keep these calls on your server.

[TypeScript package README](https://www.npmjs.com/package/@rightnowai/system-zero)

[Python package README](https://pypi.org/project/rightnow-system-zero/)

```shell
npm install @rightnowai/system-zero@0.4.7
python -m pip install rightnow-system-zero==0.4.7
```

Node 22 or newer, ES modules, server side only.
Python 3.10 or newer, server side only.

SDK 0.4.7 adds typed decision provenance, webhook methods and request compression.

Tool selection: the RightNow decision model picks which of your tools to call next, or none, with a probability for each. It does not write arguments or messages, and it does not run tools.

Text, PDF and video allow 9 tools and none, or 10 when a tool is required; image or audio allows 25 tools and none, or 26 when a tool is required.

Use `toolQuestions` / `tool_questions` and `selectTool` / `select_tool`. See [Tool selection](https://github.com/RightNow-AI/system-zero-sdk/blob/sdk-v0.4.7/TOOL-SELECTION.md).

### TypeScript first call

```typescript
// 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.
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);
```

### Python first call

```python
# 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.
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)
```

### Inline files

Pass files as bytes; the helper builds the data URL. The helpers refuse locally with the API's own codes.

Preflight is explicit; complete image decoding, dimensions, availability and other request checks remain with the API.

Send audio separately from images, PDFs and video.

#### TypeScript media

```typescript
import { readFileSync } from 'node:fs';
import { SystemZeroClient, inlineAttachment, validateAttachments } from '@rightnowai/system-zero';

const client = new SystemZeroClient();
const picture = await client.decide({
  model: 'rightnow-decision', state: 'Use the picture.',
  attachments: validateAttachments([
    inlineAttachment({ name: 'picture', type: 'image', mime: 'image/png', data: readFileSync('picture.png') }),
  ]),
  questions: { ready: { type: 'boolean', instructions: 'Is the item ready?' } },
});
console.log(picture.answers);

const recording = await client.decide({
  model: 'rightnow-decision', state: 'Use the recording.',
  attachments: validateAttachments([
    inlineAttachment({ name: 'recording', type: 'audio', mime: 'audio/wav', data: readFileSync('speech-16khz.wav') }),
  ]),
  questions: { ready: { type: 'boolean', instructions: 'Is the item ready?' } },
});
console.log(recording.answers);
```

#### Python media

```python
from pathlib import Path
from system_zero import SystemZero, inline_attachment, validate_attachments

with SystemZero() as client:
    picture = client.decide({
        "model": "rightnow-decision", "state": "Use the picture.",
        "attachments": validate_attachments([
            inline_attachment(name="picture", type="image", mime="image/png", data=Path("picture.png").read_bytes()),
        ]),
        "questions": {"ready": {"type": "boolean", "instructions": "Is the item ready?"}},
    })
    print(picture["answers"])

    recording = client.decide({
        "model": "rightnow-decision", "state": "Use the recording.",
        "attachments": validate_attachments([
            inline_attachment(name="recording", type="audio", mime="audio/wav", data=Path("speech-16khz.wav").read_bytes()),
        ]),
        "questions": {"ready": {"type": "boolean", "instructions": "Is the item ready?"}},
    })
    print(recording["answers"])
```

Images, PDFs, and video share a 32,768 rendered-token limit.

### SDK retries

- Bounds: 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.
- Safe requests: Only GET requests or requests carrying a non-empty idempotency key can retry. With retries enabled, decide POSTs receive a generated key when none is supplied. An explicit blank key omits the header and disables decision retries. Each retry retains the same key and serialized body. Setting the retry count or budget to zero disables retries.
- Retry flags: Marked means error.retryable is true or the x-should-retry header is true. An explicit false in either marker vetoes retry.
- 429: capacity_exhausted, rate_limit_exceeded, concurrency_limit_exceeded or workspace_limit_exceeded.
- 5xx: except 504 and maintenance, including service_unavailable and system_zero_unavailable.
- 409: idempotency_conflict.
- Transport: A qualifying connection failure or timeout before any response byte can also retry, even if the request was sent. Failures after a response byte never retry. Errors of authentication and permission types never retry.
- Wait: 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.
- Cold start: A cold pool returns marked 503 system_zero_unavailable with a wait in Retry-After. The SDK retries the same request body with the same idempotency key. An explicit false retry marker stops retries.

## Streaming

Server to server only: Node 22 or later or Python 3. Browsers cannot set the Authorization header on the WebSocket upgrade.
Open WSS at `/v1/system-zero/stream`, request `system-zero.stream.v1`, and authenticate with `Authorization: Bearer <key>` on the upgrade.
Read hello and send start. Use append_text for strings, append_items for whole array elements, append_attachments for attachments and replace for a complete state. Each accepted mutation advances state_version. Automatic decisions may combine accepted updates. Check each decision's state_version and covered_event_ids before using it. Reconnect by replaying the full desired state with its unchanged idempotency key; this is not durable stream resume.
Both clients use a receipt deadline of 3818000 ms from command send; confirmed liveness can refresh inactivity timers but cannot extend it, and JSON-only EventTarget transports use a 3848000 ms heartbeat fallback without extending the receipt deadline.
Stream inline attachment `type` values: "document" or "image" or "audio" or "video".
PDF and video (preview) use the same limits as HTTP. Read [Attachments](https://www.rightnowai.co/docs/decision-model/attachments).
The stream does not take upload references.
Read [Streaming](https://www.rightnowai.co/docs/decision-model/streaming) for messages, ordering, limits, close codes and a complete JSON exchange.

## Manual HTTP

Read the key from `SYSTEM_ZERO_API_KEY` in the calling process. Run these examples on a server over HTTPS.
For a single decision as events, send Accept: text/event-stream. Read [Event stream reply](https://www.rightnowai.co/docs/decision-model/decide#event-stream) for the Accept rule, events and same-key retries.

### Manual HTTP

```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
```

## Text caching

Text caching is automatic. An optional prompt_cache_key improves reuse within a workspace. Cache lifetime, minimum size and eviction are not published.
For console usage, sum input_tokens, cached_input_tokens, media_tokens and cached_media_tokens across the selection. Text input is total input_tokens minus media_tokens and cached_media_tokens. Cached text is total cached_input_tokens minus cached_media_tokens. Express cached text divided by text input as a percentage.
Mixed selections can show a text rate when every row has all required measured counts. No data means a missing, invalid or inconsistent count, or no text input. Older usage can return null for cached_media_tokens.
Read [Text caching](https://www.rightnowai.co/docs/decision-model/caching) for usage fields, coverage and prices.

## Endpoint and authentication

- API base: `https://api.rightnowai.co/v1`.
- Decision: `POST https://api.rightnowai.co/v1/system-zero/decide`.
- Send `Authorization: Bearer $SYSTEM_ZERO_API_KEY` and `Content-Type: application/json`.
- Keep the key out of browser code, files, commits and logs.
- Create a named key under API keys in an active workspace.

## Request

- Inline input: Text, JSON, Robot state (sent as JSON), Codebases (sent as text), PDF, Images, Voice, Video (preview). Only listed formats are supported.
- TypeScript: `npm install @rightnowai/system-zero@0.4.7`.
- Python: `python -m pip install rightnow-system-zero==0.4.7`.
- SDK 0.4.7 adds typed decision provenance, webhook methods and request compression.
- Held upload references return 400 media_unsupported at attachments[i].upload. Send files inline.
- An admission store timeout returns 503 service_unavailable. Wait for Retry-After and Retry-After-Ms before retrying.
- Attachment names match `^[A-Za-z][A-Za-z0-9_-]{0,63}$`; a dotted name returns 400.
- The 64k and 128k bands are served through Danger zone with SZ-Danger-Zone: long_context_64k or long_context_128k.
- Video (preview): H.264 MP4 is served inline, with or without an opt-in header.
- Video costs USD 0.0008 per started second, with a USD 0.008 minimum per clip; clips up to 10 seconds.
- Each answered question costs USD 0.0001.
- Residual text tokens are charged separately.
- Send one silent H.264 MP4 video inline.
- Format: H.264 MP4.
- Duration: Up to 10 seconds.
- Dimensions: Up to 1920 x 1080.
- File size: Up to 2.5 MiB.
- Attachments: One video only.
- Send video without other files. Soundtracks and rotated videos are not supported.
- Use Manual HTTP with the rightnow-decision alias for video.
- image: image/png, image/jpeg. Static PNG, including interlaced PNG, and JPEG. Send files inline.
- image: USD 0.003 per image. Minimum per file: USD 0.003. Maximum measure: 16,777,216 source pixels.
- Audio: one WAV file with PCM or float samples at exactly 16,000 Hz per request. All attachments share the 2.5 MiB decoded limit. Channels and sample encoding affect how much audio fits. Audio is limited to 60 seconds per request.
- audio: audio/wav. Send inline RIFF WAV with integer PCM or IEEE float samples at exactly 16,000 Hz. Mono and multichannel files are accepted; pricing counts sample frames, not channels. Other audio formats, other sample rates, unknown audio MIME types and audio upload references are unavailable. Refused audio is not processed or charged and creates no billing receipt. Send files inline.
- audio: USD 0.0002 per started second (about USD 0.012 per minute). Minimum per file: 30 seconds. Maximum measure: 60 seconds, 1 pass.
- Documents: Send PDF files inline. Answers use readable text; pictures may be omitted. For a scan with no readable text, send extracted text in state. A scan whose text can't be read may not be answered reliably. Other native document formats are unavailable; send their extracted text in state.
- Text, office and archive documents return 415 document_unsupported.
- Up to 500 pages per PDF; 2,621,440 decoded bytes per request.
- Send PDF files inline. Document upload references are not available to outside callers today (400 media_unsupported at attachments[i].upload).
- Answers use the PDF text and include a note when pictures were left out.
- PDF is supported. Export this document to PDF and send the PDF file.
- document: USD 0.002 per page. Minimum per file: USD 0.001. Maximum measure: 500 pdf pages.
- Send model, state and questions. Use model rightnow-decision for every input. To pin a version, use rightnow-decision-3.0.0.
- state accepts a JSON object, array or string. Nested numbers, booleans and null remain JSON.
- Use compact keys and numeric strings for large integers or exact decimals when precision matters. Supply your own units, coordinate frames and freshness data.
- Plain HTTP decide has no state_version or sequence field. Associate its response with your own request.
- questions maps names to a type and instructions; the answer uses the same name.
- boolean: optional criteria with true and false descriptions.
- choice: provide named options to select among, within limits.choice_options.
- score: provide ordered level labels, within limits.score_levels.
- Unknown fields, including messages, temperature and stream, are refused as invalid_request.

## Answers

- Boolean answers return probability. Choice answers return choice and probabilities. Score answers return score, legend and probabilities.
- Fresh answers include decision, confidence, alternatives, calibrated and calibration_version. Probability and confidence are probability ratios.
- For a boolean, decision is true when true is at least as likely as false. confidence is the selected outcome's probability.
- For a choice, decision equals choice and confidence is probabilities[choice], the maximum option probability. A tied maximum preserves choice.
- For a score, decision is the most likely level key string, with ties at the lowest level. confidence is that level's probability; score remains the expected level index.
- alternatives lists every other outcome as { decision, confidence } objects. Entries use descending confidence; tied choices use UTF-8 key byte order and tied score levels use level order.
- calibration_version: An opaque public identifier, or `unpublished` when none is assigned. A nonempty identifier does not prove calibration.
- calibrated is false in this release; a calibration_version value does not make it true.
- Legacy idempotent replays retain their stored answer shape. Do not use legacy confidence as decision probability.
- A refused question contains error with code and retryable: false. An HTTP 200 reply can contain a refusal beside other answers.

## Idempotency and retries

- Rerunning a program creates a new idempotency key and starts new work.
- Idempotency-Key is optional. Use a fresh key for a new decision; retain the same key and JSON body for its retries.
- Successful keys have a 24-hour answer window and retain an identity tombstone without request or answer content for 30 days after that window; after answer expiry, the same body returns 409 idempotency_replay_unavailable and a changed body returns 400 idempotency_key_reuse.
- Follow error.retryable and x-should-retry. A false retry flag ends automatic retries, including system_zero_timeout.
- Prefer Retry-After-Ms when both wait headers exist; otherwise follow Retry-After. Keep retries bounded.
- A rate_limit_exceeded refusal with error.details.cause set to replay_bytes means: Your account has used today's allowance for replayed answers. A request that repeats an Idempotency-Key gets its stored answer back, and those replays are limited by total bytes per account per UTC day. Replays resume at the next UTC day; the Retry-After headers give the wait.
- Gateway 429 and 503 refusals carry Retry-After in whole seconds, rounded up, and Retry-After-Ms with the exact wait in milliseconds, except webhook_limit_exceeded.
- Maintenance returns 503 with Retry-After and Retry-After-Ms. Each answer draws a wait of about 5 minutes (4 to 6), uniformly from 240 to 360 whole seconds.
- A key-store outage returns 503 service_unavailable, which is retryable. Wait for Retry-After and Retry-After-Ms before retrying.
- A stored reply can be replayed while available. If a stored answer cannot be read temporarily, the same key and body may replay it after the store recovers and before the answer expires or is evicted. Recovery is not guaranteed. A new key starts new work and may produce a different answer. An unavailable replay does not mean the original request failed.

## Limits

Read the authenticated `GET https://api.rightnowai.co/v1/models` response for attested model IDs, limits and held_routes. A larger schema bound is not a serving promise.

- session_id is a routing affinity hint; it does not open a persistent session or guarantee cached input.
- [Inputs and exact refusals](https://www.rightnowai.co/docs/decision-model/attachments).
- [Sessions and affinity](https://www.rightnowai.co/docs/decision-model/session-affinity).

## Errors

Read error.code and error.retryable, not only the HTTP status. Keep X-Request-Id or request_id for support. A call without an API key returns 401 invalid_api_key.

HTTP 402 insufficient_credits uses permission_error. Add funds before starting a new request. Read balance_cents, required_cents and add_funds_url in error.details.

| Code | Status | Retryable | Action |
| --- | --- | --- | --- |
| `idempotency_key_reuse` | 400 | false | Use the original body with this key, or use a new key for changed work. |
| `invalid_idempotency_key` | 400 | false | Use a nonblank Idempotency-Key with valid ASCII characters within the accepted length. |
| `invalid_request` | 400 | false | Correct the JSON, field names, types or encoding before resending. |
| `invalid_trace_header` | 400 | false | Correct or remove X-Client-Request-Id; its value must be nonblank ASCII within the accepted length. |
| `media_unsupported` | 400 | false | An attachment type, format or count is not supported. Held upload references return 400 media_unsupported at attachments[i].upload. Send files inline. |
| `webhook_invalid` | 400 | false | The URL, events, limit or body is invalid, or Idempotency-Key was sent. |
| `invalid_api_key` | 401 | false | Authorization is missing, malformed or unknown; a Playground credential is not accepted as a public bearer key. Use a valid API key. |
| `insufficient_credits` | 402 | false | Add funds before starting a new request. Read balance_cents, required_cents and add_funds_url in error.details. |
| `spend_limit_reached` | 402 | false | Raise or remove the key's limit, or wait for resets_at. Read limit_cents, spent_cents and resets_at in error. |
| `account_suspended` | 403 | false | The account is suspended, blocked or waiting for access. Contact support. |
| `auth_error` | 403 | false | The API key is revoked or blocked. Replace the key; this response uses authentication_error as its type. |
| `https_required` | 403 | false | Use the HTTPS base URL. |
| `media_unpriced` | 403 | false | The input format has no published price. Use a priced format listed in Inputs. |
| `starter_credit_held` | 403 | false | Starting credit is under review. Contact us to request a review. |
| `audio_not_available` | 404 | false | This release can refuse an unavailable route or input. Audio combined with images, PDFs or video is unavailable. Run the audio on its own. |
| `document_not_available` | 404 | false | Send document contents as state text without an attachment. |
| `image_not_available` | 404 | false | This release can refuse an unavailable route or input. |
| `long_context_not_available` | 404 | false | Check the access steps in Danger zone, or send a shorter request. |
| `model_not_found` | 404 | false | The requested model is not offered. Choose the alias or an available pinned version. |
| `unknown_endpoint` | 404 | false | The route is unavailable. Check the base URL and path; this response does not mean the API key is invalid. |
| `video_not_available` | 404 | false | An unavailable video route was refused. Production serves inline H.264 MP4 video as a preview. |
| `decision_not_found` | 404 | false | The id is invalid, or no stored decision matches it for this API key. |
| `webhook_not_found` | 404 | false | The endpoint is absent or belongs to another workspace. |
| `method_not_allowed` | 405 | false | Use a method from the Allow response header. |
| `idempotency_conflict` | 409 | true | The same key and body is still running. Wait and retry with that key. |
| `idempotency_replay_unavailable` | 409 | false | The request completed, but the stored answer is unavailable. details.original_status records the original status. A new key starts new work; this is not a replay of the old answer. |
| `payload_too_large` | 413 | false | Reduce the whole request body. |
| `questions_too_large` | 413 | false | Send fewer or shorter questions. |
| `state_too_large` | 413 | false | Shorten the state or reduce nesting. |
| `media_too_large` | 413 | false | Media takes the attachments over the decoded size limit. |
| `document_too_large` | 413 | false | A document takes the attachments over the decoded size limit. |
| `document_unsupported` | 415 | false | A document's format is not supported, or its bytes do not match it. |
| `injection_suspected` | 422 | false | Remove embedded instructions from the input and send changed input. |
| `input_quarantined` | 422 | false | Do not resend the same input in a loop. |
| `invalid_criteria` | 422 | false | Use the supported criteria shape and the option and level counts in the model list's limits. |
| `question_too_large` | 422 | false | Shorten the affected question. |
| `request_refused` | 422 | false | Review the state and question instructions before starting new work. |
| `workspace_quarantined` | 422 | false | Stop dispatching work from this workspace while the pause is in effect. |
| `media_decode_failed` | 422 | false | Media bytes do not match the declared format. |
| `document_decode_failed` | 422 | false | A document could not be decoded. |
| `question_not_grounded` | 422 | false | The question cannot be answered from this state. |
| `capacity_exhausted` | 429 | true | Wait for Retry-After; retry only with bounded client backoff. |
| `concurrency_limit_exceeded` | 429 | true | The account already has its allowed work in flight. Follow Retry-After and reduce concurrency; details include max_concurrent. |
| `daily_dispatch_limit` | 429 | true | Wait until the UTC reset named by the response. |
| `daily_unbilled_limit` | 429 | true | Wait until the UTC reset named by the response. |
| `monthly_cap_reached` | 429 | true | The workspace reached its monthly decision cap. Follow resets_at and Retry-After; details include monthly_request_cap and used. |
| `playground_daily_limit` | 429 | true | The Playground reached its daily limit. Wait for resets_at; details include daily_limit. |
| `rate_limit_exceeded` | 429 | true | Wait for Retry-After and pace requests to the returned limits. |
| `workspace_limit_exceeded` | 429 | true | Wait for Retry-After and reduce the workspace's concurrent work or input load. |
| `starter_spend_limit` | 429 | true | Follow Retry-After or request a review of the starting credit spend limit. |
| `starter_rate_limit` | 429 | true | Follow Retry-After before sending more starting credit requests. |
| `starter_concurrency_limit` | 429 | true | Starting credit requests already occupy the available concurrency. Follow Retry-After. |
| `starter_attempt_limit` | 429 | true | Follow Retry-After before processing more starting credit requests. |
| `webhook_limit_exceeded` | 429 | false | The workspace already has 5 endpoints. Delete an endpoint before creating another. |
| `internal_error` | 500 | true | An unexpected failure was caught. Retry after the stated delay; contact support with the request ID if it persists. |
| `idempotency_unavailable` | 503 | true | Replay protection cannot answer. Retry with the same Idempotency-Key after the wait. |
| `limiter_unavailable` | 503 | true | The limiting service cannot answer. Retry after the stated delay. |
| `maintenance` | 503 | true | The API is in maintenance. Wait for Retry-After. |
| `service_unavailable` | 503 | true | Wait for Retry-After; keep the request ID if the failure persists. An admission store timeout returns 503 service_unavailable. Wait for Retry-After and Retry-After-Ms before retrying. |
| `system_zero_unavailable` | 503 | true | Wait for Retry-After and retry when retryable is true. |
| `starter_unavailable` | 503 | true | Starting credit enforcement is temporarily unavailable. Follow Retry-After. |
| `webhook_unavailable` | 503 | true | Webhook management storage or encryption is unavailable. |
| `system_zero_timeout` | 504 | false | Shorten the work or inspect the outcome; do not automatically replay unchanged. |

## Billing

Purchased credit expires 12 months after purchase. Your starting credit does not expire.
- Fresh text: USD 0.19. Per million residual uncached input tokens.
- Cached text: USD 0.019. Per million residual cached input tokens.
- Answered question: USD 0.0001. Per answered question.
- Call fee: USD 0.00001 per answered call. Once per call with at least one answered question.
- Output: USD 0. No output charge.
Cached input costs less.
Text caching is automatic. An optional prompt_cache_key improves reuse within a workspace. Cache lifetime, minimum size and eviction are not published.
Refused questions are not charged. An all-refused request has no text, question, file or call charge.

Read [Billing and credits](https://www.rightnowai.co/docs/decision-model/billing-and-credits) for the current account terms.

Read a prepaid workspace balance with GET /v1/credits using your API key. Reply fields: object, balance_cents, available_cents, currency, as_of, promotional_held_cents. Read limit: 60 per minute.
Read [Credit balance](https://www.rightnowai.co/docs/decision-model/decide#credits) for field meanings.

## Next

- [Pricing](https://www.rightnowai.co/docs/decision-model/pricing).
- [API reference](https://www.rightnowai.co/docs/decision-model/decide).
- [Errors](https://www.rightnowai.co/docs/decision-model/errors).
- [Leaked API keys](https://www.rightnowai.co/docs/decision-model/leaked-api-keys).
- [All guides](https://www.rightnowai.co/docs/decision-model/llms.txt).
