> ## 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.

# Decisions

> allow, deny and escalate at the check, the generation and the session, and what each one carries.

<Note>
  Sandbox and protocol reference. Test checks and generations return deterministic decisions for
  fictional fixtures. Sessions and production evaluation described below are not available
  through the current Talent API.
  Start with the [runnable quickstart](/quickstart) and [sandbox support matrix](/sandbox).
</Note>

Every check, generation and session sample ends in a **decision**. A decision is never an error:
`deny` and `escalate` are `200` responses with a `decision` field. An error means the request could
not be evaluated at all. Handle the decisions in your product, not in your error handler.

## The three decisions

| Decision | Meaning | What you do |
| - | - | - |
| `allow` | The use is licensed, on conditions | Proceed and meet every [obligation](/concepts/obligations) |
| `deny` | The use is not licensed | Do not proceed. Show `reasons[].sentence`; `remedy` says what would change it |
| `escalate` | A person with authority must decide | Do not proceed yet. Wait for the outcome; an unanswered escalation becomes `deny` |

## At each stage

The same decisions apply at three stages. Each reason says which one in `stage`.

| Stage | Call | Decisions | What `escalate` means |
| - | - | - | - |
| `check` | [`POST /v1/checks`](/api-reference/checks/create-a-check) | `allow`, `deny`, `escalate` | A reviewer decides whether the use may start |
| `generation` | [`POST /v1/generations`](/api-reference/generations/create-a-generation) | `allow`, `deny`, `escalate` | The output is `held` until a reviewer releases or declines it |
| `session` | [`POST /v1/sessions`](/api-reference/sessions/create-a-session) and its samples | `allow`, `deny` | Never used: anything that would escalate is `deny` |

A live call has no time for a person to review it, so sessions never escalate. A license that holds
every output for release cannot start a session, and the session is denied with
`session_not_permitted`. Interactive products can ask checks to behave the same way with
`on_escalate: "deny"`. See [Escalation](/concepts/escalation).

## Check statuses

A check's `status` tracks what happened after its decision.

```mermaid theme={null}
stateDiagram-v2
    [*] --> allowed: allow
    [*] --> denied: deny
    [*] --> escalated: escalate
    escalated --> allowed: reviewer allows
    escalated --> denied: reviewer denies or it expires
    escalated --> cancelled: you cancel
    allowed --> consumed: every generation used
    allowed --> expired: authorization expires unused
    allowed --> cancelled: you cancel
```

| Status | Meaning |
| - | - |
| `checking` | Live mode only: evaluation has not finished, so there is no decision yet. `POST /v1/checks` returns `202` with `Retry-After`; retry with the same body and key |
| `allowed` | Decided `allow`; the authorization can be used |
| `denied` | Decided `deny`; or escalated, then declined (`review_declined`) or unanswered by `escalation.expires_at` (`hold_expired`) |
| `escalated` | Waiting for a person |
| `consumed` | Every allowed generation was used |
| `expired` | An allowed check's authorization expired unused. An escalation that expires ends `denied`, not `expired`. |
| `cancelled` | You cancelled it with [`POST /v1/checks/{check_id}/cancel`](/api-reference/checks/cancel-a-check) |

When an escalated check is decided, you receive [`check.resolved`](/api-reference/webhooks/check-resolved)
with the new `decision`. A declined escalation is `deny` with `review_declined`, and an expired one
is `deny` with `hold_expired`.

## The authorization

An `allow` carries an `authorization`: proof that the check allowed this use.

| Field | Meaning |
| - | - |
| `token` | A JWS with `typ` `tlp-auth+jwt`, signed by Reflections |
| `max_generations` | How many generations it covers |
| `generations_used` | How many have been submitted |
| `expires_at` | At most 15 minutes after the authorization was issued |

Without `reuse`, the authorization covers only the request that was checked. The check's
`request_digest` is a digest of the inputs, the use and the execution, and the token carries it, so
a different prompt, a new reference or another model needs a new check. Pass the token to the tool
or model provider that generates; it verifies the token offline against the JWKS. A generation past
`max_generations` fails with `409 authorization_consumed`. See
[Authorization token](/api-reference/formats/authorization-token) for the claims.

### Reuse

