> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reflections.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> The OpenAPI document at /api-reference/openapi.yaml defines the target contract. Its x-reflections-availability field and each operation's availability notice identify supported test and live forms. Do not infer implementation from a schema or code sample. Live keys support account details, reads of the licenses the calling account holds as licensee or issued as talent (GET /v1/licenses?role=issuer), and GET /v1/generations?license=… for the state of outputs under such a license (summaries only: no prompt, reference, bytes or metadata). Live image checks, submissions and receipts are built but not activated: until activation, a live POST /v1/checks or /v1/generations returns 403 live_not_activated, which must not be retried. Discovery and JWKS are at the origin root (https://app.reflections.ai/.well-known/...), not under /v1.
> Three rules hold everywhere: a decision is never an error (deny and escalate are 200 responses, and a live check still evaluating is a 202 with status checking); generate nothing for a named talent without an allow; deliver nothing until the generation is allow.
> The sandbox accepts personal rk_test_ keys. Persist a separate Idempotency-Key and body for each logical /v1 POST; retry an unknown outcome with the same key and body. OAuth and Talent API MCP remain planned.

# Failure modes

> What to do when Reflections, an evaluator, a session or your webhook endpoint fails: fail closed for named talent, and proceed only on requests that name and match no one.

<Note>
  Production protocol reference. The current sandbox has deterministic test decisions. The name
  index, revocation feed, session stream and webhook delivery discussed below are not available.
  Start with the [runnable quickstart](/quickstart) and [sandbox support matrix](/sandbox).
</Note>

Two rules decide what your platform does when something fails:

1. **Fail closed for named talent.** If a request invokes a talent and you cannot get an `allow`, do
   not generate, and do not deliver an output whose generation is not `allow`.
