Concepts

How authentication, review content, policies, and workflows fit together.

Authentication

ActorHowNotes
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: list_review_requests with external_id; list_review_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",
  "quorum": 2,                    // QUORUM only; defaults to reviewer count
  "rejectionThreshold": 1,        // optional early reject after N rejections
  "decisionScope": "request" | "item"   // default "request"
}
TypeBehavior
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.

If rejectionThreshold is set, reaching that many rejections rejects immediately for all policy types. cancelled / expired are terminal and not overwritten by policy.

Status & outcomes

Request / item status: pending, approved, rejected, changes_required, cancelled, expired.

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 create_review_request

Body fields

FieldRequiredDescription
titleyesRequest title
descriptionnoLonger context for reviewers
itemsyes (≥1)Assets to review — see below
reviewersyes (≥1){ email, name? }
subscribersnoEmails notified on verdict
policyyesSee Policies
externalIdnoYour correlation ID
metadatanoOpaque bag

Item fields

FieldRequiredDescription
titleyesItem title
documentyesSee Documents
idnoClient-supplied item id
externalIdnoItem correlation ID
metadatanoOpaque 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

Reviewers create comments; agents list and resolve them. See ReviewComment and comment endpoints.

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 add_reviewer / remind_reviewers / cancel_review_request).