API Reference

Admina exposes a REST + JSON-RPC 2.0 API β€” on :3000 in the default zero-Docker local mode, on :8080 under --stack. The interactive Swagger UI is always available at /docs on that same port.

Every example below uses :8080, the proxy port under admina dev --stack. In the default zero-Docker local mode (admina dev) the same API β€” and /docs β€” is served by a single process on :3000; swap the port accordingly. See Quick Start.

Authentication: Every endpoint requires the X-API-Key header (or Authorization: Bearer <key>) when ADMINA_API_KEY is set, except for the exempt paths listed below. See Configuration. The OpenAI-compatible gateway routes (POST /v1/chat/completions, GET /v1/models) are covered by the same credential check. The only exempt paths are /health, /docs, /openapi.json, /redoc, /metrics and the bundled dashboard shell (/, /heimdall.png, /vendor/*) β€” the dashboard's own /api/* calls still authenticate, via a signed session cookie. The ?api_key= query-parameter form is accepted only on the dashboard WebSocket upgrade, never on HTTP requests.

Core endpoints

GET /health public

Health check. Returns proxy status, service name, version, engine diagnostics and a timestamp. Always public.

curl http://localhost:8080/health
# Response
{
  "status": "healthy",
  "service": "admina-proxy",
  "version": "0.11.1",
  "engine": {
    "engine": "rust",
    "rust_available": true,
    "rust_version": "0.11.1",
    "selection": "auto",
    "active": "rust",
    "pii_active": "python"
  },
  "timestamp": "2026-05-21T09:30:00+00:00"
}

engine is a nested object, not a string. selection echoes ADMINA_ENGINE (default auto), active is the resolved firewall / loop-breaker engine, and pii_active is resolved separately β€” under auto the PII path stays on Python for full recall even when Rust is available.

POST /mcp auth

Main governance proxy endpoint. Accepts any MCP JSON-RPC 2.0 message, applies all 4 governance domains bidirectionally, and forwards to the upstream MCP server. The response includes governance metadata as HTTP headers.

A pluggable governance guard that raises ValueError, RuntimeError, OSError or TypeError is skipped by default β€” recorded as an ERROR entry in the request-side checks, logged only on the response side. With ADMINA_GUARD_FAIL_MODE=closed the exception becomes a BLOCK instead, on both the request and the response side of this endpoint. See Configuration.

curl -X POST http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $ADMINA_API_KEY" \
  -H "X-Session-Id: my-session" \
  -H "X-Agent-Id: my-agent" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "read_file",
      "arguments": { "path": "/etc/hostname" }
    }
  }'

Request headers:

X-Session-IdSession identifier for rate limiting and loop detection
X-Agent-IdAgent identifier for audit trail and OTEL spans

There is no header to override the upstream. In single-upstream mode everything goes to UPSTREAM_MCP_URL. With multi-upstream routing configured, the target is selected either by path β€” POST /mcp/route/{server} β€” or, when no route prefix is given, by resolving the called tool name (params.name) against the routing table.

Response headers added by Admina:

X-Admina-Event-IdUnique id for this governance event (correlates with forensic log / OTEL span)
X-Admina-Governance-ActionAlways ALLOW β€” these headers are only emitted on the forwarded (success) path
X-Admina-Latency-UsGovernance overhead in microseconds
X-Admina-Forensic-HashTruncated (16-char) forensic record hash, when present

Denied requests carry no X-Admina-* headers at all β€” they are signalled by status code and JSON-RPC error body instead. A block returns HTTP 403 with error code -32600 (reason: "injection_detected"); a circuit break returns HTTP 429 with error code -32000 (reason: "reasoning_loop"). Both error payloads include event_id, so correlation with the forensic log still works. No proxied request ever reports REDACT here: PII redaction rewrites the body in place and the request is still forwarded as an ALLOW. (REDACT is a value of the /api/v1/validate REST contract only.)

Governance stats

GET /api/stats auth

Aggregate governance statistics, nested one block per subsystem: the proxy's own request counters, engine diagnostics, and the per-domain counters of the firewall, loop breaker, PII redactor, forensic black box and compliance engine.

curl http://localhost:8080/api/stats \
  -H "X-API-Key: $ADMINA_API_KEY"
{
  "proxy": {
    "requests_total": 10482,
    "requests_blocked": 146,
    "requests_allowed": 10336,
    "requests_redacted": 35,
    "avg_latency_ms": 0.42,
    "started_at": "2026-05-21T09:30:00+00:00"
  },
  "engine": {
    "engine": "rust",
    "rust_available": true,
    "rust_version": "0.11.1",
    "selection": "auto",
    "active": "rust",
    "pii_active": "python"
  },
  "firewall": {
    "total_checked": 10482,
    "total_blocked": 142,
    "block_rate": 1.35,
    "detections_by_type": { "instruction_override": 96, "role_hijack": 46 }
  },
  "loop_breaker": { "active_sessions": 12, "total_blocked": 4 },
  "pii_redactor": {
    "total_redacted": 35,
    "redactions_by_type": { "EMAIL": 22, "IBAN": 13 },
    "spacy_available": true
  },
  "forensic_blackbox": {
    "record_count": 10482,
    "chain_head": "9f2c1a7b4e8d0c35...",
    "storage_available": true
  },
  "compliance": { "total_assessments": 3, "enforcement_deadline": "2027-12-02" },
  "routing": {}
}

Latency is reported in milliseconds (avg_latency_ms), as a running average rounded to 2 decimals β€” there is no percentile series on this endpoint. forensic_blackbox is {} when no forensic backend is configured, and routing is {} unless multi-upstream routing is enabled.

Validation & audit

POST /api/v1/validate auth

Inline governance check. Submit content for validation without proxying to upstream. Returns the governance decision.

Breaking in v0.11.0. The response action value formerly returned as "MODIFY" is now "REDACT". There is no compatibility shim β€” external callers that compare against "MODIFY" must be updated. The in-repo n8n node and Cheshire Cat AI plugin were updated in the same release (see Integrations); the OpenClaw skill needed no change because it never referenced the value. Admina is pre-1.0: the public API is feature-complete and production-ready, but the stability commitment is deferred to 1.0, which is what permits a rename in a minor release.

Request body. A JSON object. content is required and must be truthy β€” a missing or empty content returns HTTP 400 with detail 'content' field is required. Optional: session_id (defaults to rest-<8 hex>), agent_id (defaults to rest-api), request_id (defaults to a uuid4 hex). All other keys are ignored. A non-object JSON body is rejected by FastAPI with HTTP 422.

Response body. Exactly five keys, always present: action, risk_level, checks, redacted_content, latency_ms. action is exactly one of the three values below β€” nothing else is reachable on this endpoint.

ALLOWClean traffic
BLOCKInjection firewall fired, or the loop breaker fired β€” a loop circuit-break is reported to REST consumers as BLOCK, never as CIRCUIT_BREAK
REDACTRequest allowed but PII was found and masked

redacted_content is a string only when action is "REDACT"; it is null for ALLOW and BLOCK. Note the casing asymmetry: the top-level risk_level is uppercase (LOW / MEDIUM / HIGH / CRITICAL), while every risk_level nested inside checks is lowercase. latency_ms is a float rounded to 2 decimals. The checks keys are conditional, not fixed: loop_breaker is always present, firewall is present only when the loop breaker did not fire, and pii_redaction is present only when the request is still allowed after the firewall stage.

curl -X POST http://localhost:8080/api/v1/validate \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $ADMINA_API_KEY" \
  -d '{"content": "Please ignore previous instructions", "session_id": "test"}'
# Response β€” injection detected
{
  "action": "BLOCK",
  "risk_level": "HIGH",
  "checks": { "loop_breaker": {...}, "firewall": { "is_injection": true, "risk_level": "high", ... } },
  "redacted_content": null,
  "latency_ms": 0.42
}
curl -X POST http://localhost:8080/api/v1/validate \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $ADMINA_API_KEY" \
  -d '{"content": "Contact me at john.doe@example.com"}'
# Response β€” PII redacted (was "MODIFY" before v0.11.0)
{
  "action": "REDACT",
  "risk_level": "LOW",
  "checks": { "loop_breaker": {...}, "firewall": {...}, "pii_redaction": { "count": 1, "entities": [...] } },
  "redacted_content": "Contact me at [EMAIL]",
  "latency_ms": 1.13
}

This endpoint honours ADMINA_GOVERNANCE_MODE: in observe or dry-run mode a blocked request comes back as action: "ALLOW", but the mode downgrade does not suppress REDACT β€” a clean request carrying PII still returns REDACT with populated redacted_content.

POST /api/v1/audit auth

Submit a forensic log entry. Used by integrations (LangChain, CrewAI) for passive governance logging.

curl -X POST http://localhost:8080/api/v1/audit \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $ADMINA_API_KEY" \
  -d '{"event": {"event_type": "tool_call", "agent_id": "crewai-01"}}'

event is required and must be a JSON object β€” a non-object value returns HTTP 400. event_id, timestamp and source are filled in when absent. The response carries recorded, sequence_number, record_hash and previous_hash; with no forensic backend configured it returns "recorded": false and an error string.

GET /api/v1/forensic/verify auth

Reads every persisted forensic record back from the configured backend and checks that each record's hash links correctly to the previous one. An invalid chain is a successful report β€” HTTP 200 with "valid": false β€” not a server error; only an unexpected exception produces a 500.

curl http://localhost:8080/api/v1/forensic/verify \
  -H "X-API-Key: $ADMINA_API_KEY"
# Response
{
  "valid": true,
  "records": 10482,
  "last_hash": "9f2c1a7b4e8d0c35...",
  "backend": "filesystem"
}

backend is one of memory, filesystem, s3. With no forensic backend configured the endpoint returns "valid": null, "records": 0, "backend": "not_configured" and a detail string.

Dashboard

GET /api/dashboard/score auth

Admina Score β€” a live-runtime weighted composite (0–100) powering the headline metric on the bundled Alpine.js dashboard. Unlike the OISG Score (static capability assessment), the Admina Score reflects what is actually happening right now on this instance: audited traffic, compliance posture, attack rate, and hash-chain integrity (plus a fixed residency credit for any running proxy).

β†’ Full formula, component breakdown, and guidance for improving each component: Admina Score.

curl http://localhost:8080/api/dashboard/score \
  -H "X-API-Key: $ADMINA_API_KEY"
# Response
{
  "score": 87,
  "max_score": 100,
  "breakdown": {
    "data_residency": 25,
    "interactions_audited": 25,
    "eu_ai_act_coverage": 12,
    "no_recent_attacks": 15,
    "forensic_chain_valid": 10
  },
  "computed_at": "2026-05-21T09:30:00+00:00"
}
GET /api/dashboard/oisg auth

OISG Adequacy Score (Open Β· Intelligent Β· Secure Β· Governed) β€” four pillars of five criteria each (5 points per criterion), for a 0–100 total. Unlike the Admina Score (live runtime metrics), this is a static capability assessment of the instance: which governance features are wired up and available. On the dashboard it appears in a separate "Instance Configuration" panel as a 2Γ—2 quadrant map. See oisg.ai and the dedicated OISG page for the full paradigm.

curl http://localhost:8080/api/dashboard/oisg \
  -H "X-API-Key: $ADMINA_API_KEY"
# Response (truncated)
{
  "total": 85,
  "max_total": 100,
  "level": "OISG adequate",
  "pillars": {
    "open":        { "score": 20, "max_score": 25, "criteria": [...] },
    "intelligent": { "score": 20, "max_score": 25, "criteria": [...] },
    "secure":      { "score": 25, "max_score": 25, "criteria": [...] },
    "governed":    { "score": 20, "max_score": 25, "criteria": [...] }
  },
  "computed_at": "2026-05-21T09:30:00+00:00"
}

Levels: 0–24 Critical gaps Β· 25–49 Partial coverage Β· 50–79 Good coverage Β· 80–100 OISG adequate.

WS /api/dashboard/live auth

Live governance event stream via WebSocket. Used by the dashboard for real-time updates.

wscat -c ws://localhost:8080/api/dashboard/live \
  -H "X-API-Key: $ADMINA_API_KEY"

Compliance

Decision-support, not legal advice. Admina's compliance modules (EU AI Act, NIS2, GDPR, cross-regulation matrix) are self-assessment aids. A passing score in gap-analysis does not constitute legal compliance and does not replace conformity assessment under EU AI Act Art. 43, NIS2 designated authority audit, or GDPR DPO review. See Compliance domain for scope.

EU AI Act

POST /api/compliance/classify auth

Classify an AI system's risk level under the EU AI Act risk taxonomy. Classification is keyword-driven over description, use_case and data_types, and returns one of four categories β€” unacceptable, high, limited, minimal β€” together with the static profile of that category. The gap analysis against Art. 9–15 is a separate endpoint, POST /api/compliance/gap-analysis (below).

curl -X POST http://localhost:8080/api/compliance/classify \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $ADMINA_API_KEY" \
  -d '{
    "description": "AI credit scoring system for consumer loans",
    "use_case": "financial risk assessment",
    "data_types": ["financial", "personal", "behavioral"]
  }'
{
  "risk_category": "high",
  "level": 3,
  "description": "High-risk AI systems requiring conformity assessment",
  "examples": [
    "credit scoring",
    "recruitment",
    "law enforcement",
    "critical infrastructure",
    "healthcare diagnostics"
  ],
  "action": "Requires conformity assessment, logging, human oversight"
}

level is an ordinal: 4 unacceptable, 3 high, 2 limited, 1 minimal. examples and action are the static profile of the matched category, not an analysis of the submitted system β€” no gaps, article, justification or enforcement_deadline field is returned here. The Annex III enforcement deadline (2027-12-02, postponed from 2 Aug 2026 by the Omnibus VII agreement of 7 May 2026) is exposed by the compliance block of /api/stats.

POST /api/compliance/gap-analysis auth

Run a gap analysis against the Art. 9–15 requirements. Body: risk_category (default high) and current_compliance, a map of requirement key to a list of booleans, one per check. Returns compliance_score, total_checks, passed_checks, gaps, gap_count, status, enforcement_deadline and assessed_at. For a limited or minimal category it short-circuits to "applicable": false with a message. This is the call that feeds the eu_ai_act_coverage component of the Admina Score.

POST /api/compliance/report auth

Generate a structured EU AI Act report for one submitted system. The body carries the system's own facts β€” system_name, description, use_case, data_types and current_compliance β€” and the call runs classification and gap analysis in a single shot, returning the combined report. Nothing is read from stored state, but the gap analysis it runs internally is recorded β€” it becomes the latest EU AI Act assessment seen by the GET report below and by the Admina Score.

GET /api/compliance/report auth

Same path, different report: a consolidated snapshot of the running instance, with no body. It aggregates the latest EU AI Act assessment, the latest NIS2 assessment, the GDPR RoPA stats and records, the cross-regulation matrix, the proxy's runtime metrics and the forensic black box stats. Serialisation is selected with ?format=json (default), csv or markdown.

NIS2 β€” self-assessment

GET /api/compliance/nis2/areas auth

List the 10 NIS2 Art. 21(2) measure areas with 4 controls each β€” 40 checks total: policies on risk analysis and information system security; incident handling; business continuity (backup, disaster recovery, crisis management); supply chain security; security in network and IS acquisition, development, and maintenance; policies and procedures to assess effectiveness of measures; basic cyber hygiene practices and cybersecurity training; policies and procedures regarding the use of cryptography; human resources security, access control policies, asset management; multi-factor authentication and secure communications.

POST /api/compliance/nis2/assess auth

Submit your self-assessment answers and receive an area-by-area gap report.

GDPR β€” RoPA & DPIA

GET /api/compliance/gdpr/records auth

List Article 30 Records of Processing Activities (RoPA). The registry is in-memory by default; persistence is opt-in through the ADMINA_GDPR_ROPA_PATH environment variable (or an explicit storage_path= when constructing the registry in-process). Note that the commented gdpr.ropa_path key in admina.yaml.example is inert β€” the config loader has no gdpr section and the proxy builds the registry with no arguments, so only the environment variable takes effect.

POST /api/compliance/gdpr/records auth

Create a new Art. 30 record. Typed schema covering controller, purposes, categories of data, recipients, transfers, retention, and security measures.

Per-record CRUD also available at GET / PUT / DELETE /api/compliance/gdpr/records/{activity_id}.

POST /api/compliance/gdpr/dpia/template auth

Generate an Art. 35 Data Protection Impact Assessment scaffold from operator-supplied facts. Returns a Markdown template populated with the provided context, structured for legal review.

Cross-regulation matrix

GET /api/compliance/matrix auth

Hand-curated mapping of 12 operational controls across EU AI Act, NIS2, and GDPR. Surfaces obligations that satisfy multiple frameworks in a single control β€” useful to avoid duplicate audit work.

OpenAI-compatible gateway

New in v0.11.0. The proxy exposes an OpenAI-compatible HTTP surface on the same port (8080) as /mcp, so any OpenAI-compatible client or front-end can be pointed at Admina and have its traffic governed. Requests are forwarded to a single configurable upstream set by ADMINA_GATEWAY_UPSTREAM (default http://localhost:11434/v1, i.e. a local Ollama).

Keyless upstreams only. The gateway sends only an X-Admina-Event-Id header upstream β€” it never forwards the caller's Authorization header, and there is no setting for an upstream API key. It therefore works with keyless upstreams such as Ollama, a local vLLM server, or LM Studio. See Configuration for the gateway variables.
POST /v1/chat/completions auth

Runs the governance pipeline inline on the prompt before forwarding β€” injection firewall, PII redaction, and pluggable governance guards. Loop detection does not run on this surface. Exactly one forensic record is written per request, before the upstream call.

Optional request headers:

X-Session-IdSession identifier, default default; truncated to 128 characters
X-Agent-IdAgent identifier, default gateway; truncated to 128 characters

Only messages, model and stream are read by Admina. Every other field (temperature, max_tokens, tools, top_p, stop, response_format, …) is passed through to the upstream verbatim. messages is replaced with the PII-redacted version only when at least one PII entity was found.

curl -X POST http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $ADMINA_API_KEY" \
  -d '{
    "model": "llama3.1:8b",
    "messages": [{"role": "user", "content": "Summarise this clause…"}]
  }'

On an allowed request the upstream JSON is returned verbatim, preserving the upstream HTTP status code, with the choices array PII-redacted in place. No wrapper object and no extra Admina fields are added.

Set "stream": true to get text/event-stream. Deltas are re-emitted as chat.completion.chunk objects redacted through a 64-character windowed recomposition buffer, and the governed stream always terminates with the literal line data: [DONE]. Only choices[0] is re-emitted, so n > 1, tool-call deltas and usage chunks from stream_options are not carried through; a streamed reply is also always served as HTTP 200 even if the upstream status differed.

Blocked requests return HTTP 200. A governance BLOCK returns a synthetic OpenAI completion so OpenAI-compatible UIs render the refusal instead of erroring. Clients cannot detect a block by status code β€” only by finish_reason == "content_filter". The message body is ADMINA_GATEWAY_BLOCK_MESSAGE (default: This request was blocked by the Admina governance policy.). On a block the upstream is never contacted. A blocked streaming request gets the same treatment in SSE form: a single chat.completion.chunk carrying the message and finish_reason: "content_filter", followed by data: [DONE].
# Response on BLOCK β€” HTTP 200
{
  "id": "chatcmpl-admina-1a2b3c4d5e6f",
  "object": "chat.completion",
  "created": 1784200000,
  "model": "llama3.1:8b",
  "choices": [{
    "index": 0,
    "message": {"role": "assistant", "content": "This request was blocked by the Admina governance policy."},
    "finish_reason": "content_filter"
  }],
  "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
}

A non-JSON body returns HTTP 400 with detail Invalid JSON body; an unreachable upstream (connection error) returns HTTP 502 with detail Gateway upstream unreachable. The shared HTTP client has a 30-second timeout.

One forensic record is written per request with event_type gateway_request, carrying event_id, agent_id, session_id, method: "chat.completions", action, risk_level, governance_latency_ms and checks. It contains no prompt text, no model name and no response content.

GET /v1/models auth

Proxies the upstream model list, returning its body and status code as-is. This route is a passthrough: it is not governed, not PII-scanned and not written to the forensic log.

ADMINA_GATEWAY_MODELS_ALLOWLIST (comma-separated, empty by default) filters the returned data[] by model id. The allow-list applies only to this endpoint β€” POST /v1/chat/completions does not check the requested model against it, so it is a listing filter, not an access-control policy.

curl http://localhost:8080/v1/models \
  -H "X-API-Key: $ADMINA_API_KEY"

The gateway is not metered by the proxy's per-session rate limiter, and it publishes nothing to the dashboard live feed. It is also absent from the per-request proxy counters (the proxy block of /api/stats and the admina_requests_* series in /metrics), which only /mcp increments. It does, however, share the firewall, PII and forensic instances, so the domain-level counters exposed by both (admina_firewall_total_checked, PII and forensic-record totals) include gateway traffic. Per-request detail lives only in the hash-chained forensic log.

Metrics

GET /metrics public

Prometheus metrics endpoint. Scrape it from your Prometheus / Grafana stack.

curl http://localhost:8080/metrics | head

OpenAPI / Swagger

The full interactive API documentation is available at http://localhost:8080/docs when the proxy is running. It includes request/response schemas, authentication, and a live "Try it out" interface.

# Open in browser
open http://localhost:8080/docs

# Download OpenAPI spec
curl http://localhost:8080/openapi.json