# agentfabric.dev > Human-in-the-loop review and approval layer for AI agents. Agents send generated > output (URLs, images, PDFs, text, HTML) to human reviewers, enforce approval > policies, and receive a full audit trail. Integrate over MCP (recommended for > agents) or REST. agentfabric.dev is designed to be consumed by AI agents. The fastest integration is the Model Context Protocol (MCP) server, which exposes human review as a set of tools. Humans are not required for signup — only for reviewing content. ## Connect over MCP (recommended for agents) MCP endpoint: https://mcp.agentfabric.dev Transport: Streamable HTTP only (not legacy SSE). Example MCP client configuration: ```json { "mcpServers": { "agentfabric": { "url": "https://mcp.agentfabric.dev", "type": "http" // Optional: call create_tenant (no auth) to get an API key, // then reconnect with it to authenticate other requests: // "headers": { "x-api-key": "ak_your_api_key" } } } } ``` OpenClaw: set `"transport": "streamable-http"` in `mcp.servers` (not `mcpServers`). ### Auth / tool scopes Pass your API key as the `x-api-key` header on MCP requests: - No API key: only `create_tenant` - Consumer API key: `reviews`, `comments` - Admin API key: consumer tools plus `users`, `api_keys`, `webhooks` (prefer consumer for review work) When `x-api-key` is set, do not pass `api_key` in tool arguments. ### Agent flow 1. Connect without a key and call `create_tenant` (`tenantName`, `ownerEmail`, `password` min 8, optional `ownerName`). 2. Save `adminApiKey` and `consumerApiKey` from the response. 3. Reconnect with `x-api-key` set to the consumer key (prefer consumer for day-to-day work). 4. Call `reviews` with `operation: "create"`, then poll `reviews` with `operation: "get"` until status leaves `pending`. ### Available MCP tools Onboarding (no auth): - create_tenant — create a tenant; returns admin + consumer API keys Consumer+ (prefer a consumer API key; each tool takes `operation`): - reviews — create, get, list, list_items, cancel, add_reviewer - comments — list, resolve, unresolve Admin (admin API key only): - users — list, create - api_keys — list, create - webhooks — list, create, delete — get told about verdicts instead of polling Tool results are the REST `data` payload for the matching endpoint. ## Connect over REST Base URL: https://rest.agentfabric.dev Auth: `x-api-key: ak_…` or `Authorization: Bearer ak_…` (or Cognito bearer / `x-access-token` for dashboard users). Success: `{ "data": … }` · Error: `{ "errors": [{ "title", "path?" }] }` ### Core endpoints - POST /v1/signup — create tenant; returns adminApiKey + consumerApiKey - GET /v1/me — current principal + role - POST /v1/review-requests — create review request - GET /v1/review-requests — list (?filter[externalId]=); includes outcome - GET /v1/review-requests/{id} — detail: request, items, reviewers, comments, events, outcome - GET /v1/review-items?filter[externalId]=… — items across requests (filter required) - GET /v1/review-requests/{id}/comments — optional ?itemId= & ?status=open|resolved (status filters whole threads) - POST /v1/review-requests/{id}/comments — { body, parentCommentId? | reviewerId?, itemId? } — reply as the tenant - POST /v1/review-requests/{id}/comments/{commentId}/resolve|unresolve — thread root only · 400 otherwise - POST /v1/review-requests/{id}/notify — optional { reviewerIds } — email pending replies now - POST /v1/review-requests/{id}/reviewers — { reviewers: [{ email, name? }] } · 409 duplicate - POST /v1/review-requests/{id}/remind — optional { emails } · 410 if cancelled/expired - POST /v1/review-requests/{id}/cancel - POST /v1/webhooks — { url, description?, events? } → `{ webhook, secret }`; the secret is returned once - GET /v1/webhooks — `{ webhooks, events }` — endpoints with masked secrets, plus the event catalogue - DELETE /v1/webhooks/{endpointId} — stop delivering to that endpoint Admin: /v1/users, /v1/api-keys (+ revoke/rotate), /v1/webhooks. Rotate issues a new admin key. Reviewer app (usually not called by agents): POST /v1/review/exchange, GET/POST /v1/review/{id}/… ## Resources (JSON shapes) Tenant: `{ tenantId, name, ownerEmail, ownerName?, status: active|suspended, createdAt, updatedAt }` AppUser: `{ tenantId, userId, email, name?, role: admin|consumer, cognitoSub, status: active|disabled, createdAt, updatedAt }` ApiKeyRecord: `{ tenantId, keyId, label, scope: admin|consumer, status: active|revoked, createdAt, lastUsedAt? }` — plaintext `ak_…` only on create/signup/rotate Document: `{ type: url|image|pdf|text|html, url?, title?, content?, metadata? }` Policy: `{ type: ALL_APPROVE|ANY_APPROVE|ANY_REJECT|QUORUM|ADVISORY, quorum?, rejectionThreshold?, decisionScope?: request|item }` ReviewRequest: `{ tenantId, requestId, title, description?, policy, status, subscribers[], externalId?, metadata?, createdAt, updatedAt }` ReviewItem: `{ itemId, title, document, status, order, externalId?, metadata? }` Reviewer: `{ reviewerId, email, name?, channel?: email|none, decision, authCodeExpiresAt, openedAt?, decidedAt? }` — `channel` absent means email ReviewItemDecision: `{ requestId, tenantId, itemId, reviewerId, decision, decidedAt }` ReviewComment: `{ commentId, requestId, tenantId, itemId?, reviewerId?, authorEmail?, authorRole: reviewer|owner, threadId, parentCommentId?, body, status: open|resolved, createdAt, resolvedAt?, resolvedBy?, context?, metadata? }` — `threadId` equals `commentId` on the comment that started the thread; `status` lives on that root ReviewNotice: `{ audience: subscribers|reviewer, reviewerId?, notifyAfter?, firstPendingAt?, lastActivityAt?, pendingComments, pendingDecisions, notifiedAt? }` — a pending activity digest; `notifyAfter` absent means nothing is owed AuditEvent: `{ eventId, tenantId, requestId, itemId?, reviewerId?, type, createdAt, metadata? }` Outcome: `{ status, summary, tally: { approved, rejected, changes_required, pending } }` (+ `evaluationScope`, optional `itemOutcomes`) ReviewViewContext (comment.context): `{ itemId, documentType, documentUrl?, contentHash?, viewport: { width, height, dpr }, scroll?, selectionText?, userAgent?, capturedAt, snapshot?: { status: pending|ready|failed|skipped, url? } }` — what the reviewer had on screen WebhookEndpoint: `{ endpointId, url, description?, events?, secretHint, status: active|disabled, createdAt, lastSucceededAt?, lastFailedAt?, lastFailureReason? }` — plaintext `whsec_…` only on create; no `events` means all of them WebhookEvent (request body): `{ data: { eventId, type, tenantId, createdAt, data } }` ## Create review request ```json { "title": "Approve landing page", "description": "optional", "externalId": "landing-page-v2", "policy": { "type": "ALL_APPROVE", "decisionScope": "request" }, "reviewers": [{ "email": "reviewer@example.com", "name": "Alex", "channel": "email" }], "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" } } ``` Create response `data`: `{ request, items, reviewers, reviewerLinks, next_actions }`. Get response `data`: `{ request, items, reviewers, itemDecisions, comments, notices, events, outcome, evaluationScope, itemOutcomes? }`. externalId: `/^[A-Za-z0-9._:-]+$/`, 1–128 chars; reusable; list newest first. metadata: opaque; stored/returned; not searchable. subscribers: notified once the request resolves — approved | rejected | changes_required | advised (set at create). ## Webhooks (stop polling) Register an https endpoint with `POST /v1/webhooks` and AgentFabric posts each event to it, so an agent can finish its turn and be woken up instead of holding a poll loop open. Event types: `review_request.decided`, `review_comment.created`. Omit `events` to receive all of them. Every delivery is a `POST` with `{ "data": { eventId, type, tenantId, createdAt, data } }` and these headers: ``` agentfabric-event: review_request.decided agentfabric-delivery: evt_… agentfabric-signature: t=1800000000,v1= ``` Verify it: HMAC-SHA256 over `"{t}.{raw body}"` with your endpoint secret, compare in constant time, and reject a `t` older than five minutes. Sign the raw body — not a re-serialised copy. `data` carries `requestId`/`externalId` and the new `status` for review events, the `comment` for `review_comment.created`. Respond `2xx` to accept. A `5xx`, a `429` or a timeout (10s) is retried; any other `4xx` is treated as a permanent rejection and dropped. Deliveries are at-least-once and not ordered, so treat `eventId` as an idempotency key and re-read the request if you need current state. ## Comment threads and activity notifications Comments are threaded, and both sides can write. Reviewers comment from their emailed link; you reply with `comments` (`operation: "create"`) or `POST /v1/review-requests/{id}/comments`, passing `parentCommentId` to answer inside an existing thread or `reviewerId` to open a new one aimed at one reviewer. A reply inherits its thread's `itemId`, and `resolve`/`unresolve` act on the thread as a whole, so they must target the root comment. Notifications are debounced rather than sent per comment. Any activity starts a 30-minute quiet period for the other side and pushes an existing deadline out, so a burst of comments produces one digest email. Reviewer comments and verdicts notify `subscribers`; your replies notify only the reviewer whose thread you answered. A reviewer leaving verdicts and never finishing therefore still surfaces once they stop typing. To skip the wait, call `comments` with `operation: "notify"` (or `POST /v1/review-requests/{id}/notify`); reviewers get the same immediate send from the "Finish & send review" button. Pending digests appear as `notices` on the request detail response, where `notifyAfter` is when the email goes out. ## Approval policies ```json { "type": "ALL_APPROVE" | "ANY_APPROVE" | "ANY_REJECT" | "QUORUM" | "ADVISORY", "quorum": 2, "rejectionThreshold": 1, "decisionScope": "request" | "item" } ``` - ALL_APPROVE — every reviewer must approve; any rejection rejects - ANY_APPROVE — first approval wins - ANY_REJECT — first rejection wins - QUORUM — approvals ≥ quorum (default: reviewer count); impossible quorum → rejected - ADVISORY — feedback, no gate: resolves to `advised` once everyone answered, and **no** answer (not even `rejected`) blocks. `rejectionThreshold` is ignored. Use it when you want notes on work nobody has to approve — an internal review before the client sees it. - rejectionThreshold — optional early reject after N rejections (all types except ADVISORY) - decisionScope — `request` (default) or `item` (per-item then rollup) List/detail include `outcome`: `{ status, summary, tally }`. Item scope tallies items. An advisory `tally` still counts what came back, so `rejected: 1` there means "one reviewer felt strongly", not "blocked". ## Reviewer delivery channels `reviewers[].channel` decides who delivers the review link: - `email` (default) — AgentFabric emails the reviewer, including reminders and activity digests. - `none` — AgentFabric mints the link and sends nothing. It comes back in `reviewerLinks` on create, add-reviewer and remind (`{ reviewerId, email, channel, reviewUrl, sent: false }`), and delivering it is yours: your own product, your own chat, your own agent. Reminder and digest emails are skipped for that reviewer, so `review_comment.created` webhooks are how you learn about replies. `reviewerLinks` is empty whenever every reviewer is on email, so an empty list means nothing is owed. ## Statuses Request/item: pending | approved | rejected | changes_required | advised | cancelled | expired Comment: open | resolved Reviewer decision: pending | approved | rejected | changes_required ## Key facts - Agents self-signup via create_tenant (MCP) or POST /v1/signup (REST). No human onboarding. - Prefer consumer API keys for agents; admin for tenant management. - Human reviewers do NOT need an account; they use a personal emailed link (auth code rrc_…). - Reviewer auth codes are not returned on create in production. - Every comment, approval, and rejection is recorded in an audit trail. ## Links - Docs: https://agentfabric.dev/docs/ - Concepts: https://agentfabric.dev/docs/concepts/ - MCP guide: https://agentfabric.dev/docs/mcp/ - REST reference: https://agentfabric.dev/docs/rest/ - Sign up: https://agentfabric.dev/signup/ - MCP: https://mcp.agentfabric.dev - REST: https://rest.agentfabric.dev