Skip to content
Docs

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

  • POST
    requests must send
    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).
  • /api/*
    routes must return JSON and must never redirect to login.

Environment + DB consistency requirements (Critical)

Branch → environment:

  • dev
    → DEV
  • preview
    → PREVIEW
  • main
    → PRODUCTION

Database model:

  • DEV / Preview / Production use three separate Supabase projects (not branching).
  • Deployments must point to the correct Supabase project via Vercel environment variables:
    • NEXT_PUBLIC_SUPABASE_URL
      must match the intended Supabase hostname for that environment.
  • 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
    VERCEL_GIT_COMMIT_REF
    matches the Supabase hostname (fail closed).
  • A read-only schema snapshot workflow is used before/after promotions:
    • supabase migration list
      should show Local == Remote
    • supabase db diff --linked --schema public
      should show No schema changes found

See:

  • docs/promotion-runbook.md
  • docs/db-rollback-plan.md

Tenancy Derivation (Critical)

  • /api/decision: tenant is derived from
    x-api-key
    hash lookup (server-side). Client body must not provide tenant identity.
  • UI-auth routes (invites, CRUD pages): tenant scope is derived from the authenticated user context and
    current_tenant_id()
    (membership-checked).

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
    x-api-key
    hash →
    api_keys.tenant_id
    . The request body must not be used to select tenant.
  • 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
)

Each 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
may also include correlation IDs when applicable:

{
  "error": {
    "code": "SOME_CODE",
    "message": "Human-friendly message"
  },
  "requestId": "uuid",
  "traceId": "uuid"
}

Notes:

  • requestId
    supports troubleshooting across logs/support.
  • traceId
    is included only when a trace exists.

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
    token
    is returned once at creation time.
  • The database stores
    token_hash
    (sha256 hex) +
    token_prefix
    only (never plaintext).

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
    accepted_at/by
    and cannot be reused.
  • A second attempt must fail with
    INVITE_INVALID
    (or equivalent).

Common errors:

  • 400
    INVITE_INVALID
    (invalid/expired/already-used/email mismatch)
  • 401
    NOT_AUTHENTICATED
  • 403
    CSRF_BLOCKED
  • 429
    RATE_LIMITED
  • 500
    SERVER_ERROR
    (fail-closed, no internals leaked)

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"
}