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

# Tokens

> The authorization and session token formats, how a generation tool verifies them, and the canonical form behind every digest.

This page specifies the two tokens a license server issues, the authorization token and the
session token, the algorithm a generation tool uses to verify them, and the `tlp-json-1` canonical
form that `terms_digest` and `request_digest` are computed over.

## Common format

| Property | Value |
| - | - |
| Serialization | JWS Compact Serialization (RFC 7515) |
| Algorithm | `ES256`: ECDSA on P-256 with SHA-256 (RFC 7518) |
| Header | `alg`, `kid`, `typ` |
| Keys | EC P-256 keys in a JWK Set (RFC 7517) at the issuer's `jwks_uri` |
| Times | NumericDate, in seconds |

| `typ` | Token | Specified on |
| - | - | - |
| `tlp-auth+jwt` | Authorization token | This page |
| `tlp-session+jwt` | Session token | This page |
| `tlp-receipt+jwt` | Receipt | [Receipts](/standard/receipts) |

**TLP-TOK-1.** A license server MUST sign every token with `ES256`, and MUST set `alg`, `kid` and
`typ` in the protected header and no other key-locating header (`jwk`, `jku`, `x5u`, `x5c`).

**TLP-TOK-2.** A license server MUST publish each signing key in its JWK Set at least 30 days before
it signs anything, MUST never reuse a `kid`, and MUST sign test-mode tokens with keys distinct from
live keys. Key rotation is specified with [receipt keys](/standard/receipts#signing-keys-and-rotation).

## Authorization token

A check that returns `allow` carries an authorization token. The platform passes it to the
generation tool, which verifies it before it runs the model.

| Claim | Value |
| - | - |
| `iss` | The license server's issuer |
| `aud` | The platform's account ID |
| `sub` | The licensee's account ID. Equal to `aud` under a platform license. |
| `jti` | The check ID |
| `iat` | When the token was issued |
| `exp` | At most 900 seconds after `iat`, and equal to the check's `authorization.expires_at` |
| `tlp.ver` | The protocol version, `"1.0"` |
| `tlp.decision` | `"allow"` |
| `tlp.talent` | The `tal_` IDs the check covered |
| `tlp.facets` | The `fct_` IDs the check covered |
| `tlp.licenses` | The licenses relied on, one entry per talent whose license applied, as [TLP-TOK-5](#several-licenses) specifies. Empty when no license applied. |
| `tlp.request_digest` | The check's [request digest](#request-digest) |
| `tlp.use` | The use object the check decided |
| `tlp.max_generations` | How many generations the token allows |
| `tlp.execution` | `provider` and `model`, as the check stated them |
| `tlp.eu` | The [end-user claim](#end-user-claim). Absent when the check named no end user. |
| `tlp.obligations` | The types of the obligations the decision carries |

**TLP-TOK-3.** A license server MUST issue an authorization token only for an `allow`, with exactly
the claims above, and MUST NOT issue one for `deny` or `escalate`.

### Example (informative)

The authorization for Ridgeline's campaign check, run on Northlight. Protected header:

```json theme={null}
{ "alg": "ES256", "kid": "rfl-2026-10-docs3", "typ": "tlp-auth+jwt" }
```

Payload:

```json theme={null}
{
  "iss": "https://app.reflections.ai",
  "aud": "acct_7p4r6s9t0wxyhk5zba2q8v1m3n",
  "sub": "acct_2q8v1m3n7p4r6s9t0wxyhk5zba",
  "jti": "chk_3m7p4r6s9t0wxyhk5zba2q8v1n",
  "iat": 1792260000,
  "exp": 1792260900,
  "tlp": {
    "ver": "1.0",
    "decision": "allow",
    "talent": ["tal_9t0wxyhk5zba2q8v1m3n7p4r6s"],
    "facets": ["fct_5zba2q8v1m3n7p4r6s9t0wxyhk"],
    "licenses": [
      {
        "talent": "tal_9t0wxyhk5zba2q8v1m3n7p4r6s",
        "license": "lic_k5zba2q8v1m3n7p4r6s9t0wxyh",
        "revision": "lrv_p4r6s9t0wxyhk5zba2q8v1m3n7",
        "terms_digest": "sha256:39f18c8bef02b5226b9feaa92e526ed30b81b91650d8a10b503281b169f44431"
      }
    ],
    "request_digest": "sha256:bf94817766d56d92211d12f5ff51a3dc1a6d5cb4be2877417b08251fe38c386a",
    "use": {
      "modality": "image",
      "kind": "depiction",
      "purpose": "advertising",
      "commercial": true,
      "channels": ["social", "web"],
      "territories": ["US", "CA"]
    },
    "max_generations": 4,
    "execution": { "provider": "example-image-provider", "model": "image-model-3" },
    "eu": "3f9c1a7e5b2d8f4061c3e9a7b5d2f8016e4c9a3b7d1f5e2c8a6b4d0f9e3c7a15",
    "obligations": ["generation_check", "hold_until_release", "draft_watermark", "generation_instructions", "disclosure", "c2pa_manifest", "no_training", "usage_report"]
  }
}
```

## Session token

A session carries a session token, renewed while the session runs.

| Claim | Value |
| - | - |
| `iss`, `aud`, `sub` | As in the authorization token |
| `jti` | The session ID |
| `iat` | When the token was issued |
| `exp` | 300 seconds after `iat` |
| `tlp.ver` | `"1.0"` |
| `tlp.session` | The `ses_` ID |
| `tlp.check` | The `chk_` ID of the check that started the session |
| `tlp.talent`, `tlp.facets` | The talent and facets the session covers |
| `tlp.licenses` | As in the authorization token |
| `tlp.use` | The use object |
| `tlp.sampling` | The sampling the session requires: `audio_window_max`, `transcript`, `frame_interval` |
| `tlp.eu` | The end-user claim |

**TLP-TOK-4.** A license server MUST issue session tokens with exactly the claims above and an `exp`
300 seconds after `iat`, and MUST stop issuing them for a session that is ended or terminated. A
renewed token MUST keep the `jti`, `tlp.licenses` and `tlp.facets` of the token it replaces.

## Several licenses

A decision can rest on several licenses, one for each talent whose license applies. Tokens and
[receipts](/standard/receipts#jws-form) name them all in one claim, `tlp.licenses`.

**TLP-TOK-5.** A license server MUST set `tlp.licenses` to an array with one entry for each talent
whose license the decision relied on, in the order of `tlp.talent`, and to an empty array when no
license applied. Each entry MUST hold `talent` (the `tal_` ID), `license` (the `lic_` ID),
`revision` (the `lrv_` ID of the signed revision relied on) and `terms_digest` (that revision's
terms digest). A talent whose posture allows the use without a license has no entry. A verifier
that relies on the terms behind a decision MUST compare each entry's `terms_digest` with the
`terms_digest` of the revision the entry names, and MUST NOT rely on an entry whose digests differ.

The authorization token example above carries one entry, for Maya Okafor's license.

## Verifying a token

**TLP-TOK-6.** A generation tool MUST verify a token with the steps below before it generates for a
request that carries a token, or that [TLP-DSC-10](/standard/discovery#talent-index) would require
a platform to check, and MUST NOT generate when any step fails. A platform that runs its
own model is a generation tool for this rule.

<Steps>
  <Step title="Parse">
    Split the token into its three base64url parts. Decode the header as JSON. Reject anything
    that is not a JWS Compact Serialization.
  </Step>

  <Step title="Check the header">
    `alg` MUST be `ES256` and `typ` MUST be `tlp-auth+jwt` for an authorization or
    `tlp-session+jwt` for a session. Reject a token with `jwk`, `jku`, `x5u` or `x5c` in its header.
  </Step>

  <Step title="Find the key">
    Look up `kid` in the JWK Set of an issuer you are configured to trust, fetched over HTTPS from
    the `jwks_uri` of its discovery document. If `kid` is unknown, fetch the JWK Set once more. If
    it is still unknown, reject.
  </Step>

  <Step title="Verify the signature">
    Verify the ES256 signature over the first two parts. Reject on failure.
  </Step>

  <Step title="Check the issuer and audience">
    `iss` MUST equal the trusted issuer whose key verified the token. `aud` MUST equal the account
    ID of the platform the request comes from.
  </Step>

  <Step title="Check the time">
    Reject when the current time is later than `exp` plus 60 seconds, or earlier than `iat` minus
    60 seconds.
  </Step>

  <Step title="Check the content">
    `tlp.ver` MUST have major version `1`. For an authorization, `tlp.decision` MUST be `allow`,
    and `tlp.execution.model` MUST equal the model about to run. For a session, `tlp.session` MUST
    equal the session you are running.
  </Step>

  <Step title="Check the count">
    For an authorization, count the generations you have run under this `jti`. Reject when this
    one would exceed `tlp.max_generations`. Record it.
  </Step>

  <Step title="Check the obligations">
    For an authorization, reject when `tlp.obligations` holds a type that is not registered in a
    protocol version you implement ([TLP-OBL-2](/standard/obligations-registry#rules-for-every-obligation)).
  </Step>
</Steps>

**TLP-TOK-7.** A generation tool MUST keep its count of generations for each `jti` until at least
60 seconds after the token's `exp`, and MUST count a generation it started even if it failed.

**TLP-TOK-8.** A generation tool MUST NOT accept a receipt, an RSL license token or any token whose
`typ` differs from the one the step expects, in place of an authorization or session token.

**TLP-TOK-9.** A party that caches a JWK Set MUST respect its `Cache-Control` max-age, and MUST NOT
fetch it again on an unknown `kid` more than once for the same `kid` within that max-age.

A license server that receives a token failing these steps answers with the `token_invalid` error.

## Canonical form

`tlp-json-1` is the canonical JSON serialization that every TLP digest is computed over.

**TLP-TOK-10.** To serialize a value in `tlp-json-1`, an implementation MUST:

1. encode the output in UTF-8, with no byte order mark;
2. write no whitespace outside strings;
3. write object members sorted by key, comparing keys as sequences of Unicode code points;
4. reject any input object with a duplicate key, rather than serialize it;
5. write strings as RFC 8785 does: escape `"` and `\`, write U+0008, U+0009, U+000A, U+000C and
   U+000D as `\b`, `\t`, `\n`, `\f` and `\r`, write other characters below U+0020 as `\u` and four
   lowercase hex digits, and write every other character as itself;
6. write numbers in the shortest form that reads back as the same IEEE 754 double, as RFC 8785
   does, with no `-0`;
7. write `true`, `false` and `null` as themselves, and keep the order of array elements.

For keys made only of characters below U+10000, which includes every key TLP defines, rule 3
orders keys the same way as RFC 8785.

**TLP-TOK-11.** A digest MUST be `sha256:` followed by the 64 lowercase hex characters of the
SHA-256 of the canonical bytes. A license revision's `terms_digest` is the digest of its terms.

## Request digest

**TLP-TOK-12.** A license server MUST compute a check's `request_digest` over this object, in
`tlp-json-1`:

```text theme={null}
{ "inputs": <inputs>, "use": <use>, "execution": { "provider": <provider>, "model": <model> } }
```

* `inputs` holds the check's inputs in the order sent. A text input becomes `{ "role", "text" }`. A
  media input becomes `{ "role", "sha256", "media_type" }`, where `sha256` is the 64-hex digest of
  its bytes, however they were sent.
* `use` is the use object as sent, with absent fields left out.
* `execution` holds `provider` and `model` as sent, with absent fields left out. Other execution
  fields are not part of the digest.

The digest covers the request as checked, before any `generation_instructions` are added
([TLP-DEC-26](/standard/decision-semantics#binding)).

### Worked example (informative)

A check for one spoken line in Maya Okafor's voice, under Northlight's platform license:

```json theme={null}
{
  "inputs": [
    { "role": "system", "text": "Speak as Maya Okafor. Warm, even pace." },
    { "role": "script", "text": "Trail season starts this weekend. See you at the trailhead." },
    { "role": "voice_sample", "sha256": "7f4cced805a3c46b2b31b63ae97f86fcd3d8b11d19fd007dc84dc4a4992c051b", "media_type": "audio/wav" }
  ],
  "use": { "modality": "speech", "kind": "impersonation", "purpose": "personal", "commercial": false },
  "execution": { "provider": "example-voice-provider", "model": "tts-2" }
}
```

Its canonical bytes:

```text theme={null}
{"execution":{"model":"tts-2","provider":"example-voice-provider"},"inputs":[{"role":"system","text":"Speak as Maya Okafor. Warm, even pace."},{"role":"script","text":"Trail season starts this weekend. See you at the trailhead."},{"media_type":"audio/wav","role":"voice_sample","sha256":"7f4cced805a3c46b2b31b63ae97f86fcd3d8b11d19fd007dc84dc4a4992c051b"}],"use":{"commercial":false,"kind":"impersonation","modality":"speech","purpose":"personal"}}
```

Its request digest:

```text theme={null}
sha256:e0244de7ab08a4e226d0b2b584e234a55c3ee728a82a528cc01091e976c97374
```

Request digest objects contain no numbers, so Python's standard library produces the same bytes:

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

def request_digest(obj):
    canonical = json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
    return "sha256:" + hashlib.sha256(canonical.encode("utf-8")).hexdigest()
```

## End-user claim

**TLP-TOK-13.** `tlp.eu` MUST be the lowercase hex HMAC-SHA256 of the end user's ID under a key the
license server holds for the platform. A license server MUST NOT put an end user's ID, or any other
identifier of a person, in a token in the clear. The claim lets the license server and the
platform relate decisions about one end user; it reveals nothing to anyone else.

## Related

<Columns cols={2}>
  <Card title="Authorization token format" icon="key" href="/api-reference/formats/authorization-token">
    The token as the Talent API issues it.
  </Card>

  <Card title="Session token format" icon="microphone" href="/api-reference/formats/session-token">
    Claims and renewal of the session token.
  </Card>

  <Card title="Receipts" icon="receipt" href="/standard/receipts">
    The third token type, and key rotation.
  </Card>

  <Card title="Security" icon="shield-check" href="/standard/security">
    The threats these rules answer.
  </Card>
</Columns>


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