# Provenance

[RightNow AI](https://www.rightnowai.co/) / [RightNow System Zero](https://www.rightnowai.co/docs/system-zero) / [Provenance](https://www.rightnowai.co/docs/system-zero/provenance)

Updated 2026-11-07

A successful RightNow System Zero decision reply can include `provenance`, metadata about the execution that produced the reply.

**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": "system-zero",
  "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": "system-zero",
        "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": "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.
# 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
```

## Read provenance

These Python and TypeScript programs send one decision over HTTP with no `Idempotency-Key` and print the `X-Request-Id` header as the current request. When the reply includes `provenance`, they print `provenance.request_id` as the first request, then the keyed replay state.

**provenance.py**

```python
import os
import requests

response = requests.post(
    "https://api.rightnowai.co/v1/system-zero/decide",
    headers={"Authorization": f"Bearer {os.environ['SYSTEM_ZERO_API_KEY']}"},
    json={
        "model": "system-zero",
        "state": "The blender I ordered arrived with a cracked jar.",
        "questions": {
            "damaged": {
                "type": "boolean",
                "instructions": "Did the item arrive damaged?"
            }
        }
    },
    timeout=130,
)
print("Current request", response.headers.get("X-Request-Id"))
reply = response.json()
provenance = reply.get("provenance") if response.ok else None
if provenance is None:
    print(reply)
else:
    print("First request", provenance["request_id"])
    print("Keyed replay", provenance["keyed_replay"])
    print(provenance)
```

**provenance.ts**

```typescript
const url = "https://api.rightnowai.co/v1/system-zero/decide";
const response = await fetch(url, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SYSTEM_ZERO_API_KEY}`,
    "Content-Type": "application/json",
  },
  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?"
      }
    }
  }),
});
console.log("Current request", response.headers.get("X-Request-Id"));
const reply = await response.json();
const provenance = response.ok ? reply.provenance : undefined;
if (provenance === undefined) {
  console.log(JSON.stringify(reply, null, 2));
} else {
  console.log("First request", provenance.request_id);
  console.log("Keyed replay", provenance.keyed_replay);
  console.log(JSON.stringify(provenance, null, 2));
}
```

The field is optional. A stored reply written before provenance keeps its original body and has none. Read `provenance` only when it is present, and never fill a missing field.

## Fields

A `provenance` object has exactly these fields.

- **`decision_id`**: The decision ID, equal to the reply's `id`.
- **`request_id`**: The ID of the request that produced the decision.
- **`created`**: The decision's creation time in Unix seconds, equal to the reply's `created`.
- **`model`**: The model string you requested, including an alias when you used one.
- **`resolved_release`**: The concrete release, equal to the reply's `model`.
- **`render_id`**: The serving render identifier.
- **`numeric_fingerprint`**: The serving fingerprint, a string of 64 lowercase hexadecimal characters.
- **`calibration_version`**: The exact calibration version of each answered question, by question name. A question refusal has no entry.
- **`route`**: `direct` or `long`, the context route of the reply.
- **`served_by`**: The reply's `served_by` value.
- **`attachment_classes`**: The sorted, unique attachment kinds in the request: `audio`, `document`, `image` or `video`. Empty for a request without attachments.
- **`keyed_replay`**: `false` on a fresh reply; `true` when the API returns a stored provenance reply for the same `Idempotency-Key`.

A calibration version alone does not establish calibration. Read each answer's `calibrated` flag. Fingerprints and serving identifiers are strings, not quality scores or signatures.

Attachment classes contain no names, source URLs or content. A listed class still follows its existing [input availability](https://www.rightnowai.co/docs/system-zero/attachments); this metadata grants no additional input support.

## Request IDs and replays

The HTTP `X-Request-Id` header identifies the current request. It can differ from `provenance.request_id`, the first request that produced the decision.

A [stream](https://www.rightnowai.co/docs/system-zero/streaming) decision carries the same object at `reply.provenance`. The frame's `request_id` identifies the current request.

When the API returns a stored reply with `keyed_replay` set to `true`, it preserves the original decision ID, request ID, creation time and other provenance facts. Reusing the same key after a dropped connection does not guarantee the original reply.

## Reply size

Provenance counts toward the reply size bound, which is checked before the request is admitted, dispatched or billed. A request near that bound can return HTTP 413 `questions_too_large`. Shorten question names or choice labels, or send fewer questions, before you send a changed request.

## Playground and data

In [Playground](https://www.rightnowai.co/docs/system-zero/playground), open Details after a run, then Provenance. Request shows the current request ID; First request shows the request that produced the decision. Older stored replies can omit provenance. Playground then shows provenance as not recorded. The stored body remains byte for byte unchanged; no missing field is inferred. Response JSON and its copied text keep the returned provenance values.

Provenance stays with the existing keyed reply and follows its retention and eviction rules. There is no separate provenance store. Decision IDs, request IDs and question names can link to your own records. Read [Idempotency](https://www.rightnowai.co/docs/system-zero/idempotency) and the [data handling terms](https://www.rightnowai.co/privacy-policy#api).

Execution evidence is not returned.

## Next

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