# Decision records

[RightNow AI](https://www.rightnowai.co/) / [RightNow System Zero](https://www.rightnowai.co/docs/system-zero) / [Decision records](https://www.rightnowai.co/docs/system-zero/decision-records)

Updated 2026-11-07

Read a stored decision, or its trace, with the API key that created it.

**TypeScript (lookup.ts)**

```typescript
const key = process.env.SYSTEM_ZERO_API_KEY;
const id = process.env.DECISION_ID;
if (!key || !id) {
  throw new Error("Set SYSTEM_ZERO_API_KEY and DECISION_ID first.");
}

// Use the API key that created the decision.
const headers = { Authorization: `Bearer ${key}` };
const url = `https://api.rightnowai.co/v1/system-zero/decisions/${id}`;

// The stored decision, then its trace.
for (const address of [url, `${url}/trace`]) {
  const response = await fetch(address, { headers });
  const body = await response.json();
  if (!response.ok) {
    throw new Error(JSON.stringify(body));
  }
  console.log(JSON.stringify(body, null, 2));
}
```

**Python (lookup.py)**

```python
import json
import os
import urllib.error
import urllib.request

key = os.environ["SYSTEM_ZERO_API_KEY"]
decision_id = os.environ["DECISION_ID"]
base = "https://api.rightnowai.co/v1/system-zero/decisions"
url = f"{base}/{decision_id}"

# The stored decision, then its trace, with the key that created it.
for address in (url, f"{url}/trace"):
    request = urllib.request.Request(
        address, headers={"Authorization": f"Bearer {key}"},
    )
    try:
        with urllib.request.urlopen(request) as response:
            print(json.dumps(json.load(response), indent=2))
    except urllib.error.HTTPError as error:
        print(error.read().decode("utf-8"))
        raise SystemExit(1) from None
```

**Manual HTTP (lookup.sh)**

```shell
# Read a stored decision with the API key that created it.
# Use the id of a decide reply sent with an Idempotency-Key.
: "${SYSTEM_ZERO_API_KEY:?Set SYSTEM_ZERO_API_KEY first}"
: "${DECISION_ID:?Set DECISION_ID to the decision id}"
url="https://api.rightnowai.co/v1/system-zero/decisions/$DECISION_ID"

# The stored decision, exactly as it was returned.
curl --silent --show-error --fail-with-body "$url" \
  --header "Authorization: Bearer $SYSTEM_ZERO_API_KEY"
printf '\n'

# Its trace: metadata only, without answer content.
curl --silent --show-error --fail-with-body "$url/trace" \
  --header "Authorization: Bearer $SYSTEM_ZERO_API_KEY"
printf '\n'
```

## Look up a decision

- **`GET /v1/system-zero/decisions/{id}`**: The stored decision, byte for byte.
- **`GET /v1/system-zero/decisions/{id}/trace`**: Metadata about the same decision, without answer content.

Send the active API key that created the decision. Another key in the same workspace cannot read it.

Use the `id` of a decide reply: `szd_` followed by 32 lowercase hexadecimal characters. Any other id returns `404 decision_not_found`.

A found decision returns its stored status and reply, exactly as they were first returned. A legacy reply keeps its stored answer shape. Do not use legacy `confidence` as decision probability.

`HEAD` returns the same status and headers without a body. Neither read takes a request body or query parameters. There is no list endpoint.

## Read the trace

A trace is a JSON object whose `object` is `system_zero.decision_trace`. It reads these fields from the stored decision.

- **`id`**: The decision's id.
- **`created`**: When the decision was made, in Unix seconds.
- **`model`**: The model named in the stored reply.
- **`model_version`**: The stored reply's `served_by`.
- **`route`**: The stored reply's `context.route`.
- **`usage_units`**: Seven usage counts, each a whole number or null.
- **`refusal_codes`**: The distinct question refusal codes in the decision, sorted.
- **`refusal_code`**: The one refusal code when there is exactly one, else null.

These fields are null in this release: `request_id`, `render_id`, `attachments`, `timings.queue_ms`, `timings.node_ms`, `timings.settle_ms`, `usage_units.cached_media_tokens`, `idempotency`, `stream_id` and `state_version`.

A missing usage count is null, never zero. The trace holds no question names, answer values, probabilities, messages or request content.

**trace.json**

```json
{
  "object": "system_zero.decision_trace",
  "id": "szd_7c41f0a9b2d84e6c9a1f3b5d7e9c0a2b",
  "request_id": null,
  "created": 1791100800,
  "model": "system-zero-2.0.0",
  "model_version": "system-zero-2.0.0",
  "render_id": null,
  "route": "direct",
  "attachments": null,
  "timings": {
    "queue_ms": null,
    "node_ms": null,
    "settle_ms": null
  },
  "usage_units": {
    "input_tokens": 214,
    "cached_input_tokens": 0,
    "media_tokens": 0,
    "cached_media_tokens": null,
    "long_input_tokens": 0,
    "media_items": 0,
    "thought_tokens": 0
  },
  "refusal_code": null,
  "refusal_codes": [],
  "idempotency": null,
  "stream_id": null,
  "state_version": null
}
```

## Availability and retention

Send decide with an `Idempotency-Key`. A lookup finds only decisions stored under an `Idempotency-Key`; see [Idempotency](https://www.rightnowai.co/docs/system-zero/idempotency).

Unkeyed decisions, older decisions without a lookup index and replies over the storage limits are unavailable.

A stored decision is kept for up to 24 hours, and its answer or index can be removed earlier. Reading it never extends that time.

A lookup runs no new decision and is not charged. It still counts toward your requests a minute. The lookup index keeps no copy of request or answer content.

A decision id is not a permanent archive. Keep the answers you need in your own storage.

## Errors and headers

Both reads can return these refusals.

- **`400 invalid_trace_header`**: X-Client-Request-Id is blank, not printable ASCII or over 512 characters.
- **`401 invalid_api_key`**: The key is missing, malformed or unknown.
- **`403 auth_error`**: The key is revoked.
- **`403 account_suspended`**: The workspace is suspended or waiting for access.
- **`404 decision_not_found`**: The id is invalid, or no stored decision matches it for this API key.
- **`405 method_not_allowed`**: The method is not GET, HEAD or OPTIONS.
- **`429 rate_limit_exceeded`**: Too many requests from this key, workspace or address.
- **`503 service_unavailable`**: A store the lookup reads cannot answer right now.
- **`503 limiter_unavailable`**: Rate limiting cannot answer right now.

**decision-not-found.json**

```json
{
  "error": {
    "message": "The requested decision is not available.",
    "type": "not_found_error",
    "param": null,
    "code": "decision_not_found",
    "retryable": false
  },
  "request_id": "request-id-from-response"
}
```

`decision_not_found` has `error.retryable`: `false`. It does not say whether a decision was never stored, expired, was removed or belongs to another key.

Retry only when `error.retryable` is true. Wait for `Retry-After-Ms`, in milliseconds, or `Retry-After`, in seconds; prefer `Retry-After-Ms`. See [Errors](https://www.rightnowai.co/docs/system-zero/errors).

Lookups use the same requests a minute as decide. Their `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` headers report them; see [Rate limits](https://www.rightnowai.co/docs/system-zero/rate-limits).

`X-Request-Id` identifies the lookup, not the original decision. A valid `X-Client-Request-Id` you send is echoed back.

Lookups never set `X-RightNow-Idempotent-Replay`. Every response sends `Cache-Control: no-store`. A `405` lists `Allow: GET, HEAD, OPTIONS`.

## Next

[Errors](https://www.rightnowai.co/docs/system-zero/errors)
