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.
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
/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.
/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 detectionX-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) pathX-Admina-Latency-UsGovernance overhead in microsecondsX-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
/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
/api/v1/validate auth Inline governance check. Submit content for validation without proxying to upstream. Returns the governance decision.
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 trafficBLOCKInjection firewall fired, or the loop breaker fired β a loop circuit-break is reported to REST consumers as BLOCK, never as CIRCUIT_BREAKREDACTRequest allowed but PII was found and maskedredacted_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.
/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.
/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
/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"
} /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.
/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
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
/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.
/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.
/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.
/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
/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.
/api/compliance/nis2/assess auth Submit your self-assessment answers and receive an area-by-area gap report.
GDPR β RoPA & DPIA
/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.
/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}.
/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
/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).
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.
/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 charactersX-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.
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.
/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
/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