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

# Distribution platforms

> Verify receipts when content is uploaded, by manifest and then by file, and act on a receipt's live use status.

<Note>
  Planned integration guide. The current sandbox verifies test receipt IDs, signed test receipts
  and exact SHA-256 digests. Manifest, file, URL, fingerprint and watermark verification are not
  available.
  Start with the [runnable quickstart](/quickstart) and [sandbox support matrix](/sandbox).
</Note>

A distribution platform hosts content that others made: a social network, an ad network, a stock
library, a streaming service. When an upload shows or sounds like a real person, verification
tells you whether a license covers it, for which channels and territories, and whether that use
is still permitted today in the target protocol. Current sandbox verification requires a test
key and supports only test receipts or exact digests; it does not establish rights over real content.

```mermaid theme={null}
flowchart TD
    A[Upload] --> B{C2PA manifest with the assertion?}
    B -->|yes| C[Verify by manifest]
    B -->|no| D[Verify by file]
    C -->|not_found| D
    C --> E{result}
    D --> E
    E -->|verified| F[Check permitted_use and use_status]
    E -->|invalid| G[Treat the license claim as false]
    E -->|not_found| H[No receipt, apply your usual policy]
```

## Verify by manifest first

Licensed outputs carry a receipt in their C2PA manifest, as the `ai.reflections.license`
assertion. Extract the manifest store with your C2PA library and send it as base64 in
`manifest`. The file stays on your side.

