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

# Submit outputs

> Upload each output, bind it to its check and candidate, read the findings, and deliver only on allow.

<Note>
  Sandbox and protocol reference. Send a SHA-256 digest, optionally with inline base64 data. The
  sandbox accepts at most 7 MiB of decoded bytes in a 10 MiB JSON request body. It does not
  store files, fetch URLs or evaluate image, audio or video content.
  Start with the [runnable quickstart](/quickstart) and [sandbox support matrix](/sandbox).
</Note>

A check allows a request. A generation decides whether the output that request produced may be
delivered. Reflections binds the bytes to the check by their SHA-256 and checks the license's rules
on them, for the talent the check covered. Deliver nothing until the generation's decision is
`allow`.

## What to submit

The check's `generation_check` obligation says how much to submit:

| Obligation | Submit |
| - | - |
| `{ "type": "generation_check", "required": "all" }` | Every output, before anyone sees it |
| `{ "type": "generation_check", "required": "sampled", "sample_rate": 0.2 }` | At least that share of outputs |
| A check with `talent: []` | Nothing. It carries no obligations, and no talent is checked on its outputs. |

Submit the bytes you deliver. If you resize, re-encode or draw a label into an image, do it before
you submit, so the bytes Reflections checks are the bytes your user receives. The C2PA manifest is
added after the decision; see [Embed provenance](/build/embedding-provenance).

## Send bytes in the sandbox

Use `artifact.data` with `artifact.sha256` for up to **7 MiB decoded bytes**, keeping the entire
JSON body within **10 MiB**. A digest-only submission is attested. The sandbox does not fetch
`artifact.url`, and `artifact.file` is rejected. Neither path evaluates the media's content.

## Planned file-upload contract

The limits and upload examples below are protocol targets. File storage, URL fetches and the
10 MB inline target are not implemented by the current sandbox.

| Size | How |
| - | - |
| Up to 10 MB | Inline, as base64 `artifact.data` on the generation |
| Up to 100 MB | `multipart/form-data` to [`POST /v1/files`](/api-reference/files/upload-a-file) with `purpose=output` |
| Up to 5 GB | JSON to `POST /v1/files` with `size`, `sha256` and `media_type`, then `PUT` the bytes to `upload_url` |
| Any size, already hosted | `artifact.url`, a URL Reflections can fetch |

For the JSON upload, `upload_url` expires in 1 hour. Use the file once its `status` is `ready`.
Files expire after 24 hours unless a held generation needs them longer.

