Concepts
How authentication, review content, policies, and workflows fit together.
Authentication
| Actor | How | Notes |
|---|---|---|
| Agents / backends | x-api-key: ak_… or Authorization: Bearer ak_… |
Scopes: admin or consumer |
| Dashboard users | Cognito email/password → bearer access token (or x-access-token) |
Roles: admin or consumer |
| Email reviewers | Personal link with auth code (rrc_…) |
No account; exchange via POST /v1/review/exchange |
Admin can do everything consumer can. Check the current principal with
GET /v1/me.
API keys & users
API keys
- Admin — manage users and keys; also create and read review requests.
- Consumer — create and read review requests only. Prefer this for agents.
Secrets are returned once on create/signup/rotate (ak_…). Hashed at rest. Admin endpoints:
-
POST /v1/api-keys—{ "label", "scope?" }(defaultconsumer) GET /v1/api-keys— list active keys (no secret)POST /v1/api-keys/{keyId}/revoke-
POST /v1/api-keys/{keyId}/rotate— revokes and issues a new admin key
Users
-
GET /v1/users/POST /v1/users— list / invite (email, optionalname,role) PATCH /v1/users/{userId}— set roleDELETE /v1/users/{userId}— cannot delete yourself or the last admin
Documents
Each review item has a document descriptor:
{
"type": "url" | "image" | "pdf" | "text" | "html",
"url": "https://…", // typical for url / image / pdf
"title": "optional title",
"content": "inline body…", // typical for text / html
"metadata": { }
}
type is required. Other fields are optional at the schema level — send what your asset needs
(for example a public URL for previews, or inline content for copy).
External IDs & metadata
externalId is an optional string on requests and items (/^[A-Za-z0-9._:-]+$/,
1–128 chars. Reuse the same ID across reviews over time (same PR, asset, or deploy target). List endpoints
return newest first.
GET /v1/review-requests?filter[externalId]=landing-page-v2
GET /v1/review-items?filter[externalId]=preview-asset-1
MCP equivalents: reviews with operation: "list" and
external_id; reviews list_items requires
external_id.
metadata is an opaque key-value bag on requests, items, comments, and documents. Stored and
returned; not searchable.
Subscribers
Optional subscribers on create — email addresses notified when the request reaches a verdict
(approved, rejected, or changes_required. Set at creation only.
Reviewers decide; subscribers are notified of the result. They are not the same role.
Multi-item reviews
One request can include several assets. Prefer decisionScope: "item" so reviewers approve or
reject each item independently.
"policy": { "type": "ALL_APPROVE", "decisionScope": "item" },
"items": [
{ "title": "Landing page", "document": { "type": "url", "url": "https://…" } },
{ "title": "Creative PDF", "document": { "type": "pdf", "url": "https://…" } }
]
Request-level status rolls up items: pending if any pending; rejected if any rejected;
changes_required if any need changes; otherwise approved. Detail/list responses include
evaluationScope, outcome, and often itemOutcomes.
Policies
"policy": {
"type": "ALL_APPROVE" | "ANY_APPROVE" | "ANY_REJECT" | "QUORUM" | "ADVISORY",
"quorum": 2, // QUORUM only; defaults to reviewer count
"rejectionThreshold": 1, // optional early reject after N rejections
"decisionScope": "request" | "item" // default "request"
}
| Type | Behavior |
|---|---|
ALL_APPROVE |
Every reviewer must approve; any rejection rejects. Pending while anyone is undecided. |
ANY_APPROVE |
First approval wins. Otherwise pending, then rejection / changes if no approvals remain. |
ANY_REJECT |
First rejection wins. Otherwise pending, then changes / approval. |
QUORUM |
Approvals ≥ quorum → approved. If remaining non-rejected reviewers cannot reach
quorum → rejected.
|
ADVISORY |
Feedback, no gate. Resolves to advised once every reviewer has answered, and no
answer — not even a rejection — blocks it. Use it for the internal round before the work goes to
whoever actually approves it.
|
If rejectionThreshold is set, reaching that many rejections rejects immediately for all
policy types except ADVISORY, which ignores it. cancelled /
expired are terminal and not overwritten by policy.
Status & outcomes
Request / item status: pending, approved, rejected,
changes_required, advised, cancelled, expired,
precheck_running, precheck_failed.
advised only happens under an ADVISORY policy: the round finished and the
feedback is in, without anything having been approved or blocked.
The last two only occur when the smart precheck is enabled, and are never overwritten by policy evaluation.
List and detail responses include outcome:
{
"status": "pending",
"summary": "0/2 approved · 2 awaiting",
"tally": {
"approved": 0,
"rejected": 0,
"changes_required": 0,
"pending": 2
}
}
For item-scoped reviews, outcome.tally counts items; use outcome.summary for
mixed results such as 1 approved · 1 rejected. Comment status is open or
resolved.
Create a review request
POST /v1/review-requests · MCP
reviews (create)
Body fields
| Field | Required | Description |
|---|---|---|
title |
yes | Request title |
description |
no | Longer context for reviewers |
items |
yes (≥1) | Assets to review — see below |
reviewers |
yes (≥1) | { email, name? } |
subscribers |
no | Emails notified on verdict |
policy |
yes | See Policies |
externalId |
no | Your correlation ID |
domain |
no |
Groups requests that share a feedback structure, e.g. ads vs
landing-pages. Defaults to default.
|
smartPrecheck |
no |
{ enabled, instructions?, model?, blockOn? } — see
Smart precheck
|
metadata |
no | Opaque bag |
Item fields
| Field | Required | Description |
|---|---|---|
title |
yes | Item title |
document |
yes | See Documents |
id |
no | Client-supplied item id |
externalId |
no | Item correlation ID |
metadata |
no | Opaque bag |
Example
curl -X POST "https://rest.agentfabric.dev/v1/review-requests" \
-H "content-type: application/json" \
-H "x-api-key: $AGENTFABRIC_API_KEY" \
-d '{
"title": "Approve landing page",
"description": "Draft for next week’s launch",
"externalId": "landing-page-v2",
"policy": { "type": "ALL_APPROVE", "decisionScope": "request" },
"reviewers": [{ "email": "reviewer@example.com", "name": "Alex" }],
"subscribers": ["ops@example.com"],
"items": [
{
"title": "Preview",
"externalId": "preview-asset-1",
"document": { "type": "url", "url": "https://example.com/page" },
"metadata": { "branch": "main" }
}
],
"metadata": { "env": "staging" }
}'
Full request/response schemas: POST /v1/review-requests and Resources.
Smart precheck
An optional quality gate. Before a single reviewer is emailed, a browser agent opens every review URL and
checks it against your instructions and the rules learned from past reviewer feedback in the same
domain. Reviewers only ever see work that already cleared the problems they reported before.
Enable it per request, or for a whole domain with enabledByDefault in the precheck config:
{
"smartPrecheck": {
"enabled": true,
"instructions": "Walk the full landing page and submit the signup form with a test address.",
"blockOn": "major"
}
}
With the gate on, POST /v1/review-requests sends no email and returns:
{
"request": { "status": "precheck_running", ... },
"precheck": { "precheckId": "pchk_…", "attempt": 1, "status": "queued" },
"next_actions": ["wait_for_precheck", "fetch_precheck"]
}
| Outcome | Request status | What happens |
|---|---|---|
| Pass | pending |
Reviewer invitations go out exactly as they normally would |
| Fail | precheck_failed |
No email; findings explain each problem with severity, the offending item, a
suggested fix and an evidence screenshot
|
| Error | precheck_failed |
A single critical finding explains why the gate could not run, so nothing is ever silently stuck |
blockOn decides which findings block delivery: any_finding,
major (default) or critical. Fix the findings and call
POST /v1/review-requests/{id}/precheck/rerun, or send the invitations regardless with
POST /v1/review-requests/{id}/precheck/override.
model picks the browser agent's model and defaults to grok-4.5. Use
claude-opus-5 when accuracy matters more than speed, or minimax-m3 for the
cheapest run. Every run is capped at $1.50 of model spend and inspects the pages at 1440×900, the
same viewport as the evidence screenshots.
Feedback groups
Reviewer feedback accumulates forever, so the gate never reads raw comments. Each new comment is either
merged into an existing group for its domain or starts a new one, and the group's summary and
rule are rewritten to integrate it rather than growing a list. The precheck prompt therefore
scales with the number of distinct problems, not the number of comments.
A group holds a title, an integrated summary, one imperative
rule the checker enforces, a severity and a memberCount. When a
domain exceeds maxGroups (60 by default), similar groups are merged and the absorbed ones are
archived with mergedInto set. Mute, archive or rewrite any group from the Smart precheck tab
in the dashboard or via PATCH /v1/feedback-groups/{groupId}.
Reviewers
Each reviewer gets an emailed personal link
{REVIEW_APP_URL}/review?authCode=… — no account. Add, remind, or cancel via the
review request endpoints (or MCP reviews with
add_reviewer / remind / cancel).
agentfabric.dev
Comments
Comments are threaded conversations, and both sides can write. Reviewers start threads from their link; agents reply with
comments(create), listing and resolving whole threads. See ReviewComment and comment endpoints.A comment also records what the reviewer had on screen in
context: the item and document URL, viewport size, scroll position, any selected text, and a screenshot captured right after submission. Both the resolving agent and the smart precheck use it to see the problem the way the reviewer did.Nobody is emailed per comment. Activity starts a 30-minute quiet period for the other side and each new comment pushes the deadline out, so a burst arrives as one digest. Reviewer comments and verdicts notify the request's
subscribers— which is also how a reviewer who leaves verdicts but never finishes still reaches you — while your replies notify only the reviewer whose thread you answered. To skip the wait, callcommentswithoperation: "notify"; reviewers do the same by finishing their review.