# Authentication

[RightNow AI](https://www.rightnowai.co/) / [Decision model](https://www.rightnowai.co/docs/decision-model) / [Authentication](https://www.rightnowai.co/docs/decision-model/authentication)

Updated 2026-11-13

Send a workspace API key in the Authorization header with every decide request.

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

For a successful request, read `answers.damaged`.

The key identifies its workspace.

## The header

- **Header**: `Authorization: Bearer <your key>`
- **Variable**: `SYSTEM_ZERO_API_KEY`

**Illustration of key handling.**

```text
[ Server environment ]
           |
           v
[ Authorization: Bearer ]
           |
           v
[ HTTPS API request ]
           |
           v
[ Key's workspace ]
```

Keep your key on your server. Calls belong to its workspace.

Call from your server over HTTPS. Calls from web pages are not accepted.

Plain HTTP returns `403 https_required`.

See [API reference](https://www.rightnowai.co/docs/decision-model/decide) for request fields and response headers.

## Keys

Create and revoke keys under API keys in an active workspace. Copy the full key when it appears. The browser tab where you created it temporarily keeps it in memory for your first call. Reloading, leaving the console or signing out clears it.

There is no public workspace-selection header.

All roles can create keys and revoke their own keys. Owners and admins can revoke any key in the workspace. See [Workspaces and keys](https://www.rightnowai.co/docs/decision-model/workspaces).

## Revocation

If a key is lost or exposed, revoke it and create a replacement. Update the apps that read it from `SYSTEM_ZERO_API_KEY`. Follow [Leaked API keys](https://www.rightnowai.co/docs/decision-model/leaked-api-keys) for the replacement steps.

## Refusals

**Key refusals**

| Status | Code | Action |
| --- | --- | --- |
| 401 | `invalid_api_key` | Use a valid RightNow decision model API key. |
| 403 | `auth_error` | Replace the revoked or blocked key. |
| 403 | `account_suspended` | Contact support about workspace access. |

Next: Send your first authenticated request with the [Quickstart](https://www.rightnowai.co/docs/decision-model/quickstart).

## Next

[Decisions](https://www.rightnowai.co/docs/decision-model/decisions)
