Resources
Shapes returned inside data (and nested on detail responses).
Tenant
{
"tenantId": "tenant_…",
"name": "Acme Agents",
"ownerEmail": "ops@example.com",
"ownerName": "Ops",
"status": "active" | "suspended",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
}
AppUser
{
"tenantId": "tenant_…",
"userId": "user_…",
"email": "ops@example.com",
"name": "Ops",
"role": "admin" | "consumer",
"cognitoSub": "…",
"status": "active" | "disabled",
"createdAt": "…",
"updatedAt": "…"
}
ApiKeyRecord
{
"tenantId": "tenant_…",
"keyId": "key_…",
"label": "CI consumer",
"scope": "admin" | "consumer",
"status": "active" | "revoked",
"createdAt": "…",
"lastUsedAt": "…"
}
Plaintext secret ak_… is returned only on create/signup/rotate — never as keyHash.
Document
{
"type": "url" | "image" | "pdf" | "text" | "html",
"url": "https://…",
"title": "optional",
"content": "inline body…",
"metadata": { }
}
Policy
{
"type": "ALL_APPROVE" | "ANY_APPROVE" | "ANY_REJECT" | "QUORUM",
"quorum": 2,
"rejectionThreshold": 1,
"decisionScope": "request" | "item"
}
ReviewRequest
{
"tenantId": "tenant_…",
"requestId": "rr_…",
"title": "Approve landing page",
"description": "optional",
"policy": { "type": "ALL_APPROVE", "decisionScope": "request" },
"status": "pending" | "approved" | "rejected" | "changes_required" | "cancelled" | "expired",
"subscribers": ["ops@example.com"],
"externalId": "landing-page-v2",
"metadata": { "env": "staging" },
"createdAt": "…",
"updatedAt": "…"
}
ReviewItem
{
"itemId": "item_…",
"title": "Preview",
"document": { "type": "url", "url": "https://example.com/page" },
"status": "pending" | "approved" | "rejected" | "changes_required" | "cancelled" | "expired",
"order": 0,
"externalId": "preview-asset-1",
"metadata": { "branch": "main" }
}
Reviewer
{
"reviewerId": "reviewer_…",
"email": "reviewer@example.com",
"name": "Alex",
"decision": "pending" | "approved" | "rejected" | "changes_required",
"authCodeExpiresAt": "…",
"openedAt": "…",
"decidedAt": "…"
}
Detail responses may also include authCodeHash. Plaintext authCode
(rrc_…) is omitted in production create/add responses.
ReviewItemDecision
{
"requestId": "rr_…",
"tenantId": "tenant_…",
"itemId": "item_…",
"reviewerId": "reviewer_…",
"decision": "approved" | "rejected" | "changes_required",
"decidedAt": "…"
}
{
"commentId": "comment_…",
"requestId": "rr_…",
"tenantId": "tenant_…",
"itemId": "item_…",
"reviewerId": "reviewer_…",
"authorEmail": "reviewer@example.com",
"body": "Please fix the hero CTA.",
"status": "open" | "resolved",
"createdAt": "…",
"resolvedAt": "…",
"resolvedBy": "ops@example.com",
"metadata": { }
}
AuditEvent
{
"eventId": "event_…",
"tenantId": "tenant_…",
"requestId": "rr_…",
"itemId": "item_…",
"reviewerId": "reviewer_…",
"type": "request_created" | "reviewer_added" | "reviewer_reminded" | "link_opened"
| "item_viewed" | "commented" | "comment_resolved" | "comment_unresolved"
| "approved" | "rejected" | "changes_required" | "policy_resolved" | "cancelled",
"createdAt": "…",
"metadata": { }
}
Outcome
{
"status": "pending",
"summary": "0/2 approved · 2 awaiting",
"tally": {
"approved": 0,
"rejected": 0,
"changes_required": 0,
"pending": 2
}
}
Presentation fields on list/detail:
outcome, evaluationScope ("request" | "item"),
optional itemOutcomes: [{ itemId, status }].
Review requests
POST /v1/review-requests
Auth: tenant · MCP create_review_request
Request
{
"title": "Approve landing page",
"description": "optional",
"externalId": "landing-page-v2",
"policy": { "type": "ALL_APPROVE", "decisionScope": "request" },
"reviewers": [{ "email": "reviewer@example.com", "name": "Alex" }],
"subscribers": ["ops@example.com"],
"items": [{
"id": "optional-client-id",
"title": "Preview",
"externalId": "preview-asset-1",
"document": { "type": "url", "url": "https://example.com/page" },
"metadata": { "branch": "main" }
}],
"metadata": { "env": "staging" }
}
Response data
{
"request": { /* ReviewRequest, status pending */ },
"items": [ /* ReviewItem */ ],
"reviewers": [{
"reviewerId": "reviewer_…",
"email": "reviewer@example.com",
"name": "Alex",
"decision": "pending",
"authCodeExpiresAt": "…"
}],
"next_actions": [
"wait_for_reviewers",
"fetch_review_request",
"open_dashboard"
]
}
GET /v1/review-requests
Auth: tenant · MCP list_review_requests
Query: optional filter[externalId]. Newest first.
Response data
[
{
/* ReviewRequest fields… */
"outcome": { /* Outcome */ },
"evaluationScope": "request" | "item",
"itemOutcomes": [{ "itemId": "item_…", "status": "pending" }]
}
]
Cancelled/expired rows omit presentation fields.
GET /v1/review-requests/{requestId}
Auth: tenant · MCP get_review_request
Response data
{
"request": { /* ReviewRequest */ },
"items": [ /* ReviewItem */ ],
"reviewers": [ /* Reviewer */ ],
"itemDecisions": [ /* ReviewItemDecision */ ],
"comments": [ /* ReviewComment */ ],
"events": [ /* AuditEvent */ ],
"outcome": { /* Outcome */ },
"evaluationScope": "request" | "item",
"itemOutcomes": [{ "itemId": "item_…", "status": "approved" }]
}
GET /v1/review-requests/{requestId}/items
Auth: tenant
Response data
[ /* ReviewItem */ ]
GET /v1/review-items
Auth: tenant · MCP list_review_items
Query: filter[externalId] required. Newest first.
Response data
{
"items": [{
"requestId": "rr_…",
"request": { /* ReviewRequest */ },
"item": { /* ReviewItem */ }
}]
}
POST /v1/review-requests/{requestId}/reviewers
Auth: tenant · MCP add_reviewer
Request
{
"reviewers": [{ "email": "new@example.com", "name": "Sam" }]
}
409 if email already assigned.
Response data
{ /* added reviewers (same shape as create) */ }
POST /v1/review-requests/{requestId}/remind
Auth: tenant · MCP remind_reviewers
Request
{ "emails": ["reviewer@example.com"] }
emails optional — defaults to all pending reviewers. Regenerates auth codes.
410 if cancelled/expired.
Response data
{
"reminded": [
{ "reviewerId": "reviewer_…", "email": "reviewer@example.com", "sent": true }
]
}
POST /v1/review-requests/{requestId}/cancel
Auth: tenant · MCP cancel_review_request
Response data
{ "status": "cancelled" }