Developers · Quickstart
Your first allow in 10 minutes
Your agent asks Mandact before every action and acts only on «allow». A test needs no payment method and no merchant: invent an action such as «hardware up to CHF 500 from ACME AG» and send different amounts and counterparties. You will see allow, deny with a reason, escalation and the receipt.
Path 1 · Mandact signs
The agent's key is stored encrypted at Mandact. Two calls before the action, one after — in any language.
scope agents:present
Path 2 · Agent signs itself
The agent holds its own Ed25519 key; Mandact only knows the public key. Cryptographically the stronger model.
own did:key in the wizard
Path 3 · Gateway
The agent no longer talks to the target system directly. Mandact checks every request and forwards only on allow — the check cannot be skipped.
token gwt_… per instance
1 · Create an account
Sign up and set up the second factor. Without it, API keys cannot be created.
2 · Create agent and mandate
New mandate in the wizard. In the «Agent» step, leave the identifier empty (path 1) or enter your agent's did:key (path 2). Set the limits: action type, amount per action and per month, counterparty, escalation threshold. Review the bound wording and activate the mandate.
3 · Create an API key
Settings → API keys. Scopes: agents:present, verify, consumptions:write, executions:write, mandates:read, evidence:read. The key is shown exactly once.
4 · Add the three calls
Before the action, have it signed and verified; afterwards, record the consumption and report the execution. The mandate ID is on the mandate's detail page.
The calls
Environment: MANDACT_API_KEY and MANDATE_ID. The action must be identical for present and verify — both sides bind the same context digest.
Have the intent signed (path 1)
curl -s -X POST https://mandact.com/api/v1/agents/present \
-H "authorization: Bearer $MANDACT_API_KEY" \
-H "content-type: application/json" \
-d '{"mandate_id":"'$MANDATE_ID'",
"action":{"type":"purchase.goods",
"amount":{"value":120,"currency":"CHF"},
"counterparty":"ACME AG"}}' > pres.json
# → { presentation, custody: "custodial", note }Verify — act only on «allow»
curl -s -X POST https://mandact.com/api/v1/verify \
-H "authorization: Bearer $MANDACT_API_KEY" \
-H "content-type: application/json" \
-H "idempotency-key: $(uuidgen)" \
-d '{"presentation":'"$(jq .presentation pres.json)"',
"action":{"type":"purchase.goods",
"amount":{"value":120,"currency":"CHF"},
"counterparty":"ACME AG"}}'
# → allow: { decision, consumption_token, receipt, mat_compact }
# → deny: { decision, reasons: ["MD-302"], reason_text, receipt }
# → escalate: { decision, escalation: { id, expires_at } }After the action: record consumption and report execution
curl -s -X POST https://mandact.com/api/v1/consumptions \
-H "authorization: Bearer $MANDACT_API_KEY" -H "content-type: application/json" \
-d '{"consumption_token":"<token>","final_amount":120,"currency":"CHF",
"action_type":"purchase.goods","counterparty":"ACME AG",
"context_digest":"'"$(jq -r .presentation.ctx pres.json)"'"}'
curl -s -X POST https://mandact.com/api/v1/executions \
-H "authorization: Bearer $MANDACT_API_KEY" -H "content-type: application/json" \
-d '{"reservation_token":"<token>","outcome":"erfolgreich",
"executed_amount":120,"executed_currency":"CHF","external_ref":"PO-4711"}'Fetch the receipt as a dispute dossier and check it without Mandact
curl -s "https://mandact.com/api/v1/verifications/<rcp_…>/dossier?mat=<mat_compact>" \ -H "authorization: Bearer $MANDACT_API_KEY" > dossier.json node verify-dossier.mjs dossier.json --mat <mat_compact> # → VALID … MAT: gueltig (ES256, kid …)
Path 2: the agent signs itself
The example agent creates an Ed25519 key and shows its did:key. Enter it in the wizard under «Identifier»; Mandact stores only the public key and never signs for this agent. The presentation must also sign the organisation that verifies it — the status endpoint returns that value as presentation_verifier.
node beispiel-agent.mjs schluessel
# → agent-key.pem + did:key:z6Mk…
curl -s https://mandact.com/api/v1/mandates/$MANDATE_ID/status -H "authorization: Bearer $MANDACT_API_KEY"
# → { limits, agent_did, presentation_verifier }
AGENT_KEY_FILE=agent-key.pem node beispiel-agent.mjs allowPath 3: through the gateway
To try it without a target system of your own, there is a neutral echo target that only reports what arrived — with no side effects.
- Connectors → new instance → set up gateway. Target: https://mandact.com/api/sandbox/echo, choose the mandate, optionally store a target credential. The token gwt_… is shown once.
- Send a first POST. Mandact creates the tool «http.post:/bestellung» and denies until it is mapped to an action of your mandate.
- In the instance, map the tool to the action, for example purchase.goods. From then on the mandate decides.
curl -s -X POST https://mandact.com/api/gateway/http/bestellung \
-H "authorization: Bearer $GATEWAY_TOKEN" -H "content-type: application/json" \
-d '{"amount":120,"currency":"CHF","counterparty":"ACME AG"}' -i
# allow → X-Mandact-Decision: allow, X-Mandact-Receipt: rcp_…
# deny → HTTP 403 { error: "mandact_deny", reasons, receipt_id }The example agent
One file, Node ≥ 20 only, no dependencies. It reads your mandate's limits and runs all three paths: pruefen, schluessel, allow, ablehnungen, gateway. Configure it with MANDACT_API_KEY, MANDATE_ID and optionally AGENT_KEY_FILE or GATEWAY_TOKEN. Put the dossier checker next to the agent and it checks every dossier offline.
Where things honestly stand
At level 1 the principal's identity is self-declared, and the credential says so. The timestamp comes from Mandact's own database; a qualified anchor is in preparation. Decisions are made only by POST /v1/verify — the example agent and the SDK decide nothing.