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

# Receipts

> The receipt format in JWS and COSE, how anyone verifies it, signing key publication and rotation, live status and receipt pages.

This page specifies the receipt a license server signs for each decision, its JWS and COSE forms,
the algorithm a verifier follows, how signing keys are published and rotated, the live status of a
receipt, and the public receipt page.

## When a receipt is issued

**TLP-RCP-1.** A license server MUST sign a receipt for every check and every generation when the
decision is `allow` or `deny`. An escalated check or a held generation gets its receipt when it
resolves. A session's receipt is the receipt of the check that started it.

**TLP-RCP-2.** A receipt MUST NOT change once signed. A later change in what is permitted is
reported through the receipt's [live status](#live-status), never by altering or re-signing it.

## JWS form

The header is `{ "alg": "ES256", "kid": "<key ID>", "typ": "tlp-receipt+jwt" }`. A receipt has no
`exp` and no `aud`: it is evidence for anyone, and it does not expire.

| Claim | Value |
| - | - |
| `iss` | The license server's issuer |
| `jti` | The receipt ID (`rcpt_`) |
| `iat` | When the decision was made |
| `sub` | The check ID or generation ID the receipt is for |
| `tlp.ver` | `"1.0"` |
| `tlp.kind` | `check` or `generation` |
| `tlp.decision` | `allow` or `deny` |
| `tlp.assurance` | `observed` or `attested`, as [TLP-DEC-29](/standard/decision-semantics#honesty) sets it |
| `tlp.talent` | The `tal_` IDs the decision covered |
| `tlp.licenses` | The licenses relied on, one entry per talent whose license applied, as [TLP-TOK-5](/standard/tokens#several-licenses) specifies. Empty when no license applied, such as for a check whose inputs invoke no talent or a `deny` with `no_license`. |
| `tlp.request_digest` | The request digest of the check, or for a generation, of the check it was made under |
| `tlp.artifact_sha256` | For a generation, the 64-hex SHA-256 of the output as submitted. `null` for a check. |
| `tlp.use` | The use decided |
| `tlp.obligations` | The obligation objects the decision carries, with their fields |
| `tlp.platform` | The platform's account ID |
| `tlp.licensee` | The licensee's account ID, or `null` when no license applied |
| `tlp.c2pa_signer` | For a generation whose platform declared it, `{ "thumbprint": "sha256:<hex>" }`: the SHA-256 of the DER encoding of the certificate that signs the output's C2PA claim. Absent otherwise. |

**TLP-RCP-3.** A license server MUST sign receipts as JWS Compact Serializations with `ES256` and the
header and claims above, and MUST NOT include an end-user identifier, prompt text or input media
in a receipt.

### Example (informative)

The receipt for a line of speech in Maya Okafor's voice that Northlight generated under its
platform license, for branded content on audio and the web in the US:

```json theme={null}
{
  "iss": "https://app.reflections.ai",
  "jti": "rcpt_hk5zba2q8v1m3n7p4r6s9t0wxy",
  "iat": 1792260363,
  "sub": "gen_2q8v1m3n7p4r6s9t0wxyhk5zba",
  "tlp": {
    "ver": "1.0",
    "kind": "generation",
    "decision": "allow",
    "assurance": "observed",
    "talent": ["tal_9t0wxyhk5zba2q8v1m3n7p4r6s"],
    "licenses": [
      {
        "talent": "tal_9t0wxyhk5zba2q8v1m3n7p4r6s",
        "license": "lic_ba2q8v1m3n7p4r6s9t0wxyhk5z",
        "revision": "lrv_7p4r6s9t0wxyhk5zba2q8v1m3n",
        "terms_digest": "sha256:8e0a2c4e6b8d0f2a4c6e8b0d2f4a6c8e0b2d4f6a8c0e2b4d6f8a0c2e4b6d8f0a"
      }
    ],
    "request_digest": "sha256:d4f6b8a0c2e4d6f8b0a2c4e6d8f0b2a4c6e8d0f2b4a6c8e0d2f4b6a8c0e2d4f6",
    "artifact_sha256": "3b5d7f9a1c3e5b7d9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d",
    "use": {
      "modality": "speech",
      "kind": "impersonation",
      "purpose": "branded_content",
      "commercial": true,
      "channels": ["audio", "web"],
      "territories": ["US"]
    },
    "obligations": [
      { "type": "disclosure", "level": "licensed", "placement": "metadata", "text": "AI voice of Maya Okafor, licensed." },
      { "type": "c2pa_manifest" },
      { "type": "usage_report", "units": "seconds", "within": "P1D" }
    ],
    "platform": "acct_7p4r6s9t0wxyhk5zba2q8v1m3n",
    "licensee": "acct_7p4r6s9t0wxyhk5zba2q8v1m3n"
  }
}
```

## COSE form

**TLP-RCP-4.** A license server MUST also make each receipt available as a tagged COSE\_Sign1
structure (RFC 9052, CBOR tag 18) carrying the same claims:

| Part | Value |
| - | - |
| Protected header | `1` (alg): `-7` (ES256); `3` (content type): `"application/json"`; `4` (kid): the `kid` as a byte string of its UTF-8 bytes |
| Unprotected header | Empty |
| Payload | The UTF-8 bytes of the JWS form's payload, unchanged |
| Signature | ES256 over the COSE `Signature1` structure, with empty external data |

```text theme={null}
18([
  << { 1: -7, 3: "application/json", 4: 'rfl-2026-10-docs3' } >>,
  {},
  '{"iat":1792260363,"iss":"https://app.reflections.ai","jti":"rcpt_hk5zba2q8v1m3n7p4r6s9t0wxy",…}',
  h'…'
])
```

Both forms carry the same payload bytes, so a verifier can check either and read the same claims.
The Talent API returns the JWS as `jws` and the COSE form as `cose` on the receipt object.

## Verifying a receipt

**TLP-RCP-5.** A verifier MUST follow these steps, and MUST treat the receipt as unverified when any
step fails:

<Steps>
  <Step title="Get the receipt">
    Read it from the output's C2PA assertion, from the receipt endpoint (`jws`), or from a
    verification result.
  </Step>

  <Step title="Check the header">
    `alg` MUST be `ES256` and `typ` MUST be `tlp-receipt+jwt`.
  </Step>

  <Step title="Choose the issuer">
    Read `iss` from the payload. Decide whether you trust that license server. Fetch its
    discovery document at `iss` followed by `/.well-known/talent-license`, confirm its `issuer`
    equals `iss`, and take its `jwks_uri`.
  </Step>

  <Step title="Verify the signature">
    Find `kid` in that JWK Set, fetching it once more if `kid` is unknown, and verify the
    signature. Reject when the key is not found.
  </Step>

  <Step title="Check the content">
    `iat` MUST NOT be later than the current time plus 60 seconds. `tlp.ver` MUST have major version `1`.
    Do not check for expiry: receipts do not expire.
  </Step>

  <Step title="Relate it to the content">
    For a generation receipt, relate the receipt to the file you hold: by
    `tlp.artifact_sha256`, by the C2PA manifest's binding, or by a verification lookup, as
    [C2PA](/standard/interop/c2pa#what-survives-stripping) specifies. A receipt that you cannot
    relate to the file proves nothing about it. A receipt read from a manifest is related to it
    only as [TLP-RCP-16](#binding-to-a-c2pa-claim-signer) allows.
  </Step>

  <Step title="Read what it proves">
    `tlp.decision` and `tlp.assurance` say what was decided at `iat` and what the license server
    observed. `tlp.licenses` names the signed revision behind each talent's license; to rely on
    those terms, compare each entry's `terms_digest` with that revision's, as
    [TLP-TOK-5](/standard/tokens#several-licenses) requires. For what is permitted now, read the
    live status.
  </Step>
</Steps>

## Binding to a C2PA claim signer

Anyone who holds a receipt can copy its assertion into a manifest for other content. The
`tlp.c2pa_signer` claim lets a verifier detect that.

**TLP-RCP-15.** A license server MUST include `tlp.c2pa_signer` in a generation receipt when the
generation request declared `c2pa_signer`, with the thumbprint as declared, and MUST NOT include it
otherwise or in a check receipt. The license server records the platform-declared thumbprint as
given and does not check it against a certificate.

**TLP-RCP-16.** A verifier that reads a receipt from a C2PA manifest's assertion MUST, when the
receipt has `tlp.c2pa_signer`, compute the SHA-256 of the DER encoding of that manifest's claim
signer certificate and treat the assertion as unrelated to the file unless it equals
`tlp.c2pa_signer.thumbprint`. A verifier MUST treat an assertion whose receipt has no
`tlp.c2pa_signer` as unbound: evidence of a decision, not of this file, unless a lookup relates
them ([TLP-C2PA-8](/standard/interop/c2pa#what-survives-stripping)).

The thumbprint names the certificate, not the file. A verifier still validates the claim signature
and its bindings to the file, as TLP-C2PA-8 requires.

## Signing keys and rotation

**TLP-RCP-6.** A license server MUST publish its signing keys as EC P-256 keys in a JWK Set at the
`jwks_uri` of its discovery document, served over HTTPS with a `Cache-Control` max-age.

**TLP-RCP-7.** A license server MUST publish a new key at least 30 days before it signs anything
with it, and MUST never reuse a `kid`. The Talent API names live keys `rfl-YYYY-MM` and test keys
`rfl-test-YYYY-MM`, such as `rfl-2026-10-docs3`.

**TLP-RCP-8.** A license server MUST keep a retired key in its JWK Set for as long as it retains any
receipt the key signed, so that old receipts keep verifying offline.

**TLP-RCP-9.** When a signing key is compromised, a license server MUST stop signing with it, remove
it from its JWK Set, keep serving the live status of every receipt it signed, and publish the
compromise and its time window. A verifier MUST then confirm such a receipt through the receipt
endpoint instead of offline.

**TLP-RCP-10.** A license server MUST sign test-mode receipts with test-mode keys and report them
with `livemode: false` at the receipt endpoint. A verifier MUST NOT accept a test-mode receipt as
evidence of a live license.

## Live status

A receipt proves what was decided when it was signed. Its live status says what use of the output
is permitted now.

| `use_status` | Meaning | Set when |
| - | - | - |
| `permitted` | Use within the receipt's scope is permitted | The receipt is issued for an `allow` |
| `ceased` | No new placements; existing ones may stay | A revocation with `post_revocation` `cease_distribution` takes effect |
| `withdrawn` | Take the output down | A revocation with `post_revocation` `withdraw` takes effect |
| `expired` | The license's window has ended | The window's end passes |
| `null` | Nothing is permitted | The receipt is issued for a `deny` |

**TLP-RCP-11.** A license server MUST serve each receipt and its current `use_status`, with
`use_status_changed_at`, at the `receipt_endpoint` of its discovery document, to anyone who has the
receipt ID, with or without credentials. It MAY rate-limit requests without credentials; the Talent
API allows 10 per minute per IP address.

**TLP-RCP-12.** A license server MUST change `use_status` only for the events in the table. A
revocation with `post_revocation` `retain` leaves it `permitted` until the window ends.

**TLP-RCP-13.** A verifier that decides whether an output may be placed or kept MUST read the live
status first, and MUST NOT rely on offline verification alone for that decision. `use_status` is
meaningful only for a receipt whose decision is `allow`, and is `null` on a `deny` receipt. A
verifier MUST ignore it on a `deny` receipt.

## Receipt pages

**TLP-RCP-14.** A license server MUST serve a human-readable page for each receipt at the
`receipt_page` URL template of its discovery document, such as
`https://app.reflections.ai/r/rcpt_hk5zba2q8v1m3n7p4r6s9t0wxy`. The page MUST show the decision,
the kind, the assurance, the talent's name, the licensee's and platform's names, the use, the time
of the decision, the current `use_status` and, for a generation, the artifact's SHA-256. It MUST
NOT show prompt text, inputs, the end-user claim or compensation.

## Related

<Columns cols={2}>
  <Card title="Receipt format" icon="receipt" href="/api-reference/formats/receipt">
    The receipt object in the Talent API.
  </Card>

  <Card title="C2PA" icon="image" href="/standard/interop/c2pa">
    Where the receipt goes in a manifest, and what survives stripping.
  </Card>

  <Card title="Revocation" icon="ban" href="/standard/revocation">
    The events that change a receipt's live status.
  </Card>

  <Card title="Retrieve a receipt" icon="code" href="/api-reference/receipts/retrieve-a-receipt">
    The receipt endpoint.
  </Card>
</Columns>


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