# Playground

[Docs](https://www.rightnowai.co/docs) / [System Zero](https://www.rightnowai.co/docs/system-zero) / [Playground](https://www.rightnowai.co/docs/system-zero/playground)

Updated 2026-10-18

Run a System Zero decision in Playground without pasting an API key.

**cURL (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": "system-zero",
  "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.
# 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.
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) ;;
          409:upload_in_use) ;;
          409:session_busy) ;;
          5??:service_unavailable) ;;
          5??:system_zero_unavailable) ;;
          5??:billing_settlement_error) ;;
          5??:idempotency_unavailable) ;;
          5??:limiter_unavailable) ;;
          5??:internal_error) ;;
          5??:maintenance) ;;
          5??:revoke_notice_unavailable) ;;
          *) 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
```

**PowerShell (decide.ps1)**

```powershell
# Set SYSTEM_ZERO_API_KEY to your API key before running this example.
if (-not $env:SYSTEM_ZERO_API_KEY) { throw "Set SYSTEM_ZERO_API_KEY first." }
# One attempt, no resend: running it again sends a new decision.
$body = @'
{
  "model": "system-zero",
  "state": "The blender I ordered arrived with a cracked jar.",
  "questions": {
    "damaged": {
      "type": "boolean",
      "instructions": "Did the item arrive damaged?"
    }
  }
}
'@
$request = @{
  Method      = "Post"
  Uri         = "https://api.rightnowai.co/v1/system-zero/decide"
  Headers     = @{
    Authorization     = "Bearer $env:SYSTEM_ZERO_API_KEY"
    "Idempotency-Key" = [guid]::NewGuid().ToString()
  }
  ContentType = "application/json; charset=utf-8"
  Body        = [Text.Encoding]::UTF8.GetBytes($body)
  TimeoutSec  = 130
}
Invoke-RestMethod @request | ConvertTo-Json -Depth 10
```

**Python (decide.py)**

```python
# Set SYSTEM_ZERO_API_KEY to your API key before running this example.
import os
import random
import time
import uuid

import requests

api_key = os.environ.get("SYSTEM_ZERO_API_KEY")
if not api_key: raise RuntimeError("Set SYSTEM_ZERO_API_KEY first.")

url = "https://api.rightnowai.co/v1/system-zero/decide"
# Generate once per logical decision; reuse this key when retrying.
idempotency_key = str(uuid.uuid4())
headers = {
    "Authorization": f"Bearer {api_key}",
    "Idempotency-Key": idempotency_key,
}
body = {
    "model": "system-zero",
    "state": "The blender I ordered arrived with a cracked jar.",
    "questions": {
        "damaged": {
            "type": "boolean",
            "instructions": "Did the item arrive damaged?"
        }
    }
}


# 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.
def retry_wait(response, attempt):
    """Seconds to wait before resending, or None when the answer is final."""
    backoff = random.uniform(0, min(30, 2**attempt))
    if response is None:
        return backoff
    status = response.status_code
    retry = response.headers.get("x-should-retry")
    if retry is None:
        try:
            code = response.json().get("error", {}).get("code")
        except (ValueError, AttributeError):
            code = None
        conflicts = (
            "idempotency_conflict",
            "upload_in_use",
            "session_busy",
        )
        unavailable = (
            "service_unavailable",
            "system_zero_unavailable",
            "billing_settlement_error",
            "idempotency_unavailable",
            "limiter_unavailable",
            "internal_error",
            "maintenance",
            "revoke_notice_unavailable",
        )
        allowed = status == 429 or (status == 409 and code in conflicts)
        allowed = allowed or (500 <= status <= 599 and code in unavailable)
        if not allowed:
            return None
    elif retry != "true" or status < 400:
        return None
    after = response.headers.get("Retry-After", "")
    if not after.isdigit():
        if retry is not None and status >= 500:
            return None
        return None if status == 429 else backoff
    if int(after) > 60:
        return None
    return int(after) * (1 + random.random() / 10)


# Stop resending after 10 minutes.
deadline = time.monotonic() + 600
attempt = 0
while True:
    try:
        response = requests.post(url, headers=headers, json=body, timeout=130)
    except (requests.ConnectionError, requests.Timeout):
        response = None
    wait = retry_wait(response, attempt)
    if wait is None or time.monotonic() + wait >= deadline:
        break
    time.sleep(wait)
    if time.monotonic() >= deadline:
        break
    attempt += 1
