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

# Bind the request

> Send the request your model actually receives, compute its request_digest, and know when a change needs a new check.

<Note>
  Sandbox and protocol reference. Request digests and test authorization tokens are available. A
  test token covers fictional fixtures only; it does not authorize use of a real talent or prove
  provider enforcement.
  Start with the [runnable quickstart](/quickstart) and [sandbox support matrix](/sandbox).
</Note>

A check decides one request: the exact inputs, the use, and the model that runs them. Without
`reuse`, its authorization covers that request and nothing else, bound by `request_digest`. If you
check your user's words but send the model something different, the decision does not cover what
you generated. With `reuse`, the authorization also covers later requests whose inputs differ, as
[Reuse](#reuse) explains.

## Send what the model receives

Most products change a request before the model sees it. They add system text, expand the prompt,
attach reference images, or prepend conversation history. Each change can add a person, a scene or
a claim the user never typed. A prompt expansion that adds "holding a cold beer" breaks a rule such
as "Do not show alcohol or tobacco", even though the user only asked for "Maya Okafor on a trail".

So the check takes the effective request, in order, with a `role` for each part:

| `role` | What it is |
| - | - |
| `prompt` | The user's words |
| `system` | Instructions you add |
| `rewritten_prompt` | Your expansion of the prompt, when the model receives that instead |
| `negative_prompt` | What the model is told to avoid |
| `context` | Conversation history the model receives |
| `script` | Words to be spoken |
| `reference`, `voice_sample`, `mask` | Media the model receives |

Send `prompt` even when the model receives only your `rewritten_prompt`. The check evaluates what
the model receives, and a reviewer of an escalation sees what your user asked for.

The planned protocol accepts a `file` ID from [`POST /v1/files`](/api-reference/files/upload-a-file),
or a fetchable `url` with its `sha256` and `media_type`.

<Note>
  The file-based examples below are planned protocol examples and do not run in the sandbox.
  Test mode rejects file IDs and never fetches URLs. It accepts media `sha256` and `media_type`
  as digest metadata only; this does not resolve talent from media or evaluate its content.
  Use the [quickstart](/quickstart) for a supported text-input check.
</Note>

<CodeGroup>
  ```bash cURL theme={null}
  curl https://app.reflections.ai/v1/checks \
    -H "Authorization: Bearer rk_test_…" \
    -H "Reflections-Version: 2026-10-15" \
    -H "Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{
      "inputs": [
        { "role": "system", "text": "Photoreal, natural light, no text in the image." },
        { "role": "prompt", "text": "Maya Okafor holding our new trail shoe on a rocky ridge at sunrise" },
        { "role": "rewritten_prompt", "text": "Photoreal portrait of Maya Okafor, golden-hour light, holding a grey trail running shoe, rocky ridge, wide shot" },
        { "role": "reference", "file": "file_7h3k9m2p4q6r8s1t3v5w7x9yza" }
      ],
      "use": {
        "modality": "image", "kind": "depiction", "purpose": "advertising",
        "commercial": true, "channels": ["social", "web"], "territories": ["US", "CA"]
      },
      "end_user": { "id": "usr_48213" },
      "execution": { "provider": "example-image-provider", "model": "image-model-3", "candidates": 4 }
    }'
  ```

  ```typescript TypeScript theme={null}
  const check = await fetch("https://app.reflections.ai/v1/checks", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.REFLECTIONS_KEY}`,
      "Reflections-Version": "2026-10-15",
      "Idempotency-Key": crypto.randomUUID(),
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      inputs: [
        { role: "system", text: "Photoreal, natural light, no text in the image." },
        { role: "prompt", text: "Maya Okafor holding our new trail shoe on a rocky ridge at sunrise" },
        { role: "rewritten_prompt", text: "Photoreal portrait of Maya Okafor, golden-hour light, holding a grey trail running shoe, rocky ridge, wide shot" },
        { role: "reference", file: "file_7h3k9m2p4q6r8s1t3v5w7x9yza" },
      ],
      use: {
        modality: "image", kind: "depiction", purpose: "advertising",
        commercial: true, channels: ["social", "web"], territories: ["US", "CA"],
      },
      end_user: { id: "usr_48213" },
      execution: { provider: "example-image-provider", model: "image-model-3", candidates: 4 },
    }),
  }).then((r) => r.json());
  ```

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

  check = requests.post(
      "https://app.reflections.ai/v1/checks",
      headers={
          "Authorization": f"Bearer {os.environ['REFLECTIONS_KEY']}",
          "Reflections-Version": "2026-10-15",
          "Idempotency-Key": str(uuid.uuid4()),
      },
      json={
          "inputs": [
              {"role": "system", "text": "Photoreal, natural light, no text in the image."},
              {"role": "prompt", "text": "Maya Okafor holding our new trail shoe on a rocky ridge at sunrise"},
              {"role": "rewritten_prompt", "text": "Photoreal portrait of Maya Okafor, golden-hour light, holding a grey trail running shoe, rocky ridge, wide shot"},
              {"role": "reference", "file": "file_7h3k9m2p4q6r8s1t3v5w7x9yza"},
          ],
          "use": {
              "modality": "image", "kind": "depiction", "purpose": "advertising",
              "commercial": True, "channels": ["social", "web"], "territories": ["US", "CA"],
          },
          "end_user": {"id": "usr_48213"},
          "execution": {"provider": "example-image-provider", "model": "image-model-3", "candidates": 4},
      },
  ).json()
  ```
</CodeGroup>

The check returns the digest of what it decided:

```json theme={null}
{
  "id": "chk_9zez5e71y8919791afn40vrm82",
  "object": "check",
  "decision": "allow",
  "request_digest": "sha256:2640a5beb8f3b4db0e46ef7be645699340d9dd19e16222d229bf123e024fab94",
  "authorization": {
    "token": "eyJhbGciOiJFUzI1NiIs…",
    "max_generations": 4,
    "generations_used": 0,
    "expires_at": "2026-10-17T18:15:00Z"
  }
}
```

The same value is in the authorization token as the `tlp.request_digest` claim.

## How `request_digest` is computed

The digest is `sha256:` followed by the lowercase hex SHA-256 of one JSON object with three keys,
`inputs`, `use` and `execution`, in `tlp-json-1` canonical form:

* `inputs` keeps the order you sent. A text input becomes `{ "role", "text" }`. A media input
  becomes `{ "role", "sha256", "media_type" }`, with the digest of the file's bytes.
* `use` is the use object as you sent it. Arrays keep their order.
* `execution` holds only `provider` and `model`.

`tlp-json-1` is UTF-8 JSON with object keys sorted by Unicode code point, no insignificant
whitespace, numbers in shortest round-trip form, and no duplicate keys.

For the check above, where the reference file's `sha256` is
`7c1e3a5b9d2f4e6a8c0b2d4f6e8a0c2e4b6d8f0a2c4e6b8d0f2a4c6e8b0d2f4a`, the canonical form is this one
line:

```text theme={null}
{"execution":{"model":"image-model-3","provider":"example-image-provider"},"inputs":[{"role":"system","text":"Photoreal, natural light, no text in the image."},{"role":"prompt","text":"Maya Okafor holding our new trail shoe on a rocky ridge at sunrise"},{"role":"rewritten_prompt","text":"Photoreal portrait of Maya Okafor, golden-hour light, holding a grey trail running shoe, rocky ridge, wide shot"},{"media_type":"image/png","role":"reference","sha256":"7c1e3a5b9d2f4e6a8c0b2d4f6e8a0c2e4b6d8f0a2c4e6b8d0f2a4c6e8b0d2f4a"}],"use":{"channels":["social","web"],"commercial":true,"kind":"depiction","modality":"image","purpose":"advertising","territories":["US","CA"]}}
```

and its digest is `sha256:2640a5beb8f3b4db0e46ef7be645699340d9dd19e16222d229bf123e024fab94`.
Both functions below produce it.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { createHash } from "node:crypto";

  const byCodePoint = (a: string, b: string) => {
    const x = [...a], y = [...b];
    for (let i = 0; i < Math.min(x.length, y.length); i++) {
      const d = x[i].codePointAt(0)! - y[i].codePointAt(0)!;
      if (d !== 0) return d;
    }
    return x.length - y.length;
  };

  function canonical(value: unknown): string {
    if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
    if (value !== null && typeof value === "object") {
      const obj = value as Record<string, unknown>;
      const keys = Object.keys(obj).sort(byCodePoint);
      return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(",")}}`;
    }
    return JSON.stringify(value);
  }

  type Part = { role: string; text?: string; sha256?: string; media_type?: string };

  export function requestDigest(inputs: Part[], use: object, execution: { provider: string; model: string }) {
    const body = {
      inputs: inputs.map((p) =>
        p.text !== undefined
          ? { role: p.role, text: p.text }
          : { role: p.role, sha256: p.sha256, media_type: p.media_type },
      ),
      use,
      execution: { provider: execution.provider, model: execution.model },
    };
    return "sha256:" + createHash("sha256").update(canonical(body), "utf8").digest("hex");
  }
  ```

  ```python Python theme={null}
  import hashlib, json

  def canonical(value) -> bytes:
      # sort_keys sorts by code point; separators remove whitespace.
      return json.dumps(value, ensure_ascii=False, separators=(",", ":"), sort_keys=True).encode("utf-8")

  def request_digest(inputs, use, execution) -> str:
      parts = [
          {"role": p["role"], "text": p["text"]}
          if "text" in p
          else {"role": p["role"], "sha256": p["sha256"], "media_type": p["media_type"]}
          for p in inputs
      ]
      body = {
          "inputs": parts,
          "use": use,
          "execution": {"provider": execution["provider"], "model": execution["model"]},
      }
      return "sha256:" + hashlib.sha256(canonical(body)).hexdigest()
  ```
</CodeGroup>

For a `file` input, use the `sha256` and `media_type` of the [file object](/api-reference/files/retrieve-a-file).
Compute the digest in your pipeline just before you call the model, and compare it with
`check.request_digest`. Without `reuse`, a mismatch means the model is about to receive a request
that was not checked.

Compute it over the inputs as checked. Adding the check's own `generation_instructions` to what the
model receives is the one change the check expects, and it does not change the digest.

## When the request changes

Without `reuse`, anything inside the digest is part of the decision. Change it and you need a new
check. With `reuse`, only the use, the talent, the execution and the end user are fixed.

| Change after the check | New check? | New check under `reuse`? |
| - | - | - |
| The user edits the prompt | Yes | No. The output is still checked. |
| You rewrite the prompt again or change the system text | Yes | No. The output is still checked. |
| You add, replace or remove a reference, mask or voice sample | Yes | No. The output is still checked. |
| The request invokes another talent | Yes | Yes |
| You switch the model or the provider | Yes. Tools compare `tlp.execution.model` with the model about to run. | Yes |
| The use changes: purpose, channel, territory or `commercial` | Yes | Yes |
| The end user changes | Yes | Yes |
| You add the check's `generation_instructions` | No | No |
| You change the seed, size or another sampling parameter | No | No |
| You run more generations, up to `authorization.max_generations` | No. Past it, the generation fails with `authorization_consumed`. | No, until `reuse.max_generations` or `reuse.ttl` runs out |

A new check needs a new `Idempotency-Key`. Reusing a key with a changed body returns
`422 idempotency_key_reused`.

### Reuse

`request_digest` and `reuse` answer different questions. `request_digest` records the one request
the check decided. `reuse` says which later generations the same authorization may cover.

* Send `reuse: { "max_generations": 200, "ttl": "PT15M" }` on the check, up to 1,000 generations
  and `PT15M`.
* Until either limit runs out, the authorization covers generations of the same use, talent,
  execution and end user, even when their inputs differ, such as each line of a text-to-speech
  script.
* `check.request_digest` and the token's `tlp.request_digest` stay the digest of the request the
  check decided. Do not compare later requests with them.
* Every output is still checked. Submit each one as a generation, as the `generation_check`
  obligation requires, and omit `candidate_index`: generations under reuse do not have one.
* A revocation ends the window at once.

See [Caching and outages](/build/caching-and-outages#reuse-windows) for when to use a window.

### Escalated requests

When an escalated check is allowed, its authorization covers exactly the request you escalated. If
your user edits the request while it waits, [cancel the check](/api-reference/checks/cancel-a-check)
and send a new one. The edited request may escalate again. See
[Handle escalations](/build/handling-escalations).

## What a generation tool checks

You pass `authorization.token` to the tool or provider that generates. Before it runs, the tool
verifies the signature against the JWKS, checks `aud`, `exp` and the model, and counts generations
against `tlp.max_generations`. A tool that receives the same inputs you checked can also recompute
the digest and compare it with `tlp.request_digest`. Under `reuse`, that comparison holds only for
the request the check decided. The
[authorization token format](/api-reference/formats/authorization-token) lists every claim.

<Note>
  In `prompt_handling.mode: "withheld"`, text never reaches Reflections and the digest covers text
  digests instead. See [Privacy modes](/build/privacy-modes).
</Note>

## Related

<Columns cols={2}>
  <Card title="Decisions" icon="shield-check" href="/concepts/decisions">
    What `allow`, `deny` and `escalate` cover.
  </Card>

  <Card title="Authorization token" icon="key" href="/api-reference/formats/authorization-token">
    Claims and verification of the check token.
  </Card>

  <Card title="Model providers" icon="server" href="/guides/model-providers">
    Verifying tokens at the inference API.
  </Card>

  <Card title="Privacy modes" icon="eye-slash" href="/build/privacy-modes">
    Full, redacted and withheld prompts.
  </Card>
</Columns>


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