# 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_VERIFICATION_IDEMPOTENCY_KEY:?Set REFLECTIONS_CREATE_VERIFICATION_IDEMPOTENCY_KEY before running this example}"
curl --fail-with-body --max-time 30 -X POST 'https://app.reflections.ai/v1/verifications' \
-H "Authorization: Bearer $REFLECTIONS_KEY" \
-H "Reflections-Version: 2026-10-15" \
-H "Idempotency-Key: $REFLECTIONS_CREATE_VERIFICATION_IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
--data-raw '{
"receipt": "rcpt_hk5zba2q8v1m3n7p4r6s9t0wxy"
}'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_VERIFICATION_IDEMPOTENCY_KEY;
if (!idempotencyKey) throw new Error("Set REFLECTIONS_CREATE_VERIFICATION_IDEMPOTENCY_KEY before running this example");
const response = await fetch("https://app.reflections.ai/v1/verifications", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Reflections-Version": "2026-10-15",
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
"receipt": "rcpt_hk5zba2q8v1m3n7p4r6s9t0wxy"
}),
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_VERIFICATION_IDEMPOTENCY_KEY"]
if not idempotencyKey:
raise ValueError("Set REFLECTIONS_CREATE_VERIFICATION_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({"receipt": "rcpt_hk5zba2q8v1m3n7p4r6s9t0wxy"}).encode("utf-8")
request = Request("https://app.reflections.ai/v1/verifications",
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_VERIFICATION_IDEMPOTENCY_KEY")
if idempotencyKey == "" {
panic("Set REFLECTIONS_CREATE_VERIFICATION_IDEMPOTENCY_KEY before running this example")
}
body := strings.NewReader(`{
"receipt": "rcpt_hk5zba2q8v1m3n7p4r6s9t0wxy"
}`)
request, err := http.NewRequest("POST", "https://app.reflections.ai/v1/verifications", 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": "vrf_8b0c2d4e6f8g0h2j4k6m8n0p2q",
"object": "verification",
"result": "verified",
"method": "manifest",
"match": null,
"manifest": {
"signer": "Northlight",
"assertion_present": true,
"signature_valid": true
},
"receipt": {
"id": "rcpt_hk5zba2q8v1m3n7p4r6s9t0wxy",
"object": "receipt",
"livemode": true,
"kind": "generation",
"subject": "gen_2q8v1m3n7p4r6s9t0wxyhk5zba",
"decision": "allow",
"assurance": "observed",
"talent": [
"tal_9t0wxyhk5zba2q8v1m3n7p4r6s"
],
"licenses": [
{
"talent": "tal_9t0wxyhk5zba2q8v1m3n7p4r6s",
"license": "lic_ba2q8v1m3n7p4r6s9t0wxyhk5z",
"license_revision": "lrv_7p4r6s9t0wxyhk5zba2q8v1m3n",
"terms_digest": "sha256:8e0a2c4e6b8d0f2a4c6e8b0d2f4a6c8e0b2d4f6a8c0e2b4d6f8a0c2e4b6d8f0a"
}
],
"request_digest": "sha256:d4f6b8a0c2e4d6f8b0a2c4e6d8f0b2a4c6e8d0f2b4a6c8e0d2f4b6a8c0e2d4f6",
"jws": "eyJhbGciOiJFUzI1NiIs…",
"use_status": "permitted",
"issued_at": "2026-10-17T18:06:03Z"
},
"detail": null,
"created_at": "2026-10-18T09:12:44Z"
}{
"type": "https://docs.reflections.ai/errors/parameter_invalid",
"title": "A parameter is invalid",
"status": 400,
"code": "parameter_invalid",
"param": "use.territories[1]",
"detail": "\"UK\" is not an ISO 3166-1 alpha-2 code. Use \"GB\".",
"request_id": "req_8f2k4m6p8r0t2v4x6z8b0d2f4h"
}{
"type": "https://docs.reflections.ai/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"code": "invalid_api_key",
"request_id": "req_8f2k4m6p8r0t2v4x6z8b0d2f4h"
}{
"type": "https://docs.reflections.ai/errors/idempotency_key_in_use",
"title": "A request with this key is still running",
"status": 409,
"code": "idempotency_key_in_use",
"detail": "Retry with the same key after a short wait.",
"request_id": "req_8f2k4m6p8r0t2v4x6z8b0d2f4h"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "invalid_request",
"request_id": "<string>",
"detail": "<string>",
"param": "<string>",
"doc_url": "<string>"
}{
"type": "https://docs.reflections.ai/errors/idempotency_key_reused",
"title": "This key was used for a different request",
"status": 422,
"code": "idempotency_key_reused",
"request_id": "req_8f2k4m6p8r0t2v4x6z8b0d2f4h"
}{
"type": "https://docs.reflections.ai/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"code": "rate_limited",
"request_id": "req_8f2k4m6p8r0t2v4x6z8b0d2f4h"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "invalid_request",
"request_id": "<string>",
"detail": "<string>",
"param": "<string>",
"doc_url": "<string>"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "invalid_request",
"request_id": "<string>",
"detail": "<string>",
"param": "<string>",
"doc_url": "<string>"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "invalid_request",
"request_id": "<string>",
"detail": "<string>",
"param": "<string>",
"doc_url": "<string>"
}Verify content
Test mode available. Live mode available when configured. Source support does not establish production activation. The initial live implementation accepts JSON with a receipt ID, receipt JWS or SHA-256. Receipt-ID and JWS verification are public; digest discovery requires the owning account’s live key. The response exposes the signed receipt and current use status without private request metadata. Sandbox verification requires a test key. Multipart files, manifests, URLs and fingerprints are unsupported in both modes. Live verification creates a fresh verification ID on each call; it does not provide the exact response replay implemented for live check and generation creation.
Broader protocol contract: the remaining description and examples include planned verification forms beyond those supported JSON inputs.
Verifies a file, a C2PA manifest or a receipt, and returns the receipt it matches with its
live status. A file is read for an embedded manifest first, then matched by its exact
digest, then by fingerprint and watermark, so verification still works after metadata is
stripped or the file is re-encoded. method says which one matched. Anyone may verify;
without a key the public rate limit applies. Test-mode receipts verify only with a test
key.
# 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_VERIFICATION_IDEMPOTENCY_KEY:?Set REFLECTIONS_CREATE_VERIFICATION_IDEMPOTENCY_KEY before running this example}"
curl --fail-with-body --max-time 30 -X POST 'https://app.reflections.ai/v1/verifications' \
-H "Authorization: Bearer $REFLECTIONS_KEY" \
-H "Reflections-Version: 2026-10-15" \
-H "Idempotency-Key: $REFLECTIONS_CREATE_VERIFICATION_IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
--data-raw '{
"receipt": "rcpt_hk5zba2q8v1m3n7p4r6s9t0wxy"
}'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_VERIFICATION_IDEMPOTENCY_KEY;
if (!idempotencyKey) throw new Error("Set REFLECTIONS_CREATE_VERIFICATION_IDEMPOTENCY_KEY before running this example");
const response = await fetch("https://app.reflections.ai/v1/verifications", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Reflections-Version": "2026-10-15",
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
"receipt": "rcpt_hk5zba2q8v1m3n7p4r6s9t0wxy"
}),
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_VERIFICATION_IDEMPOTENCY_KEY"]
if not idempotencyKey:
raise ValueError("Set REFLECTIONS_CREATE_VERIFICATION_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({"receipt": "rcpt_hk5zba2q8v1m3n7p4r6s9t0wxy"}).encode("utf-8")
request = Request("https://app.reflections.ai/v1/verifications",
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_VERIFICATION_IDEMPOTENCY_KEY")
if idempotencyKey == "" {
panic("Set REFLECTIONS_CREATE_VERIFICATION_IDEMPOTENCY_KEY before running this example")
}
body := strings.NewReader(`{
"receipt": "rcpt_hk5zba2q8v1m3n7p4r6s9t0wxy"
}`)
request, err := http.NewRequest("POST", "https://app.reflections.ai/v1/verifications", 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": "vrf_8b0c2d4e6f8g0h2j4k6m8n0p2q",
"object": "verification",
"result": "verified",
"method": "manifest",
"match": null,
"manifest": {
"signer": "Northlight",
"assertion_present": true,
"signature_valid": true
},
"receipt": {
"id": "rcpt_hk5zba2q8v1m3n7p4r6s9t0wxy",
"object": "receipt",
"livemode": true,
"kind": "generation",
"subject": "gen_2q8v1m3n7p4r6s9t0wxyhk5zba",
"decision": "allow",
"assurance": "observed",
"talent": [
"tal_9t0wxyhk5zba2q8v1m3n7p4r6s"
],
"licenses": [
{
"talent": "tal_9t0wxyhk5zba2q8v1m3n7p4r6s",
"license": "lic_ba2q8v1m3n7p4r6s9t0wxyhk5z",
"license_revision": "lrv_7p4r6s9t0wxyhk5zba2q8v1m3n",
"terms_digest": "sha256:8e0a2c4e6b8d0f2a4c6e8b0d2f4a6c8e0b2d4f6a8c0e2b4d6f8a0c2e4b6d8f0a"
}
],
"request_digest": "sha256:d4f6b8a0c2e4d6f8b0a2c4e6d8f0b2a4c6e8d0f2b4a6c8e0d2f4b6a8c0e2d4f6",
"jws": "eyJhbGciOiJFUzI1NiIs…",
"use_status": "permitted",
"issued_at": "2026-10-17T18:06:03Z"
},
"detail": null,
"created_at": "2026-10-18T09:12:44Z"
}{
"type": "https://docs.reflections.ai/errors/parameter_invalid",
"title": "A parameter is invalid",
"status": 400,
"code": "parameter_invalid",
"param": "use.territories[1]",
"detail": "\"UK\" is not an ISO 3166-1 alpha-2 code. Use \"GB\".",
"request_id": "req_8f2k4m6p8r0t2v4x6z8b0d2f4h"
}{
"type": "https://docs.reflections.ai/errors/invalid_api_key",
"title": "The API key is not valid",
"status": 401,
"code": "invalid_api_key",
"request_id": "req_8f2k4m6p8r0t2v4x6z8b0d2f4h"
}{
"type": "https://docs.reflections.ai/errors/idempotency_key_in_use",
"title": "A request with this key is still running",
"status": 409,
"code": "idempotency_key_in_use",
"detail": "Retry with the same key after a short wait.",
"request_id": "req_8f2k4m6p8r0t2v4x6z8b0d2f4h"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "invalid_request",
"request_id": "<string>",
"detail": "<string>",
"param": "<string>",
"doc_url": "<string>"
}{
"type": "https://docs.reflections.ai/errors/idempotency_key_reused",
"title": "This key was used for a different request",
"status": 422,
"code": "idempotency_key_reused",
"request_id": "req_8f2k4m6p8r0t2v4x6z8b0d2f4h"
}{
"type": "https://docs.reflections.ai/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"code": "rate_limited",
"request_id": "req_8f2k4m6p8r0t2v4x6z8b0d2f4h"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "invalid_request",
"request_id": "<string>",
"detail": "<string>",
"param": "<string>",
"doc_url": "<string>"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "invalid_request",
"request_id": "<string>",
"detail": "<string>",
"param": "<string>",
"doc_url": "<string>"
}{
"type": "<string>",
"title": "<string>",
"status": 123,
"code": "invalid_request",
"request_id": "<string>",
"detail": "<string>",
"param": "<string>",
"doc_url": "<string>"
}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"
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
Send exactly one of receipt, manifest, url, sha256 or fingerprint.
A receipt ID or JWS.
A C2PA manifest store.
A file Reflections can fetch.
SHA-256 of the file's bytes, in hex. Matches only an exact copy of a registered output.
^[0-9a-f]{64}$A fingerprint you computed, when the bytes cannot leave your platform.
Show child attributes
Show child attributes
Response
The verification.
Unique identifier, prefixed vrf_.
^vrf_[0-9a-hjkmnp-tv-z]{26}$Always verification.
"verification"verified: a valid receipt matches. not_found: nothing matches; this says nothing about whether the content is licensed. invalid: a receipt or manifest is present and fails verification.
verified, not_found, invalid How the content matched: its receipt, an embedded manifest, the exact digest, a
fingerprint or a watermark.
receipt, manifest, digest, fingerprint, watermark When the verification was made.
How the file matched a registered output, for digest, fingerprint and watermark. null when a receipt or manifest matched.
Show child attributes
Show child attributes
The C2PA manifest read from the file or sent as manifest. null when none was read.
Show child attributes
Show child attributes
The matching receipt, in full, with its live use_status. null when nothing matched.
Show child attributes
Show child attributes
What failed, when result is invalid. Otherwise null.