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

# Revocation

> What a revocation stops, what it leaves in place, how fast it reaches you, and how you acknowledge it.

<Note>
  Planned protocol reference. The Talent API sandbox does not serve revocation feeds, lifecycle
  mutations or acknowledgements. The consent duties described below are not evidence of deployed
  API support.
  Start with the [runnable quickstart](/quickstart) and [sandbox support matrix](/sandbox).
</Note>

A talent can narrow consent at any time. A **revocation** is the one object that records every way
it narrows: a license revoked, a facet withdrawn, a representative's mandate ended, or consent
withdrawn everywhere. Your platform stops what the revocation covers and acknowledges that it did.

## Scopes

| `scope` | What narrowed | `targets` |
| - | - | - |
| `license` | One license was revoked | `licenses` |
| `facet` | A facet was withdrawn, which narrows every license that covers it | `facets`, `licenses` |
| `mandate` | A representative's authority ended, and any licenses the talent revoked with it | `mandate`, `licenses` |
| `talent` | The talent withdrew consent everywhere | `talent` |

`initiated_by` is `talent`, `representative` or `reflections`. `for_cause` is `true` when the
revocation is for a breach of the license, and `cause` then states the breach. `note` carries any
note from whoever revoked it. The talent revokes in the Talent Portal, at any scope. A
representative whose mandate has `revoke` can revoke one license at a time with
[`POST /v1/revocations`](/api-reference/revocations/create-a-revocation). The same object reaches
you either way.

## What it stops

At `effective_at`, Reflections:

* denies new checks under the targets with `license_revoked`;
* denies open escalations, and denies held outputs with `license_revoked` so they can never be
  released, sending [`generation.denied`](/api-reference/webhooks/generation-denied) for each;
* terminates live sessions, pushing `session.terminated` on the session stream;
* ends reuse windows, and refuses renewals of session tokens.

An authorization already in a tool's hands cannot be recalled: it verifies offline until its
`expires_at`, at most 15 minutes. That is why your platform must purge cached decisions and stop
generating as soon as the revocation arrives, rather than wait for tokens to expire.

## What it leaves

Outputs already released follow the license's `post_revocation` policy, copied onto the revocation:

| Policy | Released outputs | Receipt `use_status` |
| - | - | - |
| `retain` | Stay usable within their original scope and window | `permitted`, then `expired` when the window ends |
| `cease_distribution` | No new placements after `effective_at` | `ceased` |
| `withdraw` | Taken down by `withdraw_by`; only when `for_cause` is `true` | `withdrawn` |

A receipt stays valid evidence that a past output was licensed when it was made. Its `use_status`
says what use is permitted now, so a distribution platform reading it sees the change.

## What you must do

`required_actions` lists the actions this revocation needs from your platform:

| Action | What you do |
| - | - |
| `stop_generation` | Issue nothing new under the targets |
| `purge_cached_decisions` | Drop cached checks and authorizations, and end reuse windows on your side |
| `delete_provider_copies` | Delete the copies listed in `provider_copies` at each model provider, such as a voice |
| `cease_distribution` | Place released outputs nowhere new |
| `withdraw_outputs` | Take released outputs down |

`affected` reports what the revocation cut at `effective_at`: `checks_denied`,
`generations_denied`, `sessions_terminated` and `released_outputs`.

## How fast

| Step | Deadline |
| - | - |
| `revocation.created` delivered | p95 5 seconds after `effective_at` |
| `session.terminated` pushed | p95 2 seconds after the revocation |
| Your session output stops | Within 1 second of `session.terminated` |
| Session tokens stop renewing | At once; a token lives at most 5 minutes |
| Authorization tokens expire | At most 15 minutes after issue |
| You read the revocation feed, if you cache decisions | At least every 60 seconds |
| You acknowledge every required action except `withdraw_outputs` | By `acknowledge_by`, 24 hours after `effective_at` |
| You acknowledge `withdraw_outputs`, when required | By `post_revocation.withdraw_by` |

[`revocation.created`](/api-reference/webhooks/revocation-created) is the fast path. The revocation
feed, [`GET /v1/revocations?after=rvk_…`](/api-reference/revocations/list-revocations), is the source
of truth: it returns every revocation after the one you name, oldest first, so a platform that
missed a webhook still sees everything.

## Acknowledging

Acknowledge with
[`POST /v1/revocations/{revocation_id}/acknowledgements`](/api-reference/revocations/acknowledge-a-revocation).
By `acknowledge_by`, list every action in `required_actions` except `withdraw_outputs`, and each
provider copy you deleted. When `withdraw_outputs` is required, acknowledge it in a later call, by
`post_revocation.withdraw_by`, once the outputs are down. Each call adds to `acknowledgements`, which
are shown on the talent's record.

A revocation stays `pending_acknowledgement` until every required action is acknowledged, then
becomes `acknowledged`. If either deadline passes first, it becomes `overdue`: you receive
[`revocation.overdue`](/api-reference/webhooks/revocation-overdue), and it is shown on the talent's
record and to your account contact.

