# 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_RESOLVE_ESCALATION_IDEMPOTENCY_KEY:?Set REFLECTIONS_RESOLVE_ESCALATION_IDEMPOTENCY_KEY before running this example}"
curl --fail-with-body --max-time 30 -X POST 'https://app.reflections.ai/v1/escalations/esc_t0wxyhk5zba2q8v1m3n7p4r6s9/resolve' \
-H "Authorization: Bearer $REFLECTIONS_KEY" \
-H "Reflections-Version: 2026-10-15" \
-H "Idempotency-Key: $REFLECTIONS_RESOLVE_ESCALATION_IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
--data-raw '{
"decision": "allow",
"note": "Fine as long as the cups hold coffee.",
"decided_by": {
"name": "Lena Brooks"
}
}'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_RESOLVE_ESCALATION_IDEMPOTENCY_KEY;
if (!idempotencyKey) throw new Error("Set REFLECTIONS_RESOLVE_ESCALATION_IDEMPOTENCY_KEY before running this example");
const response = await fetch("https://app.reflections.ai/v1/escalations/esc_t0wxyhk5zba2q8v1m3n7p4r6s9/resolve", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Reflections-Version": "2026-10-15",
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
"decision": "allow",
"note": "Fine as long as the cups hold coffee.",
"decided_by": {
"name": "Lena Brooks"
}
}),
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_RESOLVE_ESCALATION_IDEMPOTENCY_KEY"]
if not idempotencyKey:
raise ValueError("Set REFLECTIONS_RESOLVE_ESCALATION_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({
"decision": "allow",
"note": "Fine as long as the cups hold coffee.",
"decided_by": {"name": "Lena Brooks"},
}).encode("utf-8")
request = Request("https://app.reflections.ai/v1/escalations/esc_t0wxyhk5zba2q8v1m3n7p4r6s9/resolve",
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_RESOLVE_ESCALATION_IDEMPOTENCY_KEY")
if idempotencyKey == "" {
panic("Set REFLECTIONS_RESOLVE_ESCALATION_IDEMPOTENCY_KEY before running this example")
}
body := strings.NewReader(`{
"decision": "allow",
"note": "Fine as long as the cups hold coffee.",
"decided_by": {
"name": "Lena Brooks"
}
}`)
request, err := http.NewRequest("POST", "https://app.reflections.ai/v1/escalations/esc_t0wxyhk5zba2q8v1m3n7p4r6s9/resolve", 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": "esc_t0wxyhk5zba2q8v1m3n7p4r6s9",
"object": "escalation",
"livemode": true,
"reason": "judgment",
"reviewer": "talent",
"status": "allowed",
"expires_at": "2026-10-24T18:04:00Z",
"resolved_at": "2026-10-17T21:30:00Z",
"note": "Fine as long as the cups hold coffee.",
"subject": {
"object": "check",
"id": "chk_r6s9t0wxyhk5zba2q8v1m3n7p4"
},
"talent": "tal_9t0wxyhk5zba2q8v1m3n7p4r6s",
"license": "lic_k5zba2q8v1m3n7p4r6s9t0wxyh",
"license_revision": "lrv_p4r6s9t0wxyhk5zba2q8v1m3n7",
"licensee": "acct_2q8v1m3n7p4r6s9t0wxyhk5zba",
"licensee_name": "Ridgeline",
"platform": "acct_7p4r6s9t0wxyhk5zba2q8v1m3n",
"platform_name": "Northlight",
"use": {
"modality": "image",
"kind": "depiction",
"purpose": "advertising",
"commercial": true,
"channels": [
"social"
],
"territories": [
"US"
]
},
"inputs": [
{
"role": "prompt",
"text": "Maya Okafor at a trailhead café with two friends, paper cups on the table, morning light"
}
],
"reasons": [
{
"code": "prohibited_custom_term",
"stage": "check",
"talent": "tal_9t0wxyhk5zba2q8v1m3n7p4r6s",
"term": "license:lic_k5zba2q8v1m3n7p4r6s9t0wxyh/terms.neverExtras",
"sentence": "Do not show alcohol or tobacco."
}
],
"findings": [],
"artifact": null,
"request_digest": "sha256:c3e5a7b9d1f3c5e7a9b1d3f5c7e9a1b3d5f7c9e1a3b5d7f9c1e3a5b7d9f1c3e5",
"review_url": null,
"decision": "allow",
"decided_by": {
"party": "representative",
"account": "acct_6m8n0p2q4r6s8t0v2w4x6y8z0b",
"name": "Lena Brooks"
},
"created_at": "2026-10-17T18:04:00Z"
}Resolve an escalation
Planned contract — unavailable in test and live modes. This operation is a protocol reference, not a callable endpoint. Start with the working sandbox quickstart.
Allows or denies an escalated check, or releases or declines a held output, on a person’s
decision. When the escalation’s reviewer is talent, needs a mandate with
review_outputs for the talent. decided_by names that person, and the decision is
recorded on the talent’s audit record with your account. An allow covers exactly the request that was escalated,
bound by its request_digest; for a held output, it releases those bytes and issues their
receipt. The platform receives check.resolved, generation.released or
generation.denied.
When a check invokes several talent, each escalated talent’s side decides their own
escalation. The check resolves to allow only when every one allows, and to deny as
soon as one denies or expires; check.resolved is sent then, and the other open
escalations become cancelled.
Do not resolve escalations with an automated rule. To answer a kind of request the same way every time, change the license, so the answer becomes a checked term that every check applies.
Restricted keys need escalations: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_RESOLVE_ESCALATION_IDEMPOTENCY_KEY:?Set REFLECTIONS_RESOLVE_ESCALATION_IDEMPOTENCY_KEY before running this example}"
curl --fail-with-body --max-time 30 -X POST 'https://app.reflections.ai/v1/escalations/esc_t0wxyhk5zba2q8v1m3n7p4r6s9/resolve' \
-H "Authorization: Bearer $REFLECTIONS_KEY" \
-H "Reflections-Version: 2026-10-15" \
-H "Idempotency-Key: $REFLECTIONS_RESOLVE_ESCALATION_IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
--data-raw '{
"decision": "allow",
"note": "Fine as long as the cups hold coffee.",
"decided_by": {
"name": "Lena Brooks"
}
}'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_RESOLVE_ESCALATION_IDEMPOTENCY_KEY;
if (!idempotencyKey) throw new Error("Set REFLECTIONS_RESOLVE_ESCALATION_IDEMPOTENCY_KEY before running this example");
const response = await fetch("https://app.reflections.ai/v1/escalations/esc_t0wxyhk5zba2q8v1m3n7p4r6s9/resolve", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Reflections-Version": "2026-10-15",
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
"decision": "allow",
"note": "Fine as long as the cups hold coffee.",
"decided_by": {
"name": "Lena Brooks"
}
}),
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_RESOLVE_ESCALATION_IDEMPOTENCY_KEY"]
if not idempotencyKey:
raise ValueError("Set REFLECTIONS_RESOLVE_ESCALATION_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({
"decision": "allow",
"note": "Fine as long as the cups hold coffee.",
"decided_by": {"name": "Lena Brooks"},
}).encode("utf-8")
request = Request("https://app.reflections.ai/v1/escalations/esc_t0wxyhk5zba2q8v1m3n7p4r6s9/resolve",
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_RESOLVE_ESCALATION_IDEMPOTENCY_KEY")
if idempotencyKey == "" {
panic("Set REFLECTIONS_RESOLVE_ESCALATION_IDEMPOTENCY_KEY before running this example")
}
body := strings.NewReader(`{
"decision": "allow",
"note": "Fine as long as the cups hold coffee.",
"decided_by": {
"name": "Lena Brooks"
}
}`)
request, err := http.NewRequest("POST", "https://app.reflections.ai/v1/escalations/esc_t0wxyhk5zba2q8v1m3n7p4r6s9/resolve", 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": "esc_t0wxyhk5zba2q8v1m3n7p4r6s9",
"object": "escalation",
"livemode": true,
"reason": "judgment",
"reviewer": "talent",
"status": "allowed",
"expires_at": "2026-10-24T18:04:00Z",
"resolved_at": "2026-10-17T21:30:00Z",
"note": "Fine as long as the cups hold coffee.",
"subject": {
"object": "check",
"id": "chk_r6s9t0wxyhk5zba2q8v1m3n7p4"
},
"talent": "tal_9t0wxyhk5zba2q8v1m3n7p4r6s",
"license": "lic_k5zba2q8v1m3n7p4r6s9t0wxyh",
"license_revision": "lrv_p4r6s9t0wxyhk5zba2q8v1m3n7",
"licensee": "acct_2q8v1m3n7p4r6s9t0wxyhk5zba",
"licensee_name": "Ridgeline",
"platform": "acct_7p4r6s9t0wxyhk5zba2q8v1m3n",
"platform_name": "Northlight",
"use": {
"modality": "image",
"kind": "depiction",
"purpose": "advertising",
"commercial": true,
"channels": [
"social"
],
"territories": [
"US"
]
},
"inputs": [
{
"role": "prompt",
"text": "Maya Okafor at a trailhead café with two friends, paper cups on the table, morning light"
}
],
"reasons": [
{
"code": "prohibited_custom_term",
"stage": "check",
"talent": "tal_9t0wxyhk5zba2q8v1m3n7p4r6s",
"term": "license:lic_k5zba2q8v1m3n7p4r6s9t0wxyh/terms.neverExtras",
"sentence": "Do not show alcohol or tobacco."
}
],
"findings": [],
"artifact": null,
"request_digest": "sha256:c3e5a7b9d1f3c5e7a9b1d3f5c7e9a1b3d5f7c9e1a3b5d7f9c1e3a5b7d9f1c3e5",
"review_url": null,
"decision": "allow",
"decided_by": {
"party": "representative",
"account": "acct_6m8n0p2q4r6s8t0v2w4x6y8z0b",
"name": "Lena Brooks"
},
"created_at": "2026-10-17T18:04:00Z"
}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.
255Path Parameters
The escalation's ID.
^esc_[0-9a-hjkmnp-tv-z]{26}$Body
For a held output, allow releases it and deny declines it.
allow, deny The person who made the decision. Recorded on the talent's audit record.
Show child attributes
Show child attributes
Shown to the platform as escalation.note.
500Response
The resolved escalation.
An escalation with the request, use and findings a reviewer needs.
The escalation's ID, esc_ followed by 26 characters.
^esc_[0-9a-hjkmnp-tv-z]{26}$judgment: a rule needs a person to read it. unverifiable: a protected attribute
could not be confirmed. evaluation_unavailable: the check could not finish.
release_review: every output under this license is released by a person.
judgment, unverifiable, evaluation_unavailable, release_review talent: the talent, or a representative whose mandate has review_outputs.
representative: only the talent's representatives. reflections: Reflections trust
and safety, under a platform license.
talent, representative, reflections open until it closes: allowed or denied by the reviewer, expired at
expires_at, or cancelled when you cancel the check, withdraw the output, or another
talent's escalation decides the check.
open, allowed, denied, expired, cancelled When an unanswered escalation becomes deny: 7 days after it opens, or the license's
shorter escalation_window.
Always escalation.
"escalation"true in live mode, false in test mode.
What waits on the decision.
Show child attributes
Show child attributes
The talent whose side decides. When a check escalates for several talent, each has their own escalation.
^tal_[0-9a-hjkmnp-tv-z]{26}$The license the request was checked under.
^lic_[0-9a-hjkmnp-tv-z]{26}$The account that holds license.
^acct_[0-9a-hjkmnp-tv-z]{26}$The platform that ran the check or submitted the output.
^acct_[0-9a-hjkmnp-tv-z]{26}$The use the platform asked for on the check.
Show child attributes
Show child attributes
The request_digest of the check. Allowing an escalated check covers exactly this
request.
^sha256:[0-9a-f]{64}$When the check escalated or the output was held.
When the escalation was decided, expired or cancelled. Null while open.
The reviewer's note, if they wrote one.
500The revision of license the request was checked against.
^lrv_[0-9a-hjkmnp-tv-z]{26}$The licensee's account name.
The platform's account name.
The request as the platform sent it. Media inputs carry a url that opens for 15
minutes. Text sent withheld appears as role and sha256 only.
Show child attributes
Show child attributes
What raised the question, with the talent's term.
Show child attributes
Show child attributes
For a held output, what Reflections recorded on its bytes.
Show child attributes
Show child attributes
For a held output, the clean bytes to review.
Show child attributes
Show child attributes
The Talent Portal page where a person with authority reviews it and decides. Present while open.
The decision a person recorded, allow or deny. null until one is recorded.
allow, deny, null Who resolved it.
Show child attributes
Show child attributes