API Reference
API
UI (Console): https://console.agenttrust.io (dev: https://dev.console.agenttrust.io, preview: https://preview.console.agenttrust.io)
API (Gateway): keep using gateway.* for /api/decision and /api/health
This document describes the current MVP API surface for AgentTrust.
- Decisions are deterministic (non‑LLM).
- Policies evolve via versioned policy models that map intent to outcomes.
- Every decision request produces a structured trace and is logged/persisted.
- Known constraints are tracked in Known Limitations: /docs/known-limitations.
Base URLs
- Local dev (Codespaces / local Next dev server):
http://localhost:3000 - DEV (Vercel):
https://dev.gateway.agenttrust.io - PREVIEW (Vercel):
https://preview.gateway.agenttrust.io - PROD (Vercel):
https://gateway.agenttrust.io
Conventions
- requests must send
POST.Content-Type: application/json - Timestamps should be ISO 8601 (e.g. ).
2025-12-16T12:34:56.789Z - The gateway is intended to be called before executing an action.
- If the gateway cannot persist a trace, it fails closed (returns deny).
- routes must return JSON and must never redirect to login.
/api/*
Environment + DB consistency requirements (Critical)
Branch → environment:
- → DEV
dev - → PREVIEW
preview - → PRODUCTION
main
Database model:
- DEV / Preview / Production use three separate Supabase projects (not branching).
- Deployments must point to the correct Supabase project via Vercel environment variables:
- must match the intended Supabase hostname for that environment.
NEXT_PUBLIC_SUPABASE_URL
- If the environment points to the wrong Supabase project, migrations and runtime behavior will drift.
Operational guardrails (expected):
- CI guardrail verifies branch → Supabase project mapping (fail closed).
- Runtime env guard verifies matches the Supabase hostname (fail closed).
VERCEL_GIT_COMMIT_REF - A read-only schema snapshot workflow is used before/after promotions:
- should show Local == Remote
supabase migration list - should show No schema changes found
supabase db diff --linked --schema public
See:
docs/promotion-runbook.mddocs/db-rollback-plan.md
Tenancy Derivation (Critical)
- /api/decision: tenant is derived from hash lookup (server-side). Client body must not provide tenant identity.
x-api-key - UI-auth routes (invites, CRUD pages): tenant scope is derived from the authenticated user context and (membership-checked).
current_tenant_id()
Agent Identity & Policy Gateway - API
Health
GET /api/health
Response:
{
"status": "ok",
"service": "agent-identity-policy-gateway",
"timestamp": "ISO8601"
}
Decision
POST /api/decision Request (JSON):
{
"agentId": "customer_support_agent",
"toolId": "reset_password_api",
"userId": "optional",
"userLogin": "optional",
"userEmail": "optional",
"environment": "prod",
"requestId": "optional",
"timestamp": "optional ISO8601",
"params": { "any": "json" }
}
Response (JSON):
{
"decision": "allow|deny",
"reason": "string",
"matchedPolicyId": "string|null",
"explanation": "string",
"traceId": "uuid",
"requestId": "string"
}
Notes:
- Decisions are deterministic (rule engine), not model-based.
- Tenant derivation for decision: the server resolves tenant from hash →
x-api-key. The request body must not be used to select tenant.api_keys.tenant_id - Every request is persisted as a structured trace in Supabase ().
decision_traces - If trace persistence fails, the gateway fails closed with HTTP 500 + deny.
Trace Schema v1 (stored in Supabase decision_traces.trace
)
decision_traces.traceEach decision call generates a structured trace object (JSON). Minimum fields:
{
"traceId": "uuid",
"requestId": "string",
"receivedAt": "ISO8601",
"requestTimestamp": "ISO8601|null",
"tenantId": "string",
"agentId": "string",
"toolId": "string",
"user": {
"userId": "string|null",
"login": "string|null",
"email": "string|null"
},
"environment": "string",
"params": "object",
"network": {
"ipAddress": "string|null",
"userAgent": "string|null"
},
"result": {
"decision": "allow|deny",
"reason": "string",
"matchedPolicyId": "string|null"
}
}
Error response format
Two shapes are currently used:
A) General endpoints (most /api/*)
{
"ok": false,
"error": {
"code": "SOME_CODE",
"message": "Human-friendly message"
}
}
B) Decision endpoint diagnostics
/api/decision{
"error": {
"code": "SOME_CODE",
"message": "Human-friendly message"
},
"requestId": "uuid",
"traceId": "uuid"
}
Notes:
- supports troubleshooting across logs/support.
requestId - is included only when a trace exists.
traceId
Invites (Org Multi-Tenancy)
Invites support adding an existing authenticated user to a tenant.
Create invite
POST /api/invites/create
Auth: Required (Supabase cookie session).
Request:
{
"email": "optional email to bind invite",
"role": "owner|admin|member",
"expiresInHours": 24
}
Response (200):
{
"ok": true,
"invite": {
"id": "uuid",
"token": "inv_<PLAINTEXT_TOKEN_RETURNED_ONCE>",
"tokenPrefix": "string",
"expiresAt": "ISO8601"
}
}
Security notes:
- The plaintext is returned once at creation time.
token - The database stores (sha256 hex) +
token_hashonly (never plaintext).token_prefix
Common errors:
- 401
NOT_AUTHENTICATED - 403
CSRF_BLOCKED - 403
NOT_AUTHORIZED
Accept invite
POST /api/invites/accept
Auth: Required (Supabase cookie session).
Request:
{ "token": "inv_<PLAINTEXT_TOKEN>" }
Response (200):
{ "ok": true }
One-time semantics:
- After acceptance, the invite is marked and cannot be reused.
accepted_at/by - A second attempt must fail with (or equivalent).
INVITE_INVALID
Common errors:
- 400 (invalid/expired/already-used/email mismatch)
INVITE_INVALID - 401
NOT_AUTHENTICATED - 403
CSRF_BLOCKED - 429
RATE_LIMITED - 500 (fail-closed, no internals leaked)
SERVER_ERROR
Missing API key (x-api-key)
Request:
curl -i -X POST "<GATEWAY_URL>/api/decision" \
-H "content-type: application/json" \
-d '{}'
Response (401):
{
"error": {
"code": "MISSING_API_KEY",
"message": "Missing x-api-key header."
},
"requestId": "uuid",
"traceId": "uuid"
}