# Set a unique key once per new action; persist it and reuse it for every retry.
: "${REFLECTIONS_KEY:?Set REFLECTIONS_KEY before running this example}"
: "${REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY:?Set REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY before running this example}"
curl --fail-with-body --max-time 30 -X POST 'https://app.reflections.ai/v1/generations' \
-H "Authorization: Bearer $REFLECTIONS_KEY" \
-H "Reflections-Version: 2026-10-15" \
-H "Idempotency-Key: $REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
--data-raw '{
"check": "chk_3m7p4r6s9t0wxyhk5zba2q8v1n",
"candidate_index": 0,
"artifact": {
"data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAADUlEQVQImWP4////fwAJ+wP9CNHoHgAAAABJRU5ErkJggg==",
"sha256": "20c1e1ec7a62d54fe8191b5c509e7a2c88b520a429bc1b74c55e7ccf72cbcc6d",
"media_type": "image/png"
}
}'import { env, exit } from "node:process";
// Set a unique key once per new action; persist it and reuse it for every retry.
const apiKey = env.REFLECTIONS_KEY;
if (!apiKey) throw new Error("Set REFLECTIONS_KEY before running this example");
const idempotencyKey = env.REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY;
if (!idempotencyKey) throw new Error("Set REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY before running this example");
const response = await fetch("https://app.reflections.ai/v1/generations", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Reflections-Version": "2026-10-15",
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
"check": "chk_3m7p4r6s9t0wxyhk5zba2q8v1n",
"candidate_index": 0,
"artifact": {
"data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAADUlEQVQImWP4////fwAJ+wP9CNHoHgAAAABJRU5ErkJggg==",
"sha256": "20c1e1ec7a62d54fe8191b5c509e7a2c88b520a429bc1b74c55e7ccf72cbcc6d",
"media_type": "image/png"
}
}),
signal: AbortSignal.timeout(30_000),
});
const responseBody = await response.text();
if (!response.ok) {
console.error(`HTTP ${response.status}: ${responseBody}`);
exit(1);
}
console.log(responseBody);
import json
import os
from urllib.error import HTTPError
from urllib.request import Request, urlopen
# Set a unique key once per new action; persist it and reuse it for every retry.
apiKey = os.environ["REFLECTIONS_KEY"]
if not apiKey:
raise ValueError("Set REFLECTIONS_KEY before running this example")
idempotencyKey = os.environ["REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY"]
if not idempotencyKey:
raise ValueError("Set REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY before running this example")
headers = {
"Authorization": f"Bearer {apiKey}",
"Reflections-Version": "2026-10-15",
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
}
data = json.dumps({
"check": "chk_3m7p4r6s9t0wxyhk5zba2q8v1n",
"candidate_index": 0,
"artifact": {
"data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAADUlEQVQImWP4////fwAJ+wP9CNHoHgAAAABJRU5ErkJggg==",
"sha256": "20c1e1ec7a62d54fe8191b5c509e7a2c88b520a429bc1b74c55e7ccf72cbcc6d",
"media_type": "image/png",
},
}).encode("utf-8")
request = Request("https://app.reflections.ai/v1/generations",
method="POST", headers=headers, data=data)
try:
with urlopen(request, timeout=30) as response:
print(response.read().decode("utf-8"))
except HTTPError as error:
error_body = error.read().decode("utf-8")
raise RuntimeError(f"HTTP {error.code}: {error_body}") from error
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Set a unique key once per new action; persist it and reuse it for every retry.
apiKey := os.Getenv("REFLECTIONS_KEY")
if apiKey == "" {
panic("Set REFLECTIONS_KEY before running this example")
}
idempotencyKey := os.Getenv("REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY")
if idempotencyKey == "" {
panic("Set REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY before running this example")
}
body := strings.NewReader(`{
"check": "chk_3m7p4r6s9t0wxyhk5zba2q8v1n",
"candidate_index": 0,
"artifact": {
"data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAADUlEQVQImWP4////fwAJ+wP9CNHoHgAAAABJRU5ErkJggg==",
"sha256": "20c1e1ec7a62d54fe8191b5c509e7a2c88b520a429bc1b74c55e7ccf72cbcc6d",
"media_type": "image/png"
}
}`)
request, err := http.NewRequest("POST", "https://app.reflections.ai/v1/generations", body)
if err != nil {
panic(err)
}
request.Header.Set("Authorization", "Bearer " + apiKey)
request.Header.Set("Reflections-Version", "2026-10-15")
request.Header.Set("Idempotency-Key", idempotencyKey)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
response, err := client.Do(request)
if err != nil {
panic(err)
}
defer response.Body.Close()
responseBody, err := io.ReadAll(response.Body)
if err != nil {
panic(err)
}
if response.StatusCode < 200 || response.StatusCode >= 300 {
panic(fmt.Sprintf("HTTP %d: %s", response.StatusCode, responseBody))
}
fmt.Print(string(responseBody))
}
{
"id": "gen_6s9t0wxyhk5zba2q8v1m3n7p4r",
"object": "generation",
"livemode": true,
"check": "chk_3m7p4r6s9t0wxyhk5zba2q8v1n",
"session": null,
"candidate_index": 0,
"status": "held",
"decision": "escalate",
"artifact": {
"file": "file_2c4e6g8j0k2m4n6p8q0r2s4t6v",
"sha256": "9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a",
"media_type": "image/png",
"duration_ms": null
},
"execution": {
"provider": "example-image-provider",
"model": "image-model-3",
"job_id": "job_88213"
},
"findings": [
{
"term": "license:lic_k5zba2q8v1m3n7p4r6s9t0wxyh/terms.requiredAttributes",
"attribute": "identity.samePerson",
"result": "preserved"
},
{
"term": "license:lic_k5zba2q8v1m3n7p4r6s9t0wxyh/terms.adjustments",
"attribute": "appearance.wardrobe",
"result": "preserved"
},
{
"term": "license:lic_k5zba2q8v1m3n7p4r6s9t0wxyh/terms.neverExtras",
"rule": "Do not show alcohol or tobacco.",
"result": "preserved"
}
],
"unchecked_terms": [],
"reasons": [],
"obligations": [
{
"type": "hold_until_release",
"reviewer": "talent"
},
{
"type": "draft_watermark"
}
],
"escalation": {
"id": "esc_hk5zba2q8v1m3n7p4r6s9t0wxy",
"reason": "release_review",
"reviewer": "talent",
"status": "open",
"expires_at": "2026-10-24T18:00:12Z",
"resolved_at": null,
"note": null
},
"assurance": "observed",
"receipt": null,
"c2pa": null,
"watermark": null,
"metadata": {},
"created_at": "2026-10-17T18:00:12Z",
"decided_at": null
}Create a generation
Test mode available. Live mode available when configured. Source support does not
establish production activation. Until activation, a live key receives 403
live_not_activated. Sandbox decisions are simulated; inline base64 or
SHA-256 attestation is supported, but file IDs and URL fetching are not.
The initial live implementation accepts one inline, still PNG, at most 7 MiB
(7,340,032 decoded bytes), in a JSON body of at most 10 MiB. Send its exact lowercase
SHA-256, candidate_index: 0, and the check’s declared provider and model. A supplied
candidate count must be one; job_id is optional and may be assigned after the provider
starts. Digest-only attestation, files, URLs, sessions, video, audio, transcripts and
watermark inputs are unsupported. Reflections evaluates the submitted bytes; it does not
render or fetch the output.
Send an Idempotency-Key. The same caller key must hold both checks:write and
generations:write. Live admission requires the new signed external-image and retention
terms and an activated live signer, as described for checks.
Send the allowed check ID; the server reads its stored authorization token. There is
no separate token field in this request. The first successful submission must arrive
before authorization.expires_at, even if the provider job started earlier; otherwise
it fails with 409 check_expired.
A successful POST always returns the immutable accepted 202 response, including on
replay. Poll the generation for checking, held, released, denied or expired.
Every image requires the talent’s actual release before decision: "allow"; upload or
evaluator success alone never permits delivery. No live webhooks are delivered.
Broader protocol contract: the remaining description and examples include planned forms beyond that initial live implementation.
Checks an output before anyone sees it.
The output is checked against the license its check relied on: the bytes are bound to the
check and candidate, and the talent’s protected attributes and rules are checked on the
bytes. Only the talent the check covered are checked. Every generation names a check, or
a session for an output made in a live session.
Images, speech up to 60 seconds and text return a decision in the response. Video and longer
audio return status: checking, and the decision arrives as generation.allowed or
generation.denied. When the license has a person release every output, they return
status: held instead, and the decision arrives as generation.released or
generation.denied.
Deliver nothing until the decision is allow. Embed the c2pa assertion in the output’s C2PA
manifest before you deliver it. Send c2pa_signer with the thumbprint of the certificate
that will sign that manifest’s claim: the receipt then names it as tlp.c2pa_signer, and a
verifier that checks the manifest can reject a copy of the assertion in a manifest another
certificate signed. POST /v1/verifications does not read manifests yet. Without it, the
receipt is unbound to any file.
Restricted keys need generations:write.
# Set a unique key once per new action; persist it and reuse it for every retry.
: "${REFLECTIONS_KEY:?Set REFLECTIONS_KEY before running this example}"
: "${REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY:?Set REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY before running this example}"
curl --fail-with-body --max-time 30 -X POST 'https://app.reflections.ai/v1/generations' \
-H "Authorization: Bearer $REFLECTIONS_KEY" \
-H "Reflections-Version: 2026-10-15" \
-H "Idempotency-Key: $REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
--data-raw '{
"check": "chk_3m7p4r6s9t0wxyhk5zba2q8v1n",
"candidate_index": 0,
"artifact": {
"data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAADUlEQVQImWP4////fwAJ+wP9CNHoHgAAAABJRU5ErkJggg==",
"sha256": "20c1e1ec7a62d54fe8191b5c509e7a2c88b520a429bc1b74c55e7ccf72cbcc6d",
"media_type": "image/png"
}
}'import { env, exit } from "node:process";
// Set a unique key once per new action; persist it and reuse it for every retry.
const apiKey = env.REFLECTIONS_KEY;
if (!apiKey) throw new Error("Set REFLECTIONS_KEY before running this example");
const idempotencyKey = env.REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY;
if (!idempotencyKey) throw new Error("Set REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY before running this example");
const response = await fetch("https://app.reflections.ai/v1/generations", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Reflections-Version": "2026-10-15",
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
"check": "chk_3m7p4r6s9t0wxyhk5zba2q8v1n",
"candidate_index": 0,
"artifact": {
"data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAADUlEQVQImWP4////fwAJ+wP9CNHoHgAAAABJRU5ErkJggg==",
"sha256": "20c1e1ec7a62d54fe8191b5c509e7a2c88b520a429bc1b74c55e7ccf72cbcc6d",
"media_type": "image/png"
}
}),
signal: AbortSignal.timeout(30_000),
});
const responseBody = await response.text();
if (!response.ok) {
console.error(`HTTP ${response.status}: ${responseBody}`);
exit(1);
}
console.log(responseBody);
import json
import os
from urllib.error import HTTPError
from urllib.request import Request, urlopen
# Set a unique key once per new action; persist it and reuse it for every retry.
apiKey = os.environ["REFLECTIONS_KEY"]
if not apiKey:
raise ValueError("Set REFLECTIONS_KEY before running this example")
idempotencyKey = os.environ["REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY"]
if not idempotencyKey:
raise ValueError("Set REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY before running this example")
headers = {
"Authorization": f"Bearer {apiKey}",
"Reflections-Version": "2026-10-15",
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
}
data = json.dumps({
"check": "chk_3m7p4r6s9t0wxyhk5zba2q8v1n",
"candidate_index": 0,
"artifact": {
"data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAADUlEQVQImWP4////fwAJ+wP9CNHoHgAAAABJRU5ErkJggg==",
"sha256": "20c1e1ec7a62d54fe8191b5c509e7a2c88b520a429bc1b74c55e7ccf72cbcc6d",
"media_type": "image/png",
},
}).encode("utf-8")
request = Request("https://app.reflections.ai/v1/generations",
method="POST", headers=headers, data=data)
try:
with urlopen(request, timeout=30) as response:
print(response.read().decode("utf-8"))
except HTTPError as error:
error_body = error.read().decode("utf-8")
raise RuntimeError(f"HTTP {error.code}: {error_body}") from error
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Set a unique key once per new action; persist it and reuse it for every retry.
apiKey := os.Getenv("REFLECTIONS_KEY")
if apiKey == "" {
panic("Set REFLECTIONS_KEY before running this example")
}
idempotencyKey := os.Getenv("REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY")
if idempotencyKey == "" {
panic("Set REFLECTIONS_CREATE_GENERATION_IDEMPOTENCY_KEY before running this example")
}
body := strings.NewReader(`{
"check": "chk_3m7p4r6s9t0wxyhk5zba2q8v1n",
"candidate_index": 0,
"artifact": {
"data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAADUlEQVQImWP4////fwAJ+wP9CNHoHgAAAABJRU5ErkJggg==",
"sha256": "20c1e1ec7a62d54fe8191b5c509e7a2c88b520a429bc1b74c55e7ccf72cbcc6d",
"media_type": "image/png"
}
}`)
request, err := http.NewRequest("POST", "https://app.reflections.ai/v1/generations", body)
if err != nil {
panic(err)
}
request.Header.Set("Authorization", "Bearer " + apiKey)
request.Header.Set("Reflections-Version", "2026-10-15")
request.Header.Set("Idempotency-Key", idempotencyKey)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
response, err := client.Do(request)
if err != nil {
panic(err)
}
defer response.Body.Close()
responseBody, err := io.ReadAll(response.Body)
if err != nil {
panic(err)
}
if response.StatusCode < 200 || response.StatusCode >= 300 {
panic(fmt.Sprintf("HTTP %d: %s", response.StatusCode, responseBody))
}
fmt.Print(string(responseBody))
}
{
"id": "gen_6s9t0wxyhk5zba2q8v1m3n7p4r",
"object": "generation",
"livemode": true,
"check": "chk_3m7p4r6s9t0wxyhk5zba2q8v1n",
"session": null,
"candidate_index": 0,
"status": "held",
"decision": "escalate",
"artifact": {
"file": "file_2c4e6g8j0k2m4n6p8q0r2s4t6v",
"sha256": "9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a",
"media_type": "image/png",
"duration_ms": null
},
"execution": {
"provider": "example-image-provider",
"model": "image-model-3",
"job_id": "job_88213"
},
"findings": [
{
"term": "license:lic_k5zba2q8v1m3n7p4r6s9t0wxyh/terms.requiredAttributes",
"attribute": "identity.samePerson",
"result": "preserved"
},
{
"term": "license:lic_k5zba2q8v1m3n7p4r6s9t0wxyh/terms.adjustments",
"attribute": "appearance.wardrobe",
"result": "preserved"
},
{
"term": "license:lic_k5zba2q8v1m3n7p4r6s9t0wxyh/terms.neverExtras",
"rule": "Do not show alcohol or tobacco.",
"result": "preserved"
}
],
"unchecked_terms": [],
"reasons": [],
"obligations": [
{
"type": "hold_until_release",
"reviewer": "talent"
},
{
"type": "draft_watermark"
}
],
"escalation": {
"id": "esc_hk5zba2q8v1m3n7p4r6s9t0wxy",
"reason": "release_review",
"reviewer": "talent",
"status": "open",
"expires_at": "2026-10-24T18:00:12Z",
"resolved_at": null,
"note": null
},
"assurance": "observed",
"receipt": null,
"c2pa": null,
"watermark": null,
"metadata": {},
"created_at": "2026-10-17T18:00:12Z",
"decided_at": null
}Authorizations
Your secret key, rk_test_… or rk_live_…, sent as Authorization: Bearer <key>. Keep it
on your server. Restricted keys carry only the scopes you give them.
Headers
The dated API version. Defaults to the version your account is pinned to.
"2026-10-15"
Act for a connected licensee. The value is the licensee's account ID. Requires an active connection between that account and yours.
^acct_[0-9a-hjkmnp-tv-z]{26}$A unique key for this operation, up to 255 characters. In test mode, POST keys are scoped to your account and mode and bind the endpoint and body for 24 hours. A different endpoint or body with the same key is refused.
Initial live check and generation creation require a nonempty key. Each operation has a
separate account-scoped namespace; retain the same API key, body and idempotency key for
retries within 24 hours of creation. A pending check returns 202 with status: "checking".
Completed checks replay their sealed response; successful generation POSTs replay the
original accepted 202, so use GET for current state. Live verification is a read-only
query that returns a fresh verification ID on each call, without exact response caching.
The broader delegated-account contract also scopes keys to Reflections-Account;
delegated requests and the other planned live POST operations remain unsupported.
255Body
- Option 1
- Option 2
The check this output was made under. Required unless session is set.
^chk_[0-9a-hjkmnp-tv-z]{26}$The output's bytes, as a file ID, a URL or base64 data, with their SHA-256.
Show child attributes
Show child attributes
The session this output was made in, instead of a check.
^ses_[0-9a-hjkmnp-tv-z]{26}$Which of the check's execution.candidates this is, from 0. Omit under reuse.
0 <= x <= 15The run that made the output. Send job_id when the provider gives one.
Show child attributes
Show child attributes
Words spoken in the output, if you have them. Reflections transcribes regardless.
32000A watermark you applied, so verification can join it to this generation.
Show child attributes
Show child attributes
The certificate that will sign the output's C2PA claim. The receipt signs its
thumbprint as tlp.c2pa_signer, as given; Reflections does not check it against a
certificate. A verifier that checks the manifest can then reject the assertion in a
manifest another certificate signed. Without it, the receipt is evidence of a decision,
not of a particular file.
Show child attributes
Show child attributes
Your own keys, returned on the generation.
Show child attributes
Show child attributes
Response
The generation.
The generation's ID, gen_ followed by 26 characters.
^gen_[0-9a-hjkmnp-tv-z]{26}$Always generation.
"generation"true in live mode, false in test mode.
Where the output is. Deliver only on allowed or released.
checking, allowed, denied, held, released, expired, withdrawn observed: Reflections checked these bytes. attested: Reflections recorded what you stated without checking it.
observed, attested When the output was submitted.
The check the output was made under. Null for an output made in a session.
^chk_[0-9a-hjkmnp-tv-z]{26}$The session the output was made in. Null for an output made under a check.
^ses_[0-9a-hjkmnp-tv-z]{26}$Which of the check's candidates this is, from 0. Null under reuse and for an output
made in a session.
allow, deny, or escalate while a reviewer must release the output. Null while
status is checking.
allow, deny, escalate The output that was checked.
Show child attributes
Show child attributes
The run that made the output, as you stated it.
Show child attributes
Show child attributes
Each rule checked on the bytes, with its result.
Show child attributes
Show child attributes
Addresses of rules the talent wrote that could not be checked on these bytes. They do not withhold the output.
Where a reason or finding rests, written as an address: license:<id>/terms.<key> for a
term, talent:<id> for a talent's standing boundary.
Why the output was denied or expired. Empty otherwise.
Show child attributes
Show child attributes
What you must do with this output, such as embed c2pa and show the disclosure, or keep
it private while held.
Show child attributes
Show child attributes
The release review, when a person must release the output. Null otherwise.
Show child attributes
Show child attributes
The receipt that records the decision. Null while the output is checking or held,
and after a withdrawal from review.
^rcpt_[0-9a-hjkmnp-tv-z]{26}$The assertion to embed in the output's C2PA manifest before you deliver it. Null unless
decision is allow.
Show child attributes
Show child attributes
The watermark you registered on the generation. Null if none.
Show child attributes
Show child attributes
Your metadata from the request.
Show child attributes
Show child attributes
When the output was allowed, released, denied or expired. Null otherwise.