One check can cover many generations of the same use, talent, execution and end user, even when
their inputs differ, such as the lines of a text-to-speech script: send
`reuse: { "max_generations": 200, "ttl": "PT15M" }`, up to 1,000 generations and `PT15M`. Every
output is still checked at the generation, and generations under reuse omit `candidate_index`. A
change of use, talent, model or end user needs a new check. A revocation ends the reuse window at
once. See [Bind the request](/build/binding-the-request#reuse).

## Reasons

A `deny` or `escalate` carries `reasons[]`:

| Field | Meaning |
| - | - |
| `code` | A stable reason code, such as `channel_not_licensed` |
| `stage` | `check`, `generation` or `session` |
| `talent` | The talent the reason concerns |
| `term` | The term it rests on, as an address: `license:lic_…/terms.channels`, or `talent:tal_…` for a standing boundary |
| `sentence` | The reason in the license's own words. Show it to your user as written |

Every code, its stage and its usual remedy are in [Reason codes](/api-reference/reason-codes).

## Remedies

A `deny` that a change could fix carries a `remedy`:

| `remedy.type` | Meaning | What to do |
| - | - | - |
| `license_request` | No license covers this use | Request one for `remedy.talent` |
| `license_change` | A license exists but is narrower | Request a change to `remedy.license` |
| `disambiguate` | The talent could not be told apart | Ask your user who they meant, then check again with `talent` or `exclude_talent` |
| `retry_with_hold` | You passed `on_escalate: "deny"` and the use needs review | Check again with `on_escalate: "hold"` |

`remedy.suggested_scope` holds the scope that would allow the use, ready to send as
`requested_scope` on a [license request](/api-reference/license-requests/request-a-license). A
`deny` for `talent_prohibits` has no remedy: no license can change it.

<Note>
  A missing license is a `deny` with a `license_request` remedy, never an `escalate`. A request for a
  license is a business negotiation, and a generation cannot be held open while it happens. Escalation
  is for a use a license covers when a person must decide, or when the check could not finish.
</Note>

## Several talent

When a request invokes more than one talent, each entry in `talent[]` carries its own `decision`,
`license`, `license_revision` and `terms_digest`. The check's `decision` is the most restrictive of
them: `deny` over `escalate`, `escalate` over `allow`. See [Multi-talent](/build/multi-talent).

## Receipts

`allow` and `deny` decisions carry a `receipt`, a signed record of the decision. An escalated check
receives its receipt when it is decided. See [Provenance](/concepts/provenance).

## Example: a channel the license does not cover

Ridgeline's license covers social media and its website. A check that adds TV is denied:

```json theme={null}
{
  "id": "chk_8v1m3n7p4r6s9t0wxyhk5zba2q",
  "object": "check",
  "status": "denied",
  "decision": "deny",
  "reasons": [
    {
      "code": "channel_not_licensed",
      "stage": "check",
      "talent": "tal_9t0wxyhk5zba2q8v1m3n7p4r6s",
      "term": "license:lic_k5zba2q8v1m3n7p4r6s9t0wxyh/terms.channels",
      "sentence": "This license covers social media and the brand's own website."
    }
  ],
  "authorization": null,
  "remedy": {
    "type": "license_change",
    "license": "lic_k5zba2q8v1m3n7p4r6s9t0wxyh",
    "talent": "tal_9t0wxyhk5zba2q8v1m3n7p4r6s",
    "suggested_scope": { "channels": ["social", "web", "tv"] }
  },
  "receipt": "rcpt_5zba2q8v1m3n7p4r6s9t0wxyhk"
}
```

Northlight shows the sentence to its user and offers to ask for the change. The request names the
denied check, so Maya Okafor sees the use that prompted it:

<CodeGroup>
  ```bash cURL theme={null}
  curl https://app.reflections.ai/v1/license_requests \
    -H "Authorization: Bearer rk_test_…" \
    -H "Reflections-Version: 2026-10-15" \
    -H "Reflections-Account: acct_2q8v1m3n7p4r6s9t0wxyhk5zba" \
    -H "Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{
      "kind": "change",
      "talent": "tal_9t0wxyhk5zba2q8v1m3n7p4r6s",
      "license": "lic_k5zba2q8v1m3n7p4r6s9t0wxyh",
      "check": "chk_8v1m3n7p4r6s9t0wxyhk5zba2q",
      "requested_scope": { "channels": ["social", "web", "tv"] },
      "note": "We would like to run the sunrise image in a 30-second TV spot."
    }'
  ```

  ```typescript TypeScript theme={null}
  const request = await fetch("https://app.reflections.ai/v1/license_requests", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.REFLECTIONS_KEY}`,
      "Reflections-Version": "2026-10-15",
      "Reflections-Account": "acct_2q8v1m3n7p4r6s9t0wxyhk5zba",
      "Idempotency-Key": crypto.randomUUID(),
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      kind: "change",
      talent: check.remedy.talent,
      license: check.remedy.license,
      check: check.id,
      requested_scope: check.remedy.suggested_scope,
      note: "We would like to run the sunrise image in a 30-second TV spot.",
    }),
  }).then((r) => r.json());
  ```

  ```python Python theme={null}
  import os, uuid, requests

  request = requests.post(
      "https://app.reflections.ai/v1/license_requests",
      headers={
          "Authorization": f"Bearer {os.environ['REFLECTIONS_KEY']}",
          "Reflections-Version": "2026-10-15",
          "Reflections-Account": "acct_2q8v1m3n7p4r6s9t0wxyhk5zba",
          "Idempotency-Key": str(uuid.uuid4()),
      },
      json={
          "kind": "change",
          "talent": check["remedy"]["talent"],
          "license": check["remedy"]["license"],
          "check": check["id"],
          "requested_scope": check["remedy"]["suggested_scope"],
          "note": "We would like to run the sunrise image in a 30-second TV spot.",
      },
  ).json()
  ```
</CodeGroup>

The request is `sent`. If Maya Okafor signs, you receive `license_request.signed` and then
`license.revision_pending`. Accept the revision at the license's `acceptance_url`; when you
receive `license.revised`, it is in force and you can check again.

## Related

<Columns cols={2}>
  <Card title="Obligations" icon="list-check" href="/concepts/obligations">
    What an `allow` requires of you.
  </Card>

  <Card title="Escalation" icon="user-clock" href="/concepts/escalation">
    Who reviews, and how long it can take.
  </Card>

  <Card title="Reason codes" icon="book" href="/api-reference/reason-codes">
    Every reason, its stage and its remedy.
  </Card>

  <Card title="Decision semantics" icon="scale-balanced" href="/standard/decision-semantics">
    The normative definition in the Standard.
  </Card>
</Columns>


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