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

# Verify content

> Find the receipt behind a file, a manifest or a receipt ID, and read what use is permitted now.

<Note>
  Sandbox and protocol reference. Test receipt IDs, signed test receipts and exact SHA-256
  digests are supported. File uploads, URLs, manifests, fingerprints and watermarks below are
  planned verification methods, not currently runnable examples.
  Start with the [runnable quickstart](/quickstart) and [sandbox support matrix](/sandbox).
</Note>

The protocol's verification result identifies a decision and its current use status. In the
sandbox, `POST /v1/verifications` needs a test key (see the [verification table](/api-reference/authentication#endpoints-that-need-no-key)) and accepts a test receipt ID, signed test
receipt or exact SHA-256 digest. Digest lookup searches your account's receipts. Reading a known
receipt at `GET /v1/receipts/{receipt_id}` needs no key. These test results grant no production rights.

## Protocol verification methods

Send exactly one of these to [`POST /v1/verifications`](/api-reference/verifications/verify-content):

| You have | Send | `method` in the result |
| - | - | - |
| The file | `file` as `multipart/form-data`, or a `url` Reflections can fetch | `manifest` if it carries one, else `digest`, `fingerprint` or `watermark` |
| A C2PA manifest store | `manifest`, base64 | `manifest` |
| A receipt ID or the receipt JWS | `receipt` | `receipt` |
| Only the bytes' SHA-256 | `sha256` | `digest` |
| A fingerprint you computed | `fingerprint`: a `pdq`, `vpdq` or `chromaprint` hash, with optional face or voice embeddings | `fingerprint` |

A file that carries a manifest is verified through it, as in the example below. Otherwise it is
matched by its exact digest, then by fingerprint and watermark, so verification still works after
metadata is stripped or the file is re-encoded. `method` says which one matched.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://app.reflections.ai/v1/verifications \
    -H "Reflections-Version: 2026-10-15" \
    -F file=@upload.wav
  ```

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

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

  const verification = await fetch("https://app.reflections.ai/v1/verifications", {
    method: "POST",
    headers: { "Reflections-Version": "2026-10-15" },
    body: form,
  }).then((r) => r.json());
  ```

  ```python Python theme={null}
  import requests

  with open("upload.wav", "rb") as f:
      verification = requests.post(
          "https://app.reflections.ai/v1/verifications",
          headers={"Reflections-Version": "2026-10-15"},
          files={"file": ("upload.wav", f, "audio/wav")},
      ).json()
  ```
</CodeGroup>

Without a key, the public limit is 10 requests per minute per IP address. Send your key in
`Authorization` for your account's limits.

```json theme={null}
{
  "id": "vrf_8b0c2d4e6f8g0h2j4k6m8n0p2q",
  "object": "verification",
  "result": "verified",
  "method": "manifest",
  "match": null,
  "manifest": { "signer": "Northlight", "assertion_present": true, "signature_valid": true },
  "receipt": {
    "id": "rcpt_hk5zba2q8v1m3n7p4r6s9t0wxy",
    "object": "receipt",
    "kind": "generation",
    "subject": "gen_2q8v1m3n7p4r6s9t0wxyhk5zba",
    "decision": "allow",
    "assurance": "observed",
    "talent": ["tal_9t0wxyhk5zba2q8v1m3n7p4r6s"],
    "licenses": [
      {
        "talent": "tal_9t0wxyhk5zba2q8v1m3n7p4r6s",
        "license": "lic_ba2q8v1m3n7p4r6s9t0wxyhk5z",
        "license_revision": "lrv_7p4r6s9t0wxyhk5zba2q8v1m3n",
        "terms_digest": "sha256:8e0a2c4e6b8d0f2a4c6e8b0d2f4a6c8e0b2d4f6a8c0e2b4d6f8a0c2e4b6d8f0a"
      }
    ],
    "licensee": "acct_7p4r6s9t0wxyhk5zba2q8v1m3n",
    "platform": "acct_7p4r6s9t0wxyhk5zba2q8v1m3n",
    "permitted_use": {
      "modality": "speech",
      "purpose": "branded_content",
      "channels": ["audio", "web"],
      "territories": ["US"],
      "ends_at": "2027-06-30T23:59:59Z"
    },
    "use_status": "permitted",
    "use_status_changed_at": null,
    "page_url": "https://app.reflections.ai/r/rcpt_hk5zba2q8v1m3n7p4r6s9t0wxy"
  },
  "detail": null,
  "created_at": "2026-10-18T09:12:44Z"
}
```

## Read the result

| `result` | Meaning | What to do |
| - | - | - |
| `verified` | A valid receipt matches. | Read `receipt.decision` and `receipt.use_status`. |
| `not_found` | Nothing matches. | This says nothing about whether the content is licensed. Apply your own policy for unverified content. |
| `invalid` | A receipt or manifest is present and fails verification. | Treat the claim as false. `detail` says what failed. |

`receipt.licenses` has one entry per talent whose license applied: the `license`, the signed
`license_revision` and its `terms_digest`.

For a `fingerprint` match, `match.exact` is `false` and `match.confidence` is `high` or `medium`.
A `medium` match is weaker evidence; show it as such.

For a `manifest` match, `manifest.signer` is the C2PA claim signer, normally the platform that
generated the output. `signature_valid` is that signature's result. The receipt inside is checked
separately, against Reflections' keys. Manifest verification is planned and not served yet.

When you read a manifest yourself, check the receipt's `tlp.c2pa_signer` against the manifest's
claim signer certificate: its `thumbprint` must equal the SHA-256 of that certificate's DER.
Reject the assertion on a mismatch. A mismatch can mean the assertion was copied from other
content, or that the platform rotated or misdeclared its certificate; either way the receipt does
not prove this file. A receipt without
`tlp.c2pa_signer` is unbound: it proves the decision, not that this file carries it
([TLP-RCP-16](/standard/receipts#binding-to-a-c2pa-claim-signer)).

## Read the live status

A receipt never changes, but what may be done with the content can. `receipt.use_status` says what
use is permitted now:

| `use_status` | Meaning |
| - | - |
| `permitted` | Use within `permitted_use` is licensed. |
| `ceased` | The license was revoked with `cease_distribution`. Existing placements may stay; place it nowhere new. |
| `withdrawn` | The license requires released outputs to be taken down. Remove it. |
| `expired` | The use window in `permitted_use.ends_at` ended. |
| `null` | The receipt records a `deny`. It permits no use. |

`use_status_changed_at` says when it last changed. To read the current status of a receipt you
already hold, call [`GET /v1/receipts/{receipt_id}`](/api-reference/receipts/retrieve-a-receipt),
or send the ID as `receipt`. For sandbox verification, use your test key and export `RECEIPT_ID`
to an ID returned by your generation, such as the one saved by the [quickstart](/quickstart):

<CodeGroup>
  ```bash cURL theme={null}
  curl --fail-with-body https://app.reflections.ai/v1/verifications \
    -H "Authorization: Bearer $REFLECTIONS_KEY" \
    -H "Reflections-Version: 2026-10-15" \
    -H "Content-Type: application/json" \
    -d "$(jq -n --arg receipt "$RECEIPT_ID" '{receipt: $receipt}')"
  ```

  ```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",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ receipt: process.env.RECEIPT_ID }),
  }).then(async (r) => {
    if (!r.ok) throw new Error(`${r.status} ${await r.text()}`);
    return r.json();
  });
  ```

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

  response = requests.post(
      "https://app.reflections.ai/v1/verifications",
      headers={
          "Authorization": f"Bearer {os.environ['REFLECTIONS_KEY']}",
          "Reflections-Version": "2026-10-15",
      },
      json={"receipt": os.environ["RECEIPT_ID"]},
      timeout=30,
  )
  response.raise_for_status()
  verification = response.json()
  ```
</CodeGroup>

The public human-readable page is `https://app.reflections.ai/r/{receipt_id}`, also published as
`receipt_page` in discovery. `GET /v1/receipts/{receipt_id}` remains the JSON endpoint. Neither
requires a key. Test pages are labelled fictional and grant no production rights. If verification
is unavailable, the human page shows an unavailable state; the JSON endpoint returns HTTP 503.

## Verify a receipt offline

The receipt is a compact JWS (`receipt.jws`, or `data.receipt` in the C2PA assertion). You can
verify it without calling Reflections:

<Steps>
  <Step title="Find the key">
    Read `kid` from the JWS header and find it in `https://app.reflections.ai/.well-known/jwks.json`.
    Cache the JWKS for its `Cache-Control` max-age, and refetch once if the `kid` is unknown.
    Retired keys stay published, so old receipts keep verifying.
  </Step>

  <Step title="Check the signature and header">
    The algorithm is ES256 and `typ` is `tlp-receipt+jwt`. `iss` is `https://app.reflections.ai`.
    A receipt has no `exp`.
  </Step>

  <Step title="Read the claims">
    `tlp.kind`, `tlp.decision`, `tlp.assurance`, `tlp.talent` and `tlp.licenses` say what was
    decided and under which signed terms. Each `tlp.licenses` entry names a talent's license, the
    signed `revision` and its `terms_digest`; the licensee can compare that digest with the
    revision's own `terms_digest`. For a generation, `tlp.artifact_sha256` is the digest of the
    bytes the platform submitted, before it embedded the manifest, and `tlp.c2pa_signer`, when
    present, names the certificate that must sign the manifest carrying it.
  </Step>
</Steps>

Offline verification proves the receipt is genuine and what it covered. It cannot tell you the live
status after a revocation; read `use_status` for that. The
[receipt format](/api-reference/formats/receipt) lists every claim.

## What can go wrong

| Problem | What to do |
| - | - |
| `504 evaluation_timeout` | A file match did not finish in time. Retry, or verify by receipt if the file carries one. |
| `413 payload_too_large` | The upload is over 100 MB. Send a `url` instead. |
| `429 rate_limited` | Wait for `Retry-After`, or send your key. |
| A test receipt does not verify | Test receipts verify only with a test key. Send `rk_test_…`. |

<Note>
  `assurance` matters. `observed` means Reflections checked the bytes. `attested` means it recorded
  what the platform stated, such as text sent `withheld`, and never inspected that content.
</Note>

## Related

<Columns cols={2}>
  <Card title="Distribution platforms" icon="upload" href="/guides/distribution-platforms">
    Verify uploads and act on live status.
  </Card>

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

  <Card title="Receipt format" icon="code" href="/api-reference/formats/receipt">
    JWS and COSE forms, and every claim.
  </Card>

  <Card title="Embed provenance" icon="image" href="/build/embedding-provenance">
    Write the receipt into a C2PA manifest.
  </Card>
</Columns>


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