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

# Agent integration design

> Proposed Talent API MCP workflow and authorization verification inside a generation tool.

<Note>
  Planned design guide. The Talent API MCP server and its OAuth flow are not available. The tool
  calls below describe the intended interface. Use the test-mode HTTP quickstart for a runnable
  integration.
  Start with the [runnable quickstart](/quickstart) and [sandbox support matrix](/sandbox).
</Note>

An agent checks a use with the `check_use` tool and passes the authorization it gets to the tool
that generates. That tool verifies the token before it runs. The check tells the agent what is
allowed; the verification in the tool is what enforces it. This design describes both sides using
the fictional talent Maya Okafor. The Talent API MCP interface is not yet available.

```mermaid theme={null}
sequenceDiagram
    participant A as Agent
    participant R as Reflections MCP
    participant T as Your generation tool
    participant API as Talent API
    A->>R: check_use
    R-->>A: allow and authorization token
    A->>T: generate_image with the token
    T->>T: Verify the token against the JWKS
    T->>API: POST /v1/generations
    API-->>T: allow and receipt
    T-->>A: Output with its C2PA assertion
```

<Steps>
  <Step title="Export your test key">
    ```bash theme={null}
    export REFLECTIONS_KEY="rk_test_…"
    ```
  </Step>

  <Step title="Get your account ID">
    An authorization token names the platform it was issued to in `aud`. In this design your
    account is both the platform and the licensee, so your tool accepts tokens whose `aud` is
    your own account ID.

    <CodeGroup>
      ```bash cURL theme={null}
      curl https://app.reflections.ai/v1/account \
        -H "Authorization: Bearer $REFLECTIONS_KEY" \
        -H "Reflections-Version: 2026-10-15"
      ```

      ```typescript TypeScript theme={null}
      const res = await fetch("https://app.reflections.ai/v1/account", {
        headers: { Authorization: `Bearer ${process.env.REFLECTIONS_KEY}`, "Reflections-Version": "2026-10-15" },
      });
      if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
      const account = await res.json();
      ```

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

      res = requests.get(
          "https://app.reflections.ai/v1/account",
          headers={"Authorization": f"Bearer {os.environ['REFLECTIONS_KEY']}", "Reflections-Version": "2026-10-15"},
      )
      res.raise_for_status()
      account = res.json()
      ```
    </CodeGroup>

    ```json theme={null}
    {
      "id": "acct_7p4r6s9t0wxyhk5zba2q8v1m3n",
      "object": "account",
      "livemode": false,
      "name": "Northlight",
      "types": ["platform"],
      "capabilities": ["hold_licenses", "request_licenses", "run_checks"],
      "status": "active",
      "api_version": "2026-10-15"
    }
    ```
  </Step>

  <Step title="Review the planned MCP interface">
    No Talent API MCP connection can be configured yet. The proposed OAuth transport and scopes
    are described in [MCP design](/api-reference/mcp/overview). The existing Licensed Studio MCP
    and Mintlify docs-search MCP have different purposes and do not supply these tools.

    The proposed interface has ten tools. Their arguments mirror the API request bodies and parameters:

    | Tool | Same as |
    | - | - |
    | `resolve_talent` | [`POST /v1/resolutions`](/api-reference/resolutions/resolve-talent) |
    | `check_use` | [`POST /v1/checks`](/api-reference/checks/create-a-check) |
    | `submit_generation` | [`POST /v1/generations`](/api-reference/generations/create-a-generation) |
    | `get_check` | [`GET /v1/checks/{check_id}`](/api-reference/checks/retrieve-a-check) |
    | `get_generation` | [`GET /v1/generations/{generation_id}`](/api-reference/generations/retrieve-a-generation) |
    | `get_license` | [`GET /v1/licenses/{license_id}`](/api-reference/licenses/retrieve-a-license) |
    | `list_licenses` | [`GET /v1/licenses`](/api-reference/licenses/list-licenses) |
    | `request_license` | [`POST /v1/license_requests`](/api-reference/license-requests/request-a-license) |
    | `report_usage` | [`POST /v1/usage_records`](/api-reference/usage-records/report-usage) |
    | `verify_receipt` | [`POST /v1/verifications`](/api-reference/verifications/verify-content) |
  </Step>

  <Step title="Check the use">
    When the agent is asked for an image of a real person, it calls `check_use` with the prompt it
    will send to the generation tool. Your client sends:

    ```json tools/call theme={null}
    {
      "jsonrpc": "2.0",
      "id": 3,
      "method": "tools/call",
      "params": {
        "name": "check_use",
        "arguments": {
          "inputs": [{ "role": "prompt", "text": "Maya Okafor on a mountain trail at sunrise" }],
          "use": {
            "modality": "image",
            "kind": "depiction",
            "purpose": "advertising",
            "commercial": true,
            "channels": ["social"],
            "territories": ["US"]
          },
          "end_user": { "id": "usr_48213" },
          "execution": { "provider": "example-image-provider", "model": "image-model-3", "candidates": 1 },
          "on_escalate": "deny"
        }
      }
    }
    ```

    The result carries the check in `structuredContent`:

    ```json result theme={null}
    {
      "jsonrpc": "2.0",
      "id": 3,
      "result": {
        "content": [{ "type": "text", "text": "{\"id\":\"chk_4gnt7r0k2m5q8s1v3x6z9b2d4f\",\"decision\":\"allow\",…}" }],
        "structuredContent": {
          "id": "chk_4gnt7r0k2m5q8s1v3x6z9b2d4f",
          "object": "check",
          "livemode": false,
          "status": "allowed",
          "decision": "allow",
          "obligations": [
            { "type": "generation_check", "required": "all" },
            { "type": "generation_instructions", "instructions": ["Keep Maya Okafor the same person: face, skin tone and build unchanged.", "Do not show alcohol or tobacco."] },
            { "type": "disclosure", "level": "licensed", "placement": "both", "text": "Made with AI. Likeness used under license." },
            { "type": "c2pa_manifest" }
          ],
          "authorization": {
            "token": "eyJhbGciOiJFUzI1NiIs…",
            "max_generations": 1,
            "generations_used": 0,
            "expires_at": "2026-10-17T18:15:00Z"
          }
        },
        "isError": false
      }
    }
    ```

    On `deny`, the agent tells the user `reasons[].sentence` and does not call the generation
    tool. An agent that can come back to the conversation can pass `on_escalate: "hold"` instead.
    On `escalate`, it tells the user the request is with a reviewer and reads the outcome later
    with `get_check`, passing `{ "check_id": "chk_…" }`.
  </Step>

  <Step title="Pass the token to your tool">
    Give your generation tool an `authorization` argument, and describe it so the agent knows to
    fill it:

    ```json Tool definition theme={null}
    {
      "name": "generate_image",
      "description": "Generate an image. If the prompt names or shows a real person, first call check_use and pass its authorization.token here, with the same prompt.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "prompt": { "type": "string" },
          "instructions": { "type": "array", "items": { "type": "string" }, "description": "The check's generation_instructions." },
          "authorization": { "type": "string", "description": "authorization.token from check_use." }
        },
        "required": ["prompt"]
      }
    }
    ```

    The agent then calls it with the prompt it checked, the instructions and the token:

    ```json tools/call theme={null}
    {
      "jsonrpc": "2.0",
      "id": 4,
      "method": "tools/call",
      "params": {
        "name": "generate_image",
        "arguments": {
          "prompt": "Maya Okafor on a mountain trail at sunrise",
          "instructions": ["Keep Maya Okafor the same person: face, skin tone and build unchanged.", "Do not show alcohol or tobacco."],
          "authorization": "eyJhbGciOiJFUzI1NiIs…"
        }
      }
    }
    ```
  </Step>

  <Step title="Verify the token in the tool">
    Before the tool runs anything that names a person, it verifies the token offline against the
    [JWKS](https://app.reflections.ai/.well-known/jwks.json): the signature, `typ`, `iss`, that
    `aud` is your account, `exp` and `iat` with at most 60 seconds of leeway, that `tlp.ver` is
    `1.x` and every obligation type is one it handles, that the model matches, and that it has run
    fewer than `max_generations` for this token's `jti`.

    <CodeGroup>
      ```typescript TypeScript theme={null}
      // npm install jose
      import { createRemoteJWKSet, jwtVerify } from "jose";

      // The JWKS is served with max-age=3600, and a key is published 30 days before it signs, so
      // refetch for an unknown kid at most once an hour (TLP-TOK-9).
      const JWKS = createRemoteJWKSet(new URL("https://app.reflections.ai/.well-known/jwks.json"), {
        cacheMaxAge: 3_600_000,
        cooldownDuration: 3_600_000,
      });
      // The obligation types of TLP 1.0. Reject a token that carries one you do not handle.
      const OBLIGATIONS = new Set([
        "generation_check", "hold_until_release", "draft_watermark", "generation_instructions",
        "disclosure", "attribution", "c2pa_manifest", "no_training", "delete_material", "usage_report",
      ]);
      const ACCOUNT = "acct_7p4r6s9t0wxyhk5zba2q8v1m3n"; // From GET /v1/account
      const MODEL = "image-model-3"; // The model this tool runs
      const used = new Map<string, number>(); // Use a shared store in production

      type Tlp = {
        ver: string;
        decision: string;
        max_generations: number;
        execution: { provider: string; model: string };
        obligations: string[];
      };

      export async function verifyAuthorization(token: string) {
        const { payload } = await jwtVerify<{ tlp: Tlp }>(token, JWKS, {
          issuer: "https://app.reflections.ai",
          audience: ACCOUNT,
          typ: "tlp-auth+jwt",
          algorithms: ["ES256"],
          clockTolerance: 60,
          maxTokenAge: "15m", // also rejects an iat in the future
          requiredClaims: ["exp", "iat", "jti", "sub"],
        });
        const { tlp } = payload;
        if (tlp.ver.split(".")[0] !== "1") throw new Error("Unsupported protocol version.");
        if (!tlp.obligations.every((type) => OBLIGATIONS.has(type))) throw new Error("Unknown obligation.");
        if (tlp.decision !== "allow") throw new Error("The token is not an allow.");
        if (tlp.execution.model !== MODEL) throw new Error("The token is for a different model.");

        const count = used.get(payload.jti!) ?? 0;
        if (count >= payload.tlp.max_generations) throw new Error("This authorization is used up.");
        used.set(payload.jti!, count + 1);
        return payload; // payload.jti is the check ID
      }
      ```

      ```python Python theme={null}
      import time
      import jwt  # pip install "pyjwt[crypto]"

      JWKS = jwt.PyJWKClient("https://app.reflections.ai/.well-known/jwks.json", lifespan=3600)
      refetch_after = 0.0
      # The obligation types of TLP 1.0. Reject a token that carries one you do not handle.
      OBLIGATIONS = {
          "generation_check", "hold_until_release", "draft_watermark", "generation_instructions",
          "disclosure", "attribution", "c2pa_manifest", "no_training", "delete_material", "usage_report",
      }

      def signing_key(token: str):
          """The token's key. Refetches the JWKS for an unknown kid at most once an hour (TLP-TOK-9)."""
          global refetch_after
          kid = jwt.get_unverified_header(token).get("kid")
          keys = {k.key_id: k.key for k in JWKS.get_signing_keys()}
          if kid not in keys and time.time() >= refetch_after:
              refetch_after = time.time() + 3600
              keys = {k.key_id: k.key for k in JWKS.get_signing_keys(refresh=True)}
          if kid not in keys:
              raise PermissionError("unknown_kid")
          return keys[kid]

      ACCOUNT = "acct_7p4r6s9t0wxyhk5zba2q8v1m3n"  # From GET /v1/account
      MODEL = "image-model-3"  # The model this tool runs
      used: dict[str, int] = {}  # Use a shared store in production

      def verify_authorization(token: str) -> dict:
          if jwt.get_unverified_header(token).get("typ") != "tlp-auth+jwt":
              raise PermissionError("Not an authorization token.")
          claims = jwt.decode(
              token,
              signing_key(token),
              algorithms=["ES256"],
              audience=ACCOUNT,
              issuer="https://app.reflections.ai",
              leeway=60,
              options={"require": ["exp", "iat", "jti", "sub"]},  # also rejects an iat in the future
          )
          tlp = claims["tlp"]
          if str(tlp["ver"]).split(".")[0] != "1":
              raise PermissionError("Unsupported protocol version.")
          if not set(tlp["obligations"]) <= OBLIGATIONS:
              raise PermissionError("Unknown obligation.")
          if tlp["decision"] != "allow":
              raise PermissionError("The token is not an allow.")
          if tlp["execution"]["model"] != MODEL:
              raise PermissionError("The token is for a different model.")

          count = used.get(claims["jti"], 0)
          if count >= tlp["max_generations"]:
              raise PermissionError("This authorization is used up.")
          used[claims["jti"]] = count + 1
          return claims  # claims["jti"] is the check ID
      ```
    </CodeGroup>

    A verified token decodes to claims like these:

    ```json theme={null}
    {
      "iss": "https://app.reflections.ai",
      "aud": "acct_7p4r6s9t0wxyhk5zba2q8v1m3n",
      "sub": "acct_7p4r6s9t0wxyhk5zba2q8v1m3n",
      "jti": "chk_4gnt7r0k2m5q8s1v3x6z9b2d4f",
      "iat": 1792260000,
      "exp": 1792260900,
      "tlp": {
        "ver": "1.0",
        "decision": "allow",
        "talent": ["tal_9t0wxyhk5zba2q8v1m3n7p4r6s"],
        "facets": ["fct_5zba2q8v1m3n7p4r6s9t0wxyhk"],
        "licenses": [
          {
            "talent": "tal_9t0wxyhk5zba2q8v1m3n7p4r6s",
            "license": "lic_97bb8qgh1b9zk81anbjkmprxh4",
            "revision": "lrv_11knr0923nvdw36qe404d6c55d",
            "terms_digest": "sha256:4036ac3ffc579dc70d5a9132242e3cb588007d08bee1b2b92812b2d4e3ff7f4e"
          }
        ],
        "request_digest": "sha256:634723b1fb87f1214ceb34c1df68d3d6210aa4c52621ace1da1e0c3030e9aae0",
        "use": {
          "modality": "image",
          "kind": "depiction",
          "purpose": "advertising",
          "commercial": true,
          "channels": ["social"],
          "territories": ["US"]
        },
        "max_generations": 1,
        "execution": { "provider": "example-image-provider", "model": "image-model-3" },
        "eu": "3f9c1a7e5b2d8f4061c3e9a7b5d2f8016e4c9a3b7d1f5e2c8a6b4d0f9e3c7a15",
        "obligations": ["generation_check", "generation_instructions", "disclosure", "c2pa_manifest"]
      }
    }
    ```

    If verification fails, return a tool error and generate nothing. The agent can run
    `check_use` again:

    ```json result theme={null}
    {
      "jsonrpc": "2.0",
      "id": 4,
      "result": {
        "content": [{ "type": "text", "text": "Authorization rejected: The token is for a different model. Call check_use again." }],
        "isError": true
      }
    }
    ```
  </Step>

  <Step title="Generate and submit the output from the tool">
    Generate with the prompt and the instructions. The tool holds the bytes, so it submits them
    itself with your secret key, naming the check from the token's `jti`. It returns the image to
    the agent only when the generation is `allow`.

    <CodeGroup>
      ```bash cURL theme={null}
      # CHECK_ID is the verified token's jti. printf is a shell builtin, so the image's base64 is
      # not limited by the argument size.
      printf '{
        "check": "%s",
        "candidate_index": 0,
        "artifact": { "data": "%s", "sha256": "%s", "media_type": "image/png" },
        "execution": { "provider": "example-image-provider", "model": "image-model-3" }
      }' "$CHECK_ID" "$(base64 < output.png | tr -d '\n')" "$(shasum -a 256 output.png | cut -d' ' -f1)" |
      curl -sS --fail-with-body https://app.reflections.ai/v1/generations \
        -H "Authorization: Bearer $REFLECTIONS_KEY" \
        -H "Reflections-Version: 2026-10-15" \
        -H "Idempotency-Key: generation-$CHECK_ID-0" \
        -H "Content-Type: application/json" \
        --data-binary @-
      ```

      ```typescript TypeScript theme={null}
      import { createHash } from "node:crypto";

      async function submitOutput(checkId: string, png: Buffer) {
        const res = await fetch("https://app.reflections.ai/v1/generations", {
          method: "POST",
          headers: {
            Authorization: `Bearer ${process.env.REFLECTIONS_KEY}`,
            "Reflections-Version": "2026-10-15",
            // One generation per check: if the agent retries the tool call, the first result replays.
            "Idempotency-Key": `generation-${checkId}-0`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            check: checkId,
            candidate_index: 0,
            artifact: {
              data: png.toString("base64"),
              sha256: createHash("sha256").update(png).digest("hex"),
              media_type: "image/png",
            },
            execution: { provider: "example-image-provider", model: MODEL },
          }),
        });
        if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
        return res.json();
      }

      // const claims = await verifyAuthorization(args.authorization);
      // const png = await runModel(args.prompt, args.instructions);
      // const generation = await submitOutput(claims.jti!, png);
      // if (generation.decision !== "allow") return a tool error with generation.reasons.
      ```

      ```python Python theme={null}
      import base64, hashlib

      def submit_output(check_id: str, png: bytes) -> dict:
          res = requests.post(
              "https://app.reflections.ai/v1/generations",
              headers={
                  "Authorization": f"Bearer {os.environ['REFLECTIONS_KEY']}",
                  "Reflections-Version": "2026-10-15",
                  # One generation per check: if the agent retries the tool call, the first result replays.
                  "Idempotency-Key": f"generation-{check_id}-0",
              },
              json={
                  "check": check_id,
                  "candidate_index": 0,
                  "artifact": {
                      "data": base64.b64encode(png).decode(),
                      "sha256": hashlib.sha256(png).hexdigest(),
                      "media_type": "image/png",
                  },
                  "execution": {"provider": "example-image-provider", "model": MODEL},
              },
          )
          res.raise_for_status()
          return res.json()

      # claims = verify_authorization(args["authorization"])
      # png = run_model(args["prompt"], args["instructions"])
      # generation = submit_output(claims["jti"], png)
      # if generation["decision"] != "allow": return a tool error with generation["reasons"].
      ```
    </CodeGroup>

    ```json theme={null}
    {
      "id": "gen_mpejjah645t5cddvrtqav51936",
      "object": "generation",
      "livemode": false,
      "check": "chk_4gnt7r0k2m5q8s1v3x6z9b2d4f",
      "status": "allowed",
      "decision": "allow",
      "assurance": "observed",
      "receipt": "rcpt_2s36m56gwb0chzeeem8r2k2vbd",
      "c2pa": {
        "label": "ai.reflections.license",
        "data": {
          "receipt": "eyJhbGciOiJFUzI1NiIs…",
          "verify_url": "https://app.reflections.ai/r/rcpt_2s36m56gwb0chzeeem8r2k2vbd"
        }
      }
    }
    ```

    Embed `c2pa` in the image's C2PA manifest, as in [Embedding provenance](/build/embedding-provenance),
    and return the image with the disclosure text. When the agent holds the output instead of the
    tool, it calls `submit_generation` with the same arguments. A video, or an output held for
    release, returns `status: "checking"` or `"held"`; the agent reads the decision later with
    `get_generation`, passing `{ "generation_id": "gen_…" }`, and delivers only on `allowed` or
    `released`.
  </Step>

  <Step title="Verify the receipt from the agent">
    Anyone downstream can check what the agent produced. The agent calls `verify_receipt` with the
    receipt ID:

    ```json tools/call theme={null}
    {
      "jsonrpc": "2.0",
      "id": 5,
      "method": "tools/call",
      "params": { "name": "verify_receipt", "arguments": { "receipt": "rcpt_2s36m56gwb0chzeeem8r2k2vbd" } }
    }
    ```

    The result's `structuredContent` is a verification with `result: "verified"` and the receipt's
    live `use_status`.
  </Step>
</Steps>

<Warning>
  An agent can skip `check_use`. A tool that generates without verifying the token gives the talent
  no protection, so verify on every call that can produce a real person.
</Warning>

## Next

<Columns cols={2}>
  <Card title="Agent builders" icon="robot" href="/guides/agent-builders">
    Token passing, tool-side verification and caching in production.
  </Card>

  <Card title="MCP tools" icon="toolbox" href="/api-reference/mcp/tools">
    Each tool, its arguments and results.
  </Card>

  <Card title="Authorization token" icon="key" href="/api-reference/formats/authorization-token">
    Every claim, and the verification rules.
  </Card>

  <Card title="Model providers" icon="server" href="/guides/model-providers">
    Verifying tokens at an inference API.
  </Card>
</Columns>


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