---
title: "Create a review"
method: POST
path: "/api/v1/reviews"
tags: ["Reviews"]
---

# Create a review

`POST /api/v1/reviews`

## Request

- Base URL: `https://api.gatewerk.com`
- URL: `https://api.gatewerk.com/api/v1/reviews`
- Auth: HTTP bearer

Agents submit a review request tied to a template. The response is returned immediately — the decision (if any) is delivered via `callback_url` webhook or retrieved by polling `GET /reviews/:id`.

If the template has `auto_approve=true`, the review is created and decided in one step; the response includes `decision: "approved"` and the callback fires immediately.

## Request body

- ReviewCreateBody
  - `template` string, required: Template slug.
  - `payload` JsonObject, required: Free-form JSON object.
  - `callback_url` string, uri: HTTPS URL to receive decision webhooks. Validated against SSRF rules (no private IPs). Omit to poll instead.
  - `priority` 'low' | 'normal' | 'high' | 'critical'
  - `actions` string[]
  - `confidence` number
  - `irreversibility` 'reversible' | 'costly_reversible' | 'irreversible'
  - `assignee` string
  - `metadata` JsonObject: Free-form JSON object.
  - `timeout` object: Override the template's timeout policy for this review.
    - `action` 'auto_approve' | 'auto_reject' | 'expire', required
    - `seconds` integer, required
  - `oversight` 'blocking' | 'monitoring': Oversight mode for the review. 'blocking' (default) pauses the agent until a human decides. 'monitoring' records the action and auto-confirms after the veto window; the human can veto before the window closes.
  - `assignment_ladder` AssignmentLadderStep[]: Escalation ladder for the review. Step 0 becomes the initial assignee; subsequent steps activate after trigger_after_seconds. Omit to use the template default or the assignee field.
    - `actor` string, required: Assignee email or role identifier.
    - `trigger_after_seconds` integer, required: Seconds from review creation after which this step becomes active. Must be strictly increasing across steps.
    - `status` 'pending' | 'active' | 'promoted': Step lifecycle status. Server normalises on create; clients may omit.
  - `idempotency_key` string: Client-supplied deduplication key. A second POST with the same (project, idempotency_key) pair returns the existing review instead of creating a duplicate. Terminal conflict (decided/expired review) returns 409 idempotency_key_terminal_conflict.
  - `trace_url` string: Optional deep link to the originating agent trace. Must be https.
  - `max_iterations` integer: Per-review cap on revision rounds. Overrides the template-level default. Worker auto-closes awaiting_iteration reviews whose current_version - 1 >= max_iterations.

## Response `201`

Review created (or auto-approved).

- Review
  - `object` 'review', required
  - `id` string, required
  - `project_id` string, required
  - `template_id` string, nullable
  - `template_slug` string, required
  - `payload` JsonObject, required: Free-form JSON object.
  - `suggested_value` JsonObject: Free-form JSON object.
  - `approved_value` JsonObject: Free-form JSON object.
  - `edited_payload` JsonObject: Free-form JSON object.
  - `callback_url` string, nullable
  - `priority` 'low' | 'normal' | 'high' | 'critical', required
  - `actions` unknown[]
    - unknown
  - `status` 'pending' | 'awaiting_iteration' | 'decided' | 'expired' | 'archived', required: Canonical review status. Wire-format output is always one of these five values. Query-param status filter additionally accepts the legacy 'changes_requested' alias as a deprecated input for one minor version per spec §11.3 — removed in v2.0.
  - `decision` 'approved' | 'rejected' | 'edited' | 'retried' | 'expired'
  - `feedback` string, nullable
  - `decided_by` string, nullable
  - `decided_by_verified` boolean, nullable: Whether decided_by names a confirmed identity. False only for a decision made through a PUBLIC review link, where the decider is the recipient_label the sharer typed and nothing verified it. Null for reviews decided before this field existed: render no claim rather than assuming unverified.
  - `decided_at` string, date-time, nullable
  - `current_version` integer, required
  - `assignee` string, nullable
  - `metadata` JsonObject: Free-form JSON object.
  - `action_value` string, nullable
  - `action_label` string, nullable
  - `created_at` string, date-time, required
  - `updated_at` string, date-time, required
  - `template` object: Embedded snapshot of the template used at creation time.
    - `name` string
    - `fields` TemplateField[]
      - `name` string, required
      - `label` string, required
      - `type` 'text' | 'markdown' | 'json' | 'image' | 'video' | 'number' | 'boolean' | 'select' | 'buttons' | 'date' | 'url', required
      - `editable` boolean
      - `options` string[]: Required when `type` is `select` or `buttons`.
    - `actions` unknown[]
      - unknown
    - `auto_approve` boolean, nullable
    - `instructions` string, nullable
    - `enable_review_links` boolean: Per-template toggle gating the Inbox kebab `Generate token link` affordance.
  - `template_fields` TemplateField[], nullable: Normalized field schema captured at review creation. Frozen — template re-publish or deletion does not affect this value. Null for reviews created before migration 073.
    - `name` string, required
    - `label` string, required
    - `type` 'text' | 'markdown' | 'json' | 'image' | 'video' | 'number' | 'boolean' | 'select' | 'buttons' | 'date' | 'url', required
    - `editable` boolean
    - `options` string[]: Required when `type` is `select` or `buttons`.
  - `active_token` object, nullable: Latest live (un-used, un-revoked, un-expired) review_tokens row, projected read-only on review.get and review.list. Null when no live token exists. Absent on mutation responses (create/decide/ retry/etc.) which don't run the projection.
    - `id` string, required
    - `recipient_label` string, required
    - `auth_level` string, required
    - `created_at` string, date-time, required
    - `expires_at` string, date-time, required
    - `opened_at` string, date-time, nullable

## Other responses

- `400`: 400 Bad Request. Validation failed.
- `401`: 401. Missing or invalid API key.
- `403`: 403. Key lacks the required scope or template access.
- `413`: 413. Total payload exceeds 5 MB or a field exceeds 1 MB.
- `429`: 429. Per-key rate limit exceeded.

---

[API](https://skmtc.dev/gatewerk/apis/gatewerk-api.md) · [All operations](https://skmtc.dev/gatewerk/apis/gatewerk-api/llms.txt) · [OpenAPI document](https://skmtc.dev/gatewerk/apis/gatewerk-api/revisions/dc634ad16f5c?raw)