if response is None:
    raise RuntimeError("No answer within 10 minutes.")
response.raise_for_status()
print(response.json()["answers"])
```

**TypeScript (decide.ts)**

```typescript
// Set SYSTEM_ZERO_API_KEY to your API key before running this example.
const apiKey = process.env.SYSTEM_ZERO_API_KEY;
if (!apiKey) {
  throw new Error("Set SYSTEM_ZERO_API_KEY first.");
}

const url = "https://api.rightnowai.co/v1/system-zero/decide";
// Generate once per logical decision; reuse this key when retrying.
const idempotencyKey = crypto.randomUUID();
const body = JSON.stringify({
  "model": "system-zero",
  "state": "The blender I ordered arrived with a cracked jar.",
  "questions": {
    "damaged": {
      "type": "boolean",
      "instructions": "Did the item arrive damaged?"
    }
  }
});

// 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.
async function retryWait(response: Response | null, attempt: number) {
  const backoff = Math.random() * Math.min(30_000, 1_000 * 2 ** attempt);
  if (response === null) return backoff;
  const { status } = response;
  const retry = response.headers.get("x-should-retry");
  if (retry === null) {
    const payload = await response.clone().json().catch(() => null);
    const code = payload?.error?.code;
    const conflicts = [
      "idempotency_conflict",
      "upload_in_use",
      "session_busy",
    ];
    const unavailable = [
      "service_unavailable",
      "system_zero_unavailable",
      "billing_settlement_error",
      "idempotency_unavailable",
      "limiter_unavailable",
      "internal_error",
      "maintenance",
      "revoke_notice_unavailable",
    ];
    const allowed = status === 429
      || (status === 409 && conflicts.includes(code))
      || (status >= 500 && status <= 599 && unavailable.includes(code));
    if (allowed === false) return null;
  } else if (retry !== "true" || status < 400) {
    return null;
  }
  const after = response.headers.get("Retry-After") ?? "";
  if (!/^\d+$/.test(after)) {
    if (retry !== null && status >= 500) return null;
    return status === 429 ? null : backoff;
  }
  if (Number(after) > 60) return null;
  return Number(after) * 1_000 * (1 + Math.random() / 10);
}

// Stop resending after 10 minutes.
const deadline = Date.now() + 600_000;
let response: Response | null = null;
for (let attempt = 0; ; attempt++) {
  response = await fetch(url, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
      "Idempotency-Key": idempotencyKey,
    },
    body,
    signal: AbortSignal.timeout(130_000),
  }).catch((error: Error) => {
    // No answer is a timeout or a failed connection; anything else is raised.
    if (error.name === "TimeoutError" || error.cause) return null;
    throw error;
  });
  const wait = await retryWait(response, attempt);
  if (wait === null || Date.now() + wait >= deadline) break;
  await response?.body?.cancel();
  await new Promise((resolve) => setTimeout(resolve, wait));
  if (Date.now() >= deadline) break;
}
if (!response) throw new Error("No answer within 10 minutes.");
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const { answers } = await response.json();
console.log(answers);
```

## Examples

Open an example in your workspace's Playground, edit the state and questions, then run it.

- [Support ticket: three question types](https://www.rightnowai.co/dashboard/playground?example=support-ticket)
- [Policy check](https://www.rightnowai.co/dashboard/playground?example=policy-check)
- [One yes or no question](https://www.rightnowai.co/dashboard/playground?example=one-question)

## How it runs

Playground sends the request through the console server using the workspace's hidden Playground identity. You do not paste an API key into the browser.

The draft leaves your browser when you run it.

## Limits

Your workspace's limits still apply. A `playground_daily_limit` refusal returns HTTP 429; wait for its `resets_at` value before trying again. See [Rate limits](https://www.rightnowai.co/docs/system-zero/rate-limits).

Billing is not open and the console takes no payment.

## Cold starts

A cold start is a model pool replacing its warm container after a crash, a recycle, a preemption or a deploy, or adding one under a burst. A cold start can outlast a client's retry window. Follow the retry flag and any `Retry-After` header; stop automatic retries when the flag is false.

## Next

[Leaked API keys](https://www.rightnowai.co/docs/system-zero/leaked-api-keys)
