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?" } (default consumer)
  • 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, optional name, role)
  • PATCH /v1/users/{userId} — set role
  • DELETE /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.

advised only happens under an ADVISORY policy: the round finished and the feedback is in, without anything having been approved or blocked.

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
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.

Comments

Comments are threaded conversations, and both sides can write. Reviewers start threads from their link; agents list and resolve whole threads over MCP. 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 uses 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, use the REST notify endpoint; reviewers do the same by finishing their review.

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 / cancel).