All endpoints are served on port 8080 by default. All request and response bodies are JSON unless noted. Error responses always include an {"error": "..."} body.
Liveness probe. Returns 200 when the process is running.
curl http://localhost:8080/healthz{"status": "ok", "service": "midas"}Readiness probe. Returns 200 when MIDAS is ready to serve requests.
curl http://localhost:8080/readyz{"status": "ready", "service": "midas"}These endpoints are intended for external orchestrators and monitors. The production container image is distroless and does not include wget, curl, or a shell, so container-internal healthchecks using those tools will not work. Configure /readyz as the target for Kubernetes readinessProbe, load-balancer health checks, or equivalent external probes.
Evaluate whether an agent is within authority to perform an action on a decision surface.
Maximum request body: 1 MiB.
Evaluation
MIDAS v1 evaluates against explicitly declared structure. Pre-create the
structural entities via POST /v1/controlplane/apply; then provide the
process_id on every evaluate request. The process must exist and the
surface must belong to it. In permissive structural mode (the default)
process_id may be omitted; in enforced mode the request returns 400
when omitted.
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
surface_id |
string | Yes | Decision surface ID. |
agent_id |
string | Yes | Agent ID. |
confidence |
float64 | Yes | Caller confidence score in [0.0, 1.0]. |
process_id |
string | No | Governed process ID. Required in enforced structural mode. |
consequence |
object | No | Consequence value. |
consequence.type |
string | — | monetary or risk_rating. |
consequence.amount |
float64 | — | Monetary amount (monetary type). |
consequence.currency |
string | — | ISO 4217 currency code (monetary type). |
consequence.risk_rating |
string | — | low, medium, high, or critical (risk_rating type). |
context |
object | No | Arbitrary key-value map. Profile may require specific keys. |
request_id |
string | No | Caller idempotency key. UUID generated if omitted. |
request_source |
string | No | Source system identifier. Defaults to "api". Scopes request_id. |
Response fields
| Field | Type | Description |
|---|---|---|
outcome |
string | accept, escalate, reject, or request_clarification. |
reason |
string | Reason code — see table below. |
envelope_id |
string | Envelope UUID. Present on all outcomes. |
explanation |
string | Optional narrative. |
Outcomes and reason codes
| Outcome | Reason code |
|---|---|
accept |
WITHIN_AUTHORITY |
escalate |
CONFIDENCE_BELOW_THRESHOLD |
escalate |
CONSEQUENCE_EXCEEDS_LIMIT |
escalate |
POLICY_DENY |
escalate |
POLICY_ERROR |
reject |
AGENT_NOT_FOUND |
reject |
SURFACE_NOT_FOUND |
reject |
SURFACE_INACTIVE |
reject |
NO_ACTIVE_GRANT |
reject |
PROFILE_NOT_FOUND |
reject |
GRANT_PROFILE_SURFACE_MISMATCH |
request_clarification |
INSUFFICIENT_CONTEXT |
Example
curl -s -X POST http://localhost:8080/v1/evaluate \
-H "Content-Type: application/json" \
-d '{
"surface_id": "surf-loan-auto-approval",
"process_id": "proc-loan-standard",
"agent_id": "agent-credit-001",
"confidence": 0.91,
"consequence": {"type": "monetary", "amount": 4500, "currency": "GBP"},
"context": {"customer_id": "C-8821", "risk_band": "low"},
"request_id": "req-00512",
"request_source": "lending-service"
}'{
"outcome": "accept",
"reason": "WITHIN_AUTHORITY",
"envelope_id": "01927f3c-8e21-7a4b-b9d0-2c4f6e8a1d3e"
}Error cases
| Status | Condition |
|---|---|
400 |
Missing surface_id or agent_id; confidence outside [0.0, 1.0]; invalid characters in request_id; malformed JSON; process_id absent in enforced structural mode; process_id present but process not found or belongs to wrong surface |
409 |
Same (request_source, request_id) submitted with a different payload |
413 |
Body exceeds 1 MiB |
500 |
Orchestrator not configured |
Note: authority outcomes (reject, escalate, etc.) are returned with HTTP 200. They are valid authority decisions, not HTTP errors.
Submit a reviewer decision for an escalated envelope in awaiting_review state.
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
envelope_id |
string | Yes | UUID of the envelope to resolve. |
decision |
string | Yes | approve/accept or reject/deny. |
reviewer |
string | Yes | Reviewer identifier (1–255 characters, no control characters). |
notes |
string | No | Free-text justification. |
Example
curl -s -X POST http://localhost:8080/v1/reviews \
-H "Content-Type: application/json" \
-d '{
"envelope_id": "01927f3c-9b44-7c11-a8e2-4d5a7f9c2b1f",
"decision": "approve",
"reviewer": "user-compliance-lead",
"notes": "Manual review completed"
}'{
"envelope_id": "01927f3c-9b44-7c11-a8e2-4d5a7f9c2b1f",
"status": "resolved"
}Error cases
| Status | Condition |
|---|---|
400 |
Missing envelope_id or decision; invalid reviewer; unrecognised decision value |
404 |
Envelope not found |
409 |
Envelope not in awaiting_review state, or already closed |
List governance envelopes. Accepts an optional ?state= query parameter.
Valid state values: received, evaluating, outcome_recorded, escalated, awaiting_review, closed.
Omitting state returns all envelopes.
curl -s "http://localhost:8080/v1/envelopes?state=closed" | jq .Returns a JSON array of envelope objects.
Retrieve a single envelope by its UUID.
curl -s http://localhost:8080/v1/envelopes/01927f3c-8e21-7a4b-b9d0-2c4f6e8a1d3e | jq .The envelope contains five sections: identity, submitted, resolved, evaluation, integrity. Also includes state, created_at, updated_at, closed_at, and review (for escalated envelopes).
Error cases
| Status | Condition |
|---|---|
400 |
Missing or invalid envelope ID |
404 |
Envelope not found |
The /v1/evidence/* family exposes the runtime audit-event chain and integrity verification for one envelope, plus a cross-envelope event search and a composed evidence-packet export. All four endpoints require platform.viewer or above and operate against the production audit / envelope repositories (disjoint from the Explorer workbench routes).
| Endpoint | Purpose |
|---|---|
GET /v1/evidence/envelopes/{id}/audit-events |
Ordered audit-event chain for one envelope (sequence_no ASC). |
GET /v1/evidence/audit-events |
Cross-envelope event search with event_type / event_types / envelope_id / request_source / request_id / since / until / limit / order filters. |
GET /v1/evidence/envelopes/{id}/integrity |
Per-envelope chain verification. HTTP 200 with valid: false reports integrity findings in-band; HTTP 500 is reserved for repository / read failures. |
GET /v1/evidence/envelopes/{id}/packet |
One response composing envelope + audit events + integrity. The envelope sub-object preserves the /v1/envelopes/{id} shape (including submitted.raw); the packet wrapper itself has no top-level submitted field. |
See docs/operations/runtime-evidence-api.md for the operator guide: filter semantics, integrity error-kind taxonomy, packet composition rules, privacy posture, and current limitations (no redaction / signing / streaming / batch / cursor / payload_contains today).
List all envelopes in awaiting_review state — the pending escalation queue.
curl -s http://localhost:8080/v1/escalations | jq .Returns a JSON array of envelope objects. Equivalent to GET /v1/envelopes?state=awaiting_review.
Retrieve the envelope for a given request_id. Accepts an optional ?source= query parameter for the request_source scope. Defaults to "api" if omitted.
curl -s "http://localhost:8080/v1/decisions/request/req-00512?source=lending-service" | jq .Returns the envelope object, or 404 if not found.
Error cases
| Status | Condition |
|---|---|
400 |
Missing or invalid request ID |
404 |
Decision not found |
Apply/plan endpoints accept YAML (application/yaml, application/x-yaml, or text/yaml). Maximum body: 10 MiB. The lifecycle action endpoints accept JSON.
Parse, validate, and persist a YAML bundle.
curl -s -X POST http://localhost:8080/v1/controlplane/apply \
-H "Content-Type: application/yaml" \
--data-binary @bundle.yaml | jq .Response:
{
"results": [
{"kind": "Surface", "id": "surf-payment-release", "status": "created"},
{"kind": "Agent", "id": "agent-payments-prod", "status": "created"}
],
"validation_errors": []
}Resource statuses: created, conflict, error, unchanged.
Error cases
| Status | Condition |
|---|---|
400 |
Body too large; malformed YAML |
415 |
Wrong Content-Type |
501 |
Control plane not configured |
Dry-run: validate and preview what a bundle would do. No writes occur.
curl -s -X POST http://localhost:8080/v1/controlplane/plan \
-H "Content-Type: application/yaml" \
--data-binary @bundle.yaml | jq .Response:
{
"entries": [
{
"kind": "Surface",
"id": "surf-payment-release",
"action": "create",
"document_index": 1,
"decision_source": "persisted_state",
"create_kind": "new_version",
"diff": {
"fields": [
{"field": "spec.name", "before": "Old Name", "after": "Payment Release"},
{"field": "spec.minimum_confidence", "before": 0.5, "after": 0.8}
]
},
"warnings": [
{
"code": "REF_PROCESS_TERMINAL",
"severity": "warning",
"message": "referenced process \"payments.v1\" is deprecated; referrer will be linked to a terminal-state process",
"field": "spec.process_id",
"related_kind": "Process",
"related_id": "payments.v1"
}
]
}
],
"would_apply": true,
"invalid_count": 0,
"conflict_count": 0,
"create_count": 1
}Entry actions: create, conflict, invalid, unchanged.
Additional per-entry fields (all optional, all advisory):
create_kind— onaction: "create"only. Values:new(no prior lineage found) ornew_version(appends a new version to an existing Surface or Profile lineage).diff— populated only forcreate_kind: "new_version"entries whosekindisSurfaceorProfile. Contains afieldsarray of changed scalar fields (field,before,after). Never emitted for plainnewcreates or for other resource kinds.warnings— advisory signals attached to the entry. Warnings never changeaction, never contribute toinvalid_countorconflict_count, and never affectwould_apply. Warning codes:REF_SURFACE_TERMINAL,REF_PROFILE_TERMINAL,REF_PROCESS_TERMINAL,REF_CAPABILITY_TERMINAL. Warnings fire when a reference resolves against persisted state whose status isdeprecatedorretired.
Read the platform administrative audit trail. This is a distinct trail from the runtime decision audit (audit_events, envelope-bound, hash-chained) and from the resource-centric control-plane audit (GET /v1/controlplane/audit). It captures first-pass coverage of the highest-value platform-administrative actions: apply invocations, successful password changes, and bootstrap admin creation. Requires platform.admin role.
curl -s "http://localhost:8080/v1/platform/admin-audit?action=apply.invoked&limit=10" | jq .{
"entries": [
{
"id": "d1d6…",
"occurred_at": "2026-04-18T09:12:04.113Z",
"action": "apply.invoked",
"outcome": "success",
"actor_type": "user",
"actor_id": "user-alice",
"target_type": "bundle",
"request_id": "req-abc-123",
"client_ip": "10.0.0.42",
"required_permission": "controlplane:apply",
"details": {
"bundle_bytes": 1247,
"created_count": 3
}
}
]
}Covered actions (first pass)
| Action | Emitted when |
|---|---|
apply.invoked |
One record per POST /v1/controlplane/apply HTTP request. Additive to the existing per-resource rows written into GET /v1/controlplane/audit. |
password.changed |
One record per successful POST /auth/change-password. No password material is recorded. |
bootstrap.admin_created |
One record when the default admin account is created on first-run bootstrap. No password material is recorded. actor_type=system, actor_id=system:bootstrap. |
Query parameters
| Field | Description |
|---|---|
action |
Filter by action enum (see above). |
outcome |
Filter by success or failure. |
actor_id |
Filter by actor. |
target_type |
Filter by target type (bundle, user, platform). |
target_id |
Filter by target ID. |
limit |
Positive integer up to 500. Default 50. |
Out of scope in the first pass
This release deliberately does not include hash chaining, cryptographic signatures, login/logout records, failed-authn or failed-authz records, demo-seed records, user CRUD or role-change records, or an external SIEM integration. The record is append-only in its repository surface; there is no update or delete API.
Error cases
| Status | Condition |
|---|---|
400 |
limit is not a positive integer, or exceeds the maximum |
401 |
Unauthenticated |
403 |
Insufficient role (requires platform.admin) |
501 |
Admin-audit not configured |
Promote a surface from review to active.
curl -s -X POST \
http://localhost:8080/v1/controlplane/surfaces/surf-payment-release/approve \
-H "Content-Type: application/json" \
-d '{
"submitted_by": "user-team-lead",
"approver_id": "user-governance-lead",
"approver_name": "Governance Lead"
}'{
"surface_id": "surf-payment-release",
"status": "active",
"approved_by": "user-governance-lead"
}Error cases
| Status | Condition |
|---|---|
400 |
Missing approver_id or submitted_by |
404 |
Surface not found |
409 |
Surface not in review status |
501 |
Approval service not configured |
Move a surface from active to deprecated.
curl -s -X POST \
http://localhost:8080/v1/controlplane/surfaces/surf-payment-release/deprecate \
-H "Content-Type: application/json" \
-d '{
"deprecated_by": "user-governance-lead",
"reason": "Superseded by surf-payment-release-v2",
"successor_id": "surf-payment-release-v2"
}'{
"surface_id": "surf-payment-release",
"status": "deprecated",
"deprecation_reason": "Superseded by surf-payment-release-v2",
"successor_surface_id": "surf-payment-release-v2"
}reason is required. successor_id is optional.
Error cases
| Status | Condition |
|---|---|
400 |
Missing deprecated_by or reason |
404 |
Surface not found |
409 |
Surface not in active status |
501 |
Approval service not configured |
Promote a profile version from review to active. Only review profiles can be approved.
curl -s -X POST \
http://localhost:8080/v1/controlplane/profiles/prof-payment-limits/approve \
-H "Content-Type: application/json" \
-d '{
"version": 1,
"approved_by": "user-governance-lead"
}'{
"profile_id": "prof-payment-limits",
"version": 1,
"status": "active",
"approved_by": "user-governance-lead"
}Error cases
| Status | Condition |
|---|---|
400 |
Missing approved_by, invalid identifier, or version < 1 |
404 |
Profile not found |
409 |
Profile not in review status |
501 |
Approval service not configured |
Move a profile version from active to deprecated. Only active profiles can be deprecated.
curl -s -X POST \
http://localhost:8080/v1/controlplane/profiles/prof-payment-limits/deprecate \
-H "Content-Type: application/json" \
-d '{
"version": 1,
"deprecated_by": "user-governance-lead"
}'{
"profile_id": "prof-payment-limits",
"version": 1,
"status": "deprecated"
}Error cases
| Status | Condition |
|---|---|
400 |
Missing deprecated_by, invalid identifier, or version < 1 |
404 |
Profile not found |
409 |
Profile not in active status |
501 |
Approval service not configured |
These endpoints provide read-only visibility into the authority model. They return 501 if the introspection service is not configured.
Retrieve the latest version of a surface.
curl -s http://localhost:8080/v1/surfaces/surf-payment-release | jq .Response fields: id, name, status, version, effective_from, approved_at, approved_by, successor_surface_id, deprecation_reason, domain, business_owner, technical_owner.
Error cases: 404 if not found; 501 if service not configured.
List all versions of a surface.
curl -s http://localhost:8080/v1/surfaces/surf-payment-release/versions | jq .Returns an array. Each item: version, status, effective_from, approved_at, approved_by, created_at, updated_at.
Error cases: 404 if surface not found; 501 if service not configured.
Read-only recovery analysis for a surface. Returns the current version history state, active/latest distinction, successor links, warnings, and deterministic recommended next actions. No state is mutated.
curl -s http://localhost:8080/v1/surfaces/surf-payment-release/recovery | jq .Response fields
| Field | Type | Description |
|---|---|---|
surface_id |
string | Logical surface ID. |
latest_version |
int | Highest-numbered version (any status). |
latest_status |
string | Status of the latest version. |
active_version |
int or null | Version currently active (effective window covers now); null if none. |
active_status |
string or null | Status of the active version; null if none. |
successor_surface_id |
string | Successor surface ID if deprecated; empty string if none. |
deprecation_reason |
string | Deprecation reason; empty string if not deprecated. |
version_count |
int | Total number of versions for this surface. |
warnings |
array | Deterministic operator warnings about current state. |
recommended_next_actions |
array | Deterministic recommended actions derived from current state. |
Recommended next action strings (deterministic)
| Condition | Action |
|---|---|
| Latest=review, no active | "approve latest review version to enable evaluation" |
| Latest=review, active exists | "approve latest review version to activate updated configuration" |
| Deprecated with successor | "inspect successor surface '{id}' and plan grant migration" |
| Deprecated, no successor, no review version | "apply replacement surface" |
| Deprecated, no successor, review version exists | "approve replacement surface" |
| Active, newer review version exists | "deprecate this surface with successor_id pointing to the replacement" |
Error cases: 404 if not found; 501 if service not configured.
Retrieve a profile by ID.
curl -s http://localhost:8080/v1/profiles/prof-payments-standard | jq .Response fields: id, version, surface_id, name, description, status, effective_date, confidence_threshold, escalation_mode, fail_mode, policy_reference, required_context_keys, created_at, updated_at, approved_by, approved_at.
List all profiles for a surface. surface_id query parameter is required.
curl -s "http://localhost:8080/v1/profiles?surface_id=surf-payment-release" | jq .Returns an array of profile objects.
Error cases: 400 if surface_id missing or invalid; 501 if service not configured.
List all versions of a profile ordered by version descending (latest first).
curl -s http://localhost:8080/v1/profiles/prof-payments-standard/versions | jq .Returns an array. Each item: id, version, surface_id, name, status, effective_date, confidence_threshold, escalation_mode, fail_mode, created_at, updated_at.
Error cases: 404 if profile not found; 501 if service not configured.
Read-only recovery analysis for a profile. Returns version history state, active/latest distinction, active grant count, an honest capability note about profile lifecycle behaviour, and deterministic recommended next actions. No state is mutated.
curl -s http://localhost:8080/v1/profiles/prof-payments-standard/recovery | jq .Response fields
| Field | Type | Description |
|---|---|---|
profile_id |
string | Logical profile ID. |
surface_id |
string | Surface this profile is attached to. |
latest_version |
int | Highest-numbered version (any status). |
latest_status |
string | Status of the latest version. |
active_version |
int or null | Version currently effective (effective_date <= now and not expired); null if none. |
active_status |
string or null | Status of the active version; null if none. |
version_count |
int | Total number of versions for this profile. |
versions |
array | All versions: version, status, effective_from. |
active_grant_count |
int | Active grants referencing this profile. -1 if grants reader not wired. |
capability_note |
string | Description of profile lifecycle: governed review→active→deprecated, explicit approval required. |
warnings |
array | Deterministic operator warnings about current state. |
recommended_next_actions |
array | Deterministic recommended actions derived from current state. |
Recommended next action strings (deterministic)
| Condition | Action |
|---|---|
| No active version due to future effective_date | "re-apply profile with an effective_date in the past to restore evaluation eligibility" |
| Active version with active grants | "inspect dependent grants before deprecating this profile version" |
Error cases: 404 if not found; 501 if service not configured.
Retrieve an agent by ID.
curl -s http://localhost:8080/v1/agents/agent-payments-prod | jq .Response fields: id, name, type, owner, model_version, endpoint, operational_state, created_at, updated_at.
Error cases: 404 if not found; 501 if service not configured.
Retrieve a single grant by its ID.
curl -s http://localhost:8080/v1/grants/grant-payments-agent-standard | jq .Response fields: id, agent_id, profile_id, status, granted_by, effective_from, effective_until, created_at, updated_at.
Error cases: 404 if not found; 501 if service not configured.
List all grants for an agent.
curl -s "http://localhost:8080/v1/grants?agent_id=agent-payments-prod" | jq .Returns an array of grant objects. Response fields: id, agent_id, profile_id, status, granted_by, effective_from, effective_until, created_at, updated_at.
List all grants referencing a profile.
curl -s "http://localhost:8080/v1/grants?profile_id=prof-payments-standard" | jq .Exactly one of agent_id or profile_id must be provided. Providing both returns 400.
Error cases: 400 if neither or both query params provided; 501 if service not configured.
These endpoints provide read-only visibility into the structural model (capabilities, processes, business services). They are registered when the structural service is configured via WithStructural(). Requires platform.viewer, platform.operator, or platform.admin role.
List all capabilities.
curl -s http://localhost:8080/v1/capabilities | jq .Returns a JSON array. Each item: id, name, description, status, owner, created_at, updated_at.
Retrieve a single capability by ID.
curl -s http://localhost:8080/v1/capabilities/cap-lending | jq .Response fields: id, name, description, status, owner, created_at, updated_at.
Error cases: 404 if not found.
Note. Under the v1 service-led structural model, Process belongs to a BusinessService (not directly to a Capability). The relationship between Capabilities and Processes is indirect, via
BusinessService → BusinessServiceCapability → Capability. The previousGET /v1/capabilities/{id}/processesendpoint has been removed.
List all processes.
curl -s http://localhost:8080/v1/processes | jq .Returns a JSON array. Each item: id, name, business_service_id, description, status, owner, created_at, updated_at.
Retrieve a single process by ID.
curl -s http://localhost:8080/v1/processes/proc-loan-origination | jq .Response fields: id, name, business_service_id, description, status, owner, created_at, updated_at.
Error cases: 404 if not found.
List all decision surfaces belonging to a process.
curl -s http://localhost:8080/v1/processes/proc-loan-origination/surfaces | jq .Returns a JSON array of surface objects (latest version of each surface linked to this process).
Error cases: 404 if process not found.
List all business services.
curl -s http://localhost:8080/v1/businessservices | jq .Returns a JSON array. Each item: id, name, description, service_type, regulatory_scope, status, created_at, updated_at.
Retrieve a single business service by ID.
curl -s http://localhost:8080/v1/businessservices/bs-consumer-lending | jq .Response fields: id, name, description, service_type, regulatory_scope, status, created_at, updated_at.
Error cases: 404 if not found.
All error responses use this shape:
{"error": "description of the problem"}All JSON endpoints accept and return application/json. The control plane apply and plan endpoints require application/yaml, application/x-yaml, or text/yaml.