<CodeGroup>
  ```bash cURL theme={null}
  printf '{ "manifest": "%s" }' "$(base64 < upload.c2pa | tr -d '\n')" |
  curl https://app.reflections.ai/v1/verifications \
    -H "Authorization: Bearer $REFLECTIONS_KEY" \
    -H "Reflections-Version: 2026-10-15" \
    -H "Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    --data-binary @-
  ```

  ```typescript TypeScript theme={null}
  const verification = await fetch("https://app.reflections.ai/v1/verifications", {
    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({ manifest: manifestStore.toString("base64") }), // manifestStore: Buffer from your C2PA library
  }).then((r) => r.json());
  ```

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

  verification = requests.post(
      "https://app.reflections.ai/v1/verifications",
      headers={"Authorization": f"Bearer {os.environ['REFLECTIONS_KEY']}", "Reflections-Version": "2026-10-15",
               "Idempotency-Key": str(uuid.uuid4())},
      json={"manifest": base64.b64encode(manifest_store).decode()},  # manifest_store: bytes from your C2PA library
  ).json()
  ```
</CodeGroup>

```json theme={null}
{
  "id": "vrf_bcb6ka721y5hsdhn5a60hdqq2w",
  "object": "verification",
  "result": "verified",
  "method": "manifest",
  "manifest": { "signer": "Northlight", "assertion_present": true, "signature_valid": true },
  "receipt": {
    "id": "rcpt_bfwgfkw4w683avvxm1q0h4fard",
    "object": "receipt",
    "kind": "generation",
    "decision": "allow",
    "assurance": "observed",
    "talent": ["tal_9t0wxyhk5zba2q8v1m3n7p4r6s"],
    "licensee": "acct_2q8v1m3n7p4r6s9t0wxyhk5zba",
    "platform": "acct_7p4r6s9t0wxyhk5zba2q8v1m3n",
    "artifact_sha256": "9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a",
    "permitted_use": {
      "modality": "image",
      "purpose": "advertising",
      "channels": ["social", "web"],
      "territories": ["US", "CA"],
      "ends_at": "2027-03-31T23:59:59Z"
    },
    "obligations": [
      { "type": "disclosure", "level": "brand", "placement": "both", "text": "Made with AI. Maya Okafor's likeness used under license by Ridgeline." }
    ],
    "use_status": "permitted",
    "page_url": "https://app.reflections.ai/r/rcpt_bfwgfkw4w683avvxm1q0h4fard"
  }
}
```

`manifest.signer` is the platform that signed the C2PA claim. The receipt inside is signed by
Reflections, so it verifies whoever signed the manifest.

## Then by file

Editors and social apps often strip metadata, so an upload with no manifest may still have a
receipt. Verify the file itself. Reflections matches its exact digest first and then its
fingerprint, so a re-encoded or resized copy still matches.

| Send | When |
| - | - |
| `file` as multipart, up to 100 MB | You can send the bytes |
| `url` | The file is at a URL Reflections can fetch |
| `sha256` | You want an exact match without sending bytes |
| `fingerprint` | The bytes cannot leave your platform: a `pdq`, `vpdq` or `chromaprint` hash and optional embeddings |

<CodeGroup>
  ```bash cURL theme={null}
  curl https://app.reflections.ai/v1/verifications \
    -H "Authorization: Bearer $REFLECTIONS_KEY" \
    -H "Reflections-Version: 2026-10-15" \
    -H "Idempotency-Key: $(uuidgen)" \
    -F file=@upload.jpg
  ```

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

  const form = new FormData();
  form.append("file", new Blob([await readFile("upload.jpg")], { type: "image/jpeg" }), "upload.jpg");

  const byFile = await fetch("https://app.reflections.ai/v1/verifications", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.REFLECTIONS_KEY}`,
      "Reflections-Version": "2026-10-15",
      "Idempotency-Key": crypto.randomUUID(),
    },
    body: form,
  }).then((r) => r.json());
  ```

  ```python Python theme={null}
  with open("upload.jpg", "rb") as f:
      by_file = requests.post(
          "https://app.reflections.ai/v1/verifications",
          headers={"Authorization": f"Bearer {os.environ['REFLECTIONS_KEY']}", "Reflections-Version": "2026-10-15",
                   "Idempotency-Key": str(uuid.uuid4())},
          files={"file": ("upload.jpg", f, "image/jpeg")},
      ).json()
  ```
</CodeGroup>

A fingerprint match returns `method: "fingerprint"` with `match.exact: false` and a
`match.confidence` of `high` or `medium`. Treat `medium` as a lead to review, not as proof.

## Read the result

| `result` | Meaning | What to do |
| - | - | - |
| `verified` | A valid receipt matches | Check the receipt against your use, below |
| `invalid` | A receipt or manifest is present and fails verification | Do not show the content as licensed. Treat the claim as false |
| `not_found` | Nothing matches | Apply your usual policy. This says nothing either way about whether the content is licensed |

## Check that your use is covered

A receipt covers one use, in `permitted_use`. Content licensed for social media is not licensed
for a billboard. Before you place it, check that:

* `permitted_use.channels` includes where you will show it, such as `social`, `streaming` or
  `out_of_home`;
* `permitted_use.territories` includes where it will be shown, or is `WW`;
* `permitted_use.ends_at` has not passed;
* `use_status` is `permitted`.

Show the `disclosure` obligation's `text` with the content when the receipt carries one.
`assurance` says what the receipt rests on: `observed` means Reflections checked the bytes,
`attested` means it recorded what the platform stated, such as text sent `withheld`.

## Act on the live status

A receipt stays valid evidence that the output was licensed when it was made. Whether it may be
used now is `use_status`, which changes when a talent revokes or a license window ends. Read it
again with [`GET /v1/receipts/{receipt_id}`](/api-reference/receipts/retrieve-a-receipt) on a
schedule for content you keep up, such as daily, and always before a new placement.

| `use_status` | What to do |
| - | - |
| `permitted` | Keep it up within `permitted_use` |
| `ceased` | Make no new placements: no new ad flights, boosts, features or re-uploads. Existing placements may stay |
| `withdrawn` | Take it down |
| `expired` | The license window ended. Make no new placements |

```json theme={null}
{
  "id": "rcpt_bfwgfkw4w683avvxm1q0h4fard",
  "object": "receipt",
  "decision": "allow",
  "use_status": "ceased",
  "use_status_changed_at": "2026-12-02T09:14:00Z"
}
```

`use_status_changed_at` says when it changed. A file uploaded again after `ceased` is a new
placement.

## Verify offline

The receipt is a compact JWS with `typ` `tlp-receipt+jwt`, signed with keys published at
`https://app.reflections.ai/.well-known/jwks.json`. It has no `exp`. You can verify `receipt.jws`,
or the `receipt` inside the C2PA assertion, without calling Reflections, and compare
`tlp.artifact_sha256` with the file's SHA-256. Offline verification proves the receipt is genuine.
Only the live receipt says what use is permitted today. See [Receipt](/api-reference/formats/receipt).

## Keys and limits

Verification works without a key at 10 requests per minute per IP, which suits a person checking
a file. To verify uploads at volume, create an account and send your key; your account's rate
limits then apply. Receipts from test mode verify only with a test key.

## What can go wrong

| Response | Cause | What to do |
| - | - | - |
| `413 payload_too_large` | The multipart file is over 100 MB | Send `url`, `sha256` or `fingerprint` |
| `504 evaluation_timeout` | A file verification did not finish in time | Retry, or verify by receipt ID |
| `429 rate_limited` | Too many requests | Wait `Retry-After` seconds; use a key for volume |
| `invalid` on content you expected to verify | The file was edited after its manifest was signed | Ask the uploader for the original |

## Related

<Columns cols={2}>
  <Card title="Verifying content" icon="magnifying-glass" href="/build/verifying-content">
    Verification by receipt, manifest or file.
  </Card>

  <Card title="Provenance" icon="stamp" href="/concepts/provenance">
    Receipts, C2PA manifests and fingerprints.
  </Card>

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

  <Card title="C2PA assertion" icon="file-code" href="/api-reference/formats/c2pa-assertion">
    The `ai.reflections.license` assertion.
  </Card>
</Columns>


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