## Suspension

A talent or representative can [suspend](/api-reference/licenses/suspend-a-license) a license
instead, for example during a dispute. A suspension is not a revocation: there is no revocation object and nothing to acknowledge. Checks deny
with `license_suspended`, live sessions are terminated, and renewals fail with `409 session_not_active`. When you receive
[`license.suspended`](/api-reference/webhooks/license-suspended), drop cached decisions under the
license. Held outputs stay held, and released outputs are unaffected. When the license resumes, you
receive `license.resumed` and checks are decided on its terms again.

## Example: Maya Okafor revokes a voice license

Northlight's voice license lets its users make personal speech in Maya Okafor's voice, and her voice
lives at a voice provider. She revokes the license:

```json theme={null}
{
  "id": "rvk_s9t0wxyhk5zba2q8v1m3n7p4r6",
  "object": "revocation",
  "scope": "license",
  "targets": {
    "talent": "tal_9t0wxyhk5zba2q8v1m3n7p4r6s",
    "licenses": ["lic_ba2q8v1m3n7p4r6s9t0wxyhk5z"],
    "facets": ["fct_v1m3n7p4r6s9t0wxyhk5zba2q8"],
    "mandate": null
  },
  "status": "pending_acknowledgement",
  "initiated_by": "talent",
  "for_cause": false,
  "cause": null,
  "note": null,
  "effective_at": "2026-10-17T18:03:40Z",
  "post_revocation": { "policy": "retain", "withdraw_by": null },
  "required_actions": ["stop_generation", "purge_cached_decisions", "delete_provider_copies"],
  "provider_copies": [
    { "facet": "fct_v1m3n7p4r6s9t0wxyhk5zba2q8", "provider": "example-voice-provider", "external_id": "voice_7Kq2mP9x" }
  ],
  "affected": { "checks_denied": 0, "generations_denied": 0, "sessions_terminated": 1, "released_outputs": 312 },
  "acknowledge_by": "2026-10-18T18:03:40Z",
  "acknowledgements": []
}
```

One live session was terminated within seconds. Northlight deletes the voice at the provider,
purges its caches, and acknowledges:

<CodeGroup>
  ```bash cURL theme={null}
  curl https://app.reflections.ai/v1/revocations/rvk_s9t0wxyhk5zba2q8v1m3n7p4r6/acknowledgements \
    -H "Authorization: Bearer rk_test_…" \
    -H "Reflections-Version: 2026-10-15" \
    -H "Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{
      "actions": ["stop_generation", "purge_cached_decisions", "delete_provider_copies"],
      "deleted_copies": [
        { "provider": "example-voice-provider", "external_id": "voice_7Kq2mP9x", "deleted_at": "2026-10-17T18:20:05Z" }
      ],
      "note": "Voice deleted at the provider. Caches purged."
    }'
  ```

  ```typescript TypeScript theme={null}
  const revocation = await fetch(
    "https://app.reflections.ai/v1/revocations/rvk_s9t0wxyhk5zba2q8v1m3n7p4r6/acknowledgements",
    {
      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({
        actions: ["stop_generation", "purge_cached_decisions", "delete_provider_copies"],
        deleted_copies: [
          { provider: "example-voice-provider", external_id: "voice_7Kq2mP9x", deleted_at: "2026-10-17T18:20:05Z" },
        ],
        note: "Voice deleted at the provider. Caches purged.",
      }),
    },
  ).then((r) => r.json());
  ```

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

  revocation = requests.post(
      "https://app.reflections.ai/v1/revocations/rvk_s9t0wxyhk5zba2q8v1m3n7p4r6/acknowledgements",
      headers={
          "Authorization": f"Bearer {os.environ['REFLECTIONS_KEY']}",
          "Reflections-Version": "2026-10-15",
          "Idempotency-Key": str(uuid.uuid4()),
      },
      json={
          "actions": ["stop_generation", "purge_cached_decisions", "delete_provider_copies"],
          "deleted_copies": [
              {"provider": "example-voice-provider", "external_id": "voice_7Kq2mP9x", "deleted_at": "2026-10-17T18:20:05Z"}
          ],
          "note": "Voice deleted at the provider. Caches purged.",
      },
  ).json()
  ```
</CodeGroup>

The 312 lines already released stay usable within their original scope, because the license says
`retain`.

## Related

<Columns cols={2}>
  <Card title="Handling revocations" icon="code" href="/build/handling-revocations">
    The feed, purges, provider copies and acknowledgements.
  </Card>

  <Card title="Failure modes" icon="triangle-exclamation" href="/concepts/failure-modes">
    Caching, outages and the revocation feed.
  </Card>

  <Card title="Revocation in the Standard" icon="book" href="/standard/revocation">
    The deadlines a conformant platform meets.
  </Card>

  <Card title="Provenance" icon="receipt" href="/concepts/provenance">
    Receipts and their live `use_status`.
  </Card>
</Columns>


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