This guide takes a fresh Postgres-backed MIDAS install and walks it
through to a successful /v1/evaluate call. It is the canonical
end-to-end walkthrough for the structural quickstart bundle.
midas init quickstart only applies the structural skeleton:
BusinessServices, Capabilities, BusinessServiceCapability links,
Processes, and Surfaces. To evaluate a decision you also need an Agent,
a Profile, and a Grant — those you author yourself through the standard
control-plane apply path. This guide shows how, end to end.
- Applies the quickstart bundle.
- Approves one of the Surfaces it creates.
- Applies a small bundle that adds an Agent, a Profile, and a Grant.
- Approves the Profile.
- Calls
POST /v1/evaluateand gets a successful outcome.
The walkthrough uses surf-v2-credit-assess from
the bundle. The same shape works for any of the bundle's Surfaces if
you substitute the Surface ID, Process ID, Profile ID, Grant ID, and
owner strings appropriately.
Before starting, you need:
- Postgres backend.
midas init quickstartrejects the memory backend (memory state is per-process and would not survive the command's exit). SetMIDAS_STORE_BACKEND=postgresand provide a reachableDATABASE_URL. - MIDAS server running against that same Postgres. The schema is applied automatically the first time the store is opened.
midas init quickstartalready applied (covered in step 1 below). Re-running is refused via preflight, so subsequent runs are safe but no-op.- An authenticated principal able to call control-plane and
approval endpoints. The simplest path is the bootstrap admin user
(created on first run with credentials
admin / admin, password change forced on first login). The bootstrap admin holds theplatform.adminrole, which bypasses the Surface owner-match check during approval. If you are using a non-admin principal you must holdgovernance.approverand your principal ID must match the Surface'sbusiness_ownerortechnical_owner— see Approve a quickstart Surface. - A policy posture that permits the example. MIDAS uses the
NoOpPolicyEvaluatorby default, which permits the policy step. If you have configured a real policy evaluator, make sure the examplespec.policy.referenceresolves to a policy that permits the sample request.
The examples below use Authorization: Bearer $MIDAS_TOKEN style
headers consistent with the rest of MIDAS's API documentation. If you
are running with MIDAS_AUTH_MODE=open (development only) you can omit
the Authorization header on /v1/* calls.
go run ./cmd/midas init quickstartSuccessful output names every created Surface and points at the approval endpoint. The bundle creates:
- 2 BusinessServices
- 4 Capabilities
- 5 BusinessServiceCapability links
- 4 Processes
- 6 Surfaces (in
reviewstatus)
The Surfaces are in review because the apply path always persists
new Surfaces in review status. Approving a Surface is the next step.
This walkthrough uses surf-v2-credit-assess. Set it as a shell
variable so the rest of the commands stay readable:
export SURFACE_ID=surf-v2-credit-assessOf the six Surfaces in the bundle, surf-v2-credit-assess is a good
one to walk through first:
- It's
category: financial,risk_tier: high, which exercises the monetaryconsequence_thresholdshape on Profile. - Its owning Process is
proc-credit-assessment, which belongs tobs-consumer-lending. - It has no
required_contextkeys, so the evaluate request body stays minimal.
If you'd rather walk through a different Surface, substitute one of:
| Surface ID | Process | Domain |
|---|---|---|
surf-v2-id-verify |
proc-consumer-onboarding |
consumer-lending |
surf-v2-consumer-fraud |
proc-consumer-onboarding |
consumer-lending |
surf-v2-credit-assess |
proc-credit-assessment |
consumer-lending |
surf-v2-merchant-risk |
proc-merchant-risk-screen |
merchant-services |
surf-v2-merchant-payment |
proc-merchant-payment-auth |
merchant-services |
surf-v2-merchant-hv-pay |
proc-merchant-payment-auth |
merchant-services |
The walkthrough's Profile, Grant, and evaluate-call IDs reference
surf-v2-credit-assess directly. If you choose a different Surface,
change the Profile ID, Grant ID, Profile surface_id, evaluate
surface_id, and evaluate process_id to match your chosen row.
curl -s -X POST \
-H "Authorization: Bearer $MIDAS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"submitted_by": "cli:init-quickstart",
"approver_id": "user:admin",
"approver_name": "Admin"
}' \
"http://localhost:8080/v1/controlplane/surfaces/$SURFACE_ID/approve" | jq .Expected response:
{
"surface_id": "surf-v2-credit-assess",
"status": "active",
"approved_by": "user:admin"
}If you are not authenticated as platform.admin, the approver
principal must hold governance.approver and approver_id must equal
the Surface's business_owner (consumer-lending-team) or
technical_owner (midas). See docs/control-plane.md for the full
approval semantics.
You only need to approve the Surface you intend to evaluate against
for this walkthrough. The other quickstart Surfaces stay in review
until you approve them too.
The quickstart bundle does not create authority artefacts.
Author your own Agent, Profile, and Grant through the standard apply
path. Save the following as authority.yaml:
apiVersion: midas.accept.io/v1
kind: Agent
metadata:
id: agent-quickstart-demo
name: Quickstart Demo Agent
spec:
type: automation
status: active
---
apiVersion: midas.accept.io/v1
kind: Profile
metadata:
id: profile-quickstart-credit-assess
name: Quickstart Credit-Assess Profile
spec:
surface_id: surf-v2-credit-assess
authority:
decision_confidence_threshold: 0.85
consequence_threshold:
type: monetary
amount: 5000
currency: GBP
policy:
reference: noop://quickstart-credit-assess
fail_mode: closed
lifecycle:
status: active
effective_from: "2026-01-01T00:00:00Z"
version: 1
---
apiVersion: midas.accept.io/v1
kind: Grant
metadata:
id: grant-quickstart-credit-assess
spec:
agent_id: agent-quickstart-demo
profile_id: profile-quickstart-credit-assess
granted_by: user:admin
granted_at: "2026-01-01T00:00:00Z"
effective_from: "2026-01-01T00:00:00Z"
status: activeNotes:
spec.type: automationavoids thellm_agent-only requirement to setspec.runtime.modelandspec.runtime.provider.spec.surface_idon the Profile must match the Surface ID you approved in step 3.spec.policy.referenceis required by the control-plane Profile validator.noop://quickstart-credit-assessis an example reference string; the defaultNoOpPolicyEvaluatorpermits it, while a real policy evaluator may interpret references differently.spec.lifecycle.status: activeis validated, but the apply path still persists the Profile inreviewregardless of this value. You will approve the Profile in step 6.spec.granted_atandspec.effective_fromon Grant are required RFC3339 timestamps.granted_atmust be before or equal toeffective_from.
If you picked a different Surface in step 2, change the Profile ID,
the Grant ID, the surface_id reference, the policy reference, and
the evaluate request's surface_id / process_id accordingly.
curl -s -X POST \
-H "Authorization: Bearer $MIDAS_TOKEN" \
-H "Content-Type: application/yaml" \
--data-binary @authority.yaml \
http://localhost:8080/v1/controlplane/apply | jq .Expected response (abbreviated):
{
"results": [
{"kind": "Agent", "id": "agent-quickstart-demo", "status": "created"},
{"kind": "Profile", "id": "profile-quickstart-credit-assess", "status": "created"},
{"kind": "Grant", "id": "grant-quickstart-credit-assess", "status": "created"}
]
}The Agent and Grant are created with the status from their YAML
(active). The Profile is persisted as review regardless of the
lifecycle.status value in the document — this is the apply path's
normal behaviour for Profiles, not a quickstart-specific carve-out.
curl -s -X POST \
-H "Authorization: Bearer $MIDAS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"version": 1,
"approved_by": "user:admin"
}' \
http://localhost:8080/v1/controlplane/profiles/profile-quickstart-credit-assess/approve | jq .Expected response:
{
"profile_id": "profile-quickstart-credit-assess",
"version": 1,
"status": "active",
"approved_by": "user:admin"
}version: 1 is the first persisted version assigned by the apply
planner; it also matches the informational lifecycle.version
declared in the YAML. Profile approval has a simpler policy than
Surface approval — it checks only the lifecycle transition (review
→ active), so no owner-match is required.
curl -s -X POST \
-H "Authorization: Bearer $MIDAS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"surface_id": "surf-v2-credit-assess",
"process_id": "proc-credit-assessment",
"agent_id": "agent-quickstart-demo",
"confidence": 0.91,
"consequence": {
"type": "monetary",
"amount": 2500,
"currency": "GBP"
},
"request_id": "req-quickstart-001",
"request_source": "quickstart-walkthrough"
}' \
http://localhost:8080/v1/evaluate | jq .The confidence (0.91) is above the Profile's
decision_confidence_threshold (0.85). The consequence.amount
(2500 GBP) is below the Profile's consequence_threshold.amount
(5000 GBP). The process_id matches the approved Surface's owning
Process, which keeps the request valid when structural enforcement is
enabled. With a clean Surface/Profile/Grant chain, the evaluation
should produce a non-reject outcome and persist a governance envelope.
The exact response fields depend on the policy evaluator wired in your
deployment. The minimum guarantee is that the request reaches the
authority chain (no SURFACE_INACTIVE, no AGENT_NOT_FOUND, no
NO_ACTIVE_GRANT) and an envelope is recorded. To retrieve the
envelope:
curl -s \
-H "Authorization: Bearer $MIDAS_TOKEN" \
"http://localhost:8080/v1/decisions/request/req-quickstart-001?source=quickstart-walkthrough" \
| jq .| Symptom | Cause | Fix |
|---|---|---|
Evaluate returns reject with SURFACE_INACTIVE |
The Surface is still in review status |
Run step 3: approve the Surface |
Evaluate returns reject with PROFILE_NOT_FOUND |
The Profile is in review status, or no Profile is bound to that Surface |
Run step 6: approve the Profile (or check spec.surface_id matches the Surface ID) |
Evaluate returns reject with NO_ACTIVE_GRANT |
No Grant exists for this Agent, or the Grant is not yet effective | Check the Grant was applied (step 5) and that effective_from is in the past |
Evaluate returns reject with GRANT_PROFILE_SURFACE_MISMATCH |
The Agent's Grant points at a Profile bound to a different Surface | Verify Profile's spec.surface_id matches the request's surface_id and that the Grant references that Profile |
Evaluate returns reject with AGENT_NOT_FOUND |
The Agent ID in the request doesn't match a persisted Agent | Check agent_id in the request matches the Agent applied in step 5 |
Evaluate returns 400 with process_id is required |
Structural enforcement is enabled and the request omitted process_id |
Include the Process ID for the Surface; for this walkthrough use proc-credit-assessment |
Evaluate returns 400 with an explicit structure validation error |
The request's process_id does not match the Surface's configured Process |
Check the Surface/Process table in step 2 and update the request |
Surface approval returns 403 forbidden / approval forbidden |
Approver lacks platform.admin, or lacks governance.approver, or approver_id doesn't match the Surface's owners |
Authenticate as platform.admin, or supply an approver_id equal to the Surface's business_owner (consumer-lending-team) or technical_owner (midas) |
| Apply returns validation errors on the authority bundle | A field is missing or has an invalid enum value | Re-check the YAML against the shapes in step 4; common mistakes: missing spec.type on Agent, missing spec.policy.reference on Profile, missing spec.granted_at or spec.effective_from on Grant |
midas init quickstart rejects with store.backend=memory is not supported |
The CLI is configured against the memory backend | Set MIDAS_STORE_BACKEND=postgres and a valid DATABASE_URL, then re-run |
midas init quickstart reports "bundle already applied" |
A previous run already applied the bundle | This is expected; the command refuses re-runs to avoid creating duplicate pending-review Surface and Profile versions. Use the apply path to evolve the platform from here |
- docs/control-plane.md — full control-plane bundle format and apply semantics.
- docs/examples/control-plane.md — self-contained example bundles for accepted, escalated, and request-clarification outcomes.
- docs/api/http-api.md — full HTTP API reference, including evaluate response shape and approval endpoints.
- docs/architecture/architecture.md —
the structural metamodel
Capability ↔ BusinessService → Process → Surfaceand how it maps to evaluation. - docs/adr/0002-service-led-structural-metamodel.md — the structural metamodel decision.
- docs/adr/0001-envelope-structural-denormalisation.md — how the envelope captures the structural chain at evaluation time.