2. **Proceed only on requests that name and match no one.** While Reflections cannot be reached, you
   may generate and deliver a request that names no talent in `talent`, matches no entry in your
   current [name index](/build/resolving-talent#match-locally-with-the-name-index), and carries no
   reference or voice sample.

An outage of Reflections therefore stops generation of named talent on your platform. That is a
deliberate cost: the alternative would turn every outage into a window for unlicensed likeness,
which the talent could neither see nor undo. The proposed production availability target for
checks is 99.95% per month; it is not a confirmed service commitment or measured sandbox result.
See [status.reflections.ai](https://status.reflections.ai) for reported service status.

## At a glance

| Situation | What you do |
| - | - |
| A check times out, or returns `500` or `503`, for a request that names a talent, matches the name index or carries a reference or voice sample | Do not generate. Retry with the same `Idempotency-Key` |
| You hold an unexpired authorization | You may still use it, within `max_generations` and before `expires_at` |
| A generation cannot be submitted | Do not deliver. Keep the output and submit it when Reflections returns |
| A check fails for any other request | You may generate and deliver it. Nothing is sent later |
| An evaluator is down | Checks return `escalate` with `evaluation_unavailable` |
| Session renewal cannot reach Reflections | Continue until the current token expires, never past it |
| Session renewal fails with `session_not_active` | Stop output at once |
| Your webhook endpoint is down | Read the revocation feed and the events list when it recovers |
| `429 rate_limited` | Wait for `Retry-After`. This is not an outage; still do not generate without an `allow` |

## Retries

Retry an unknown outcome, such as a timeout, with the same `Idempotency-Key`, never a new one.
Keys are kept 24 hours: the same key and body return the original response, so a check that was
decided before the connection dropped is not decided twice. A `409 idempotency_key_in_use` means the
first request is still running; wait briefly and retry with the same key.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

  // Returns the check, or null if Reflections could not be reached. Null means: do not generate.
  async function checkUse(body: object) {
    const key = crypto.randomUUID(); // the same key on every retry
    for (let attempt = 0; attempt < 3; attempt++) {
      let res: Response;
      try {
        res = await fetch("https://app.reflections.ai/v1/checks", {
          method: "POST",
          headers: {
            Authorization: `Bearer ${process.env.REFLECTIONS_KEY}`,
            "Reflections-Version": "2026-10-15",
            "Idempotency-Key": key,
            "Content-Type": "application/json",
          },
          body: JSON.stringify(body),
          signal: AbortSignal.timeout(5000),
        });
      } catch {
        await sleep(2 ** attempt * 1000); // timeout or network error: the outcome is unknown
        continue;
      }
      if (res.ok) {
        const result = await res.json();
        // A live check still evaluating returns 202 "checking": not final, retry with the same key.
        if (res.status === 202 && result.object === "check" && result.status === "checking") {
          await sleep(retryAfterMs(res) ?? 1000);
          continue;
        }
        return result;
      }
      const problem = await res.json().catch(() => ({})); // a proxy's 502 may not be JSON
      if (res.status === 429 || res.status >= 500 || problem.code === "idempotency_key_in_use") {
        await sleep(retryAfterMs(res) ?? 2 ** attempt * 1000);
        continue;
      }
      throw new Error(`Check rejected with ${res.status} ${problem.code}`); // a fault in the request
    }
    return null;
  }

  // Retry-After from Reflections is in seconds; a proxy in front of it may send an HTTP date.
  function retryAfterMs(res: Response): number | undefined {
    const value = res.headers.get("Retry-After");
    if (!value) return undefined;
    const seconds = Number(value);
    if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);
    const date = Date.parse(value);
    return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now());
  }
  ```

  ```python Python theme={null}
  import os, time, uuid, requests
  from email.utils import parsedate_to_datetime

  def check_use(body):
      """Returns the check, or None if Reflections could not be reached. None means: do not generate."""
      key = str(uuid.uuid4())  # the same key on every retry
      for attempt in range(3):
          try:
              res = requests.post(
                  "https://app.reflections.ai/v1/checks",
                  headers={
                      "Authorization": f"Bearer {os.environ['REFLECTIONS_KEY']}",
                      "Reflections-Version": "2026-10-15",
                      "Idempotency-Key": key,
                  },
                  json=body,
                  timeout=5,
              )
          except requests.RequestException:
              time.sleep(2 ** attempt)  # timeout or network error: the outcome is unknown
              continue
          if res.ok:
              result = res.json()
              # A live check still evaluating returns 202 "checking": not final, retry with the same key.
              if res.status_code == 202 and result.get("object") == "check" and result.get("status") == "checking":
                  wait = retry_after(res)
                  time.sleep(1 if wait is None else wait)
                  continue
              return result
          try:
              code = res.json().get("code")
          except ValueError:  # a proxy's 502 may not be JSON
              code = None
          if res.status_code == 429 or res.status_code >= 500 or code == "idempotency_key_in_use":
              wait = retry_after(res)
              time.sleep(2 ** attempt if wait is None else wait)
              continue
          res.raise_for_status()  # any other 4xx is a fault in the request
      return None

  def retry_after(res):
      """Seconds to wait, or None. Reflections sends seconds; a proxy may send an HTTP date."""
      value = res.headers.get("Retry-After")
      if not value:
          return None
      try:
          return max(0.0, float(value))
      except ValueError:
          pass
      try:
          return max(0.0, parsedate_to_datetime(value).timestamp() - time.time())
      except (TypeError, ValueError):
          return None
  ```
</CodeGroup>

## Outputs during an outage

An output of a named talent is not delivered until its generation is `allow`. Keep it, and submit
it with the same `Idempotency-Key` when Reflections returns. If the generation is then refused with
`409 check_expired`, run a new check before you generate again.

A request that names no talent, matches no entry in your name index, and carries no reference or
voice sample may be generated and delivered while Reflections is unreachable. Match only against an
index whose `expires_at` has not passed: with no current index, every request waits. An indirect
reference, such as "the runner from Ridgeline's spring campaign", is resolved only by a check, so
during an outage it goes unresolved.

## Evaluator outages

Sometimes Reflections is reachable but cannot finish a check, for example when an evaluator times
out or returns something it cannot read.

* **At the check**, the decision is `escalate` with `escalation.reason: "evaluation_unavailable"`. A
  person decides, and you receive `check.resolved`. With `on_escalate: "deny"`, you get `deny` with
  `review_required` instead.
* **At the generation**, a rule the talent wrote that could not be read is delivered and recorded as
  unchecked: its finding has `result: "unchecked"` and its address is in `unchecked_terms`. A
  protected attribute, such as `identity.samePerson`, that could not be confirmed denies the output.

## Reuse windows

A check with `reuse: { "max_generations": 200, "ttl": "PT15M" }` lets one decision cover many
generations of the same use for the same end user, up to 1,000 generations and 15 minutes. While it
lasts, those generations need no new check, including during a short outage. A revocation ends it at
once.

## Sessions

A session token lives 5 minutes and is renewed every 60 seconds.

* If a renewal fails with `409 session_not_active`, the license was revoked, suspended or ended, or
  the session was terminated. Stop output at once.
* If a renewal cannot reach Reflections, continue until the current token's `expires_at`, never past
  it, and keep retrying.
* If the session stream drops, reconnect and read the session to learn its state. A missed
  `session.terminated` is also reflected on the session and sent as a webhook.

## Caching and the revocation feed

If you keep decisions to act on later, such as authorizations, reuse windows or session tokens,
read the revocation feed, [`GET /v1/revocations?after=rvk_…`](/api-reference/revocations/list-revocations),
at least every 60 seconds. Webhooks are the fast path; the feed is the source of truth, and it
returns everything after the last revocation you processed. Webhook deliveries are retried for 72
hours, and events are kept 30 days, so an endpoint that was down can catch up from
[`GET /v1/events`](/api-reference/events/list-events).

## Testing failure paths

In test mode, put `rfl_test_unavailable` in a check's text to get `escalate` with
`evaluation_unavailable`, `rfl_test_unchecked` in a generation's `transcript` to get `allow` with a
rule in `unchecked_terms`. See [Sandbox](/sandbox).

## Related

<Columns cols={2}>
  <Card title="Caching and outages" icon="code" href="/build/caching-and-outages">
    Reuse windows, the revocation feed and retries.
  </Card>

  <Card title="Idempotency" icon="key" href="/api-reference/idempotency">
    Keys, replay and the three outcomes.
  </Card>

  <Card title="Revocation" icon="ban" href="/concepts/revocation">
    What a revocation stops, and how fast.
  </Card>

  <Card title="Monitoring" icon="chart-line" href="/production/monitoring">
    Request IDs, the status page and alerts.
  </Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.