<CodeGroup>
  ```bash cURL theme={null}
  FILE=$(curl -s https://app.reflections.ai/v1/files \
    -H "Authorization: Bearer rk_test_…" \
    -H "Reflections-Version: 2026-10-15" \
    -H "Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d "{
      \"purpose\": \"output\",
      \"size\": $(wc -c < ridge-trail.mp4 | tr -d ' '),
      \"sha256\": \"$(shasum -a 256 ridge-trail.mp4 | cut -d' ' -f1)\",
      \"media_type\": \"video/mp4\",
      \"filename\": \"ridge-trail.mp4\"
    }")

  curl -X PUT "$(echo "$FILE" | jq -r .upload_url)" \
    -H "Content-Type: video/mp4" \
    --data-binary @ridge-trail.mp4
  ```

  ```typescript TypeScript theme={null}
  import { readFile, stat } from "node:fs/promises";
  import { createHash } from "node:crypto";

  const bytes = await readFile("ridge-trail.mp4");
  const sha256 = createHash("sha256").update(bytes).digest("hex");

  const file = await fetch("https://app.reflections.ai/v1/files", {
    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({
      purpose: "output",
      size: (await stat("ridge-trail.mp4")).size,
      sha256,
      media_type: "video/mp4",
      filename: "ridge-trail.mp4",
    }),
  }).then((r) => r.json());

  await fetch(file.upload_url, { method: "PUT", headers: { "Content-Type": "video/mp4" }, body: bytes });
  ```

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

  headers = {
      "Authorization": f"Bearer {os.environ['REFLECTIONS_KEY']}",
      "Reflections-Version": "2026-10-15",
  }

  with open("ridge-trail.mp4", "rb") as f:
      data = f.read()

  file = requests.post(
      "https://app.reflections.ai/v1/files",
      headers={**headers, "Idempotency-Key": str(uuid.uuid4())},
      json={
          "purpose": "output",
          "size": len(data),
          "sha256": hashlib.sha256(data).hexdigest(),
          "media_type": "video/mp4",
          "filename": "ridge-trail.mp4",
      },
  ).json()

  requests.put(file["upload_url"], headers={"Content-Type": "video/mp4"}, data=data)
  ```
</CodeGroup>

## Bind the output to its check

Create one generation per candidate. Name the `check`, the `candidate_index` (from 0 up to the
check's `execution.candidates` minus one), and the `sha256` of the bytes.

```json theme={null}
{
  "check": "chk_3m7p4r6s9t0wxyhk5zba2q8v1n",
  "candidate_index": 0,
  "artifact": {
    "file": "file_2c4e6g8j0k2m4n6p8q0r2s4t6v",
    "sha256": "9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a",
    "media_type": "image/png"
  },
  "execution": { "provider": "example-image-provider", "model": "image-model-3", "job_id": "job_88213" }
}
```

Send it to [`POST /v1/generations`](/api-reference/generations/create-a-generation) with an
`Idempotency-Key`, as in the [quickstart](/quickstart). Optional fields:

| Field | When to send it |
| - | - |
| `execution.job_id` | Always, when the provider gives one. It joins the output to the provider's record. |
| `transcript` | For speech, when you have the words. Reflections transcribes regardless. |
| `artifact.duration_ms` | For audio and video. |
| `watermark` | When you apply your own watermark, as `{ "algorithm", "id" }`, so verification can find the generation from it. |

## Read the decision

Images, speech up to 60 seconds and text return a decision in the response. Video and longer audio
return `status: "checking"`, and the decision arrives as
[`generation.allowed`](/api-reference/webhooks/generation-allowed) or
[`generation.denied`](/api-reference/webhooks/generation-denied).

| `status` | `decision` | What to do |
| - | - | - |
| `checking` | `null` | Wait for the event, or read the generation. Deliver nothing. |
| `allowed` | `allow` | Embed `c2pa`, meet the obligations, deliver. |
| `held` | `escalate` | Keep it private until a reviewer releases it. See [Handle escalations](/build/handling-escalations). |
| `released` | `allow` | A reviewer released it. Embed `c2pa` and deliver. |
| `denied` | `deny` | Never deliver it. Show `reasons[].sentence`. |
| `expired` | `deny` | A held output nobody reviewed in time. Discard it. |
| `withdrawn` | | You withdrew it. |

### Findings

`findings` lists each rule checked on the bytes and its `result`:

```json theme={null}
{
  "id": "gen_jqsm0950h8c6rcjme79vjwkj1e",
  "object": "generation",
  "status": "denied",
  "decision": "deny",
  "findings": [
    { "term": "license:lic_k5zba2q8v1m3n7p4r6s9t0wxyh/terms.requiredAttributes", "attribute": "identity.samePerson", "result": "preserved" },
    { "term": "license:lic_k5zba2q8v1m3n7p4r6s9t0wxyh/terms.neverExtras", "rule": "Do not show alcohol or tobacco.", "result": "violated", "detail": "A bottle of beer is visible in her left hand." }
  ],
  "unchecked_terms": [],
  "reasons": [
    {
      "code": "prohibited_custom_term",
      "stage": "generation",
      "talent": "tal_9t0wxyhk5zba2q8v1m3n7p4r6s",
      "term": "license:lic_k5zba2q8v1m3n7p4r6s9t0wxyh/terms.neverExtras",
      "sentence": "Do not show alcohol or tobacco."
    }
  ],
  "assurance": "observed",
  "receipt": "rcpt_dj4e1wt7a5g3xq2nzr6v8hk0m9",
  "c2pa": null
}
```

* `preserved`: the rule holds. `violated`: it does not, and the output is withheld.
* `unchecked`: the check could not read this rule. A rule the talent wrote that is unchecked does
  not withhold the output; it is delivered and listed in `unchecked_terms`. A protected attribute
  such as `identity.samePerson` that cannot be read withholds the output.
* `assurance: "observed"` means Reflections checked these bytes.

A denied generation still has a receipt. It records the decision; it is not something to embed.

## Which talent are checked

Reflections checks an output only for the talent its check covered. It does not match the output
against anyone else, so the check must cover every talent the request invokes. Name each talent your
user picks, and send references and voice samples as inputs; see [Resolve
talent](/build/resolving-talent). Every generation names a `check`, or a `session` for an output
made in a live session.

## Errors

| Status | Code | Cause and fix |
| - | - | - |
| 409 | `generation_already_submitted` | This candidate already has a generation. Read it instead of submitting again. |
| 409 | `authorization_consumed` | The check's `max_generations` is spent. Run a new check. |
| 409 | `check_not_allowed` | The check is not `allow`. Never generate under it. |
| 409 | `check_expired` | The authorization expired. Run a new check. |
| 422 | `artifact_digest_mismatch` | The bytes do not match `sha256`. Hash the exact bytes you upload. |
| 413 | `payload_too_large` | In the sandbox, decoded inline data exceeds 7 MiB or the JSON body exceeds 10 MiB. Reduce the payload; file uploads are not available. |
| 400 | `parameter_missing` | The generation names neither a `check` nor a `session`. Send the check the output was made under. |

A timeout leaves the outcome unknown. Retry with the same `Idempotency-Key`. A new key for a
candidate that already has a generation returns `409 generation_already_submitted`.

## Test it

Put `rfl_test_rule_violated`, `rfl_test_identity_changed`, `rfl_test_unchecked` or
`rfl_test_async` in the generation's `transcript` to force each result in test mode.

## Related

<Columns cols={2}>
  <Card title="Generation checks" icon="image" href="/concepts/generation-checks">
    What is checked on the bytes, and for which talent.
  </Card>

  <Card title="Embed provenance" icon="receipt" href="/build/embedding-provenance">
    Put the receipt in the output's C2PA manifest.
  </Card>

  <Card title="Handle escalations" icon="gavel" href="/build/handling-escalations">
    Held outputs and releases.
  </Card>

  <Card title="Create a generation" icon="code" href="/api-reference/generations/create-a-generation">
    The endpoint reference.
  </Card>
</Columns>


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