This page is optimized for AI agent consumption. For human-friendly guides, see the Documentation.
Quick Reference
| Key | Value |
|---|---|
| Base URL | https://app.mandate.md/api |
| Auth header | Authorization: Bearer <MANDATE_RUNTIME_KEY> |
| Content-Type | application/json |
| Live key prefix | mndt_live_... (mainnet) |
| Test key prefix | mndt_test_... (testnet) |
| Credential file | ~/.mandate/credentials.json |
| Non-custodial | Private keys never leave your machine |
Mandatory Security Rules
These rules are non-negotiable. Violating any of them is a security breach, not a simplification.- Call
/validatebefore EVERY transaction. No exceptions. - If
allowed: false, do NOT execute. Display theblockReasonto the user. - If the API is unreachable, do NOT execute. Do NOT fall back to the wallet directly.
- Display validation results to the user BEFORE any wallet call.
- Even with custodial wallets, validate with Mandate BEFORE calling the wallet API.
- Skipping validation bypasses spend limits, allowlists, and approval workflows the owner configured.
Fail-Safe Behavior
When the Mandate API is unreachable:- Do NOT execute the transaction.
- Do NOT fall back to calling the wallet API directly.
- Display:
Mandate: policy server unreachable, transaction halted for safety - Retry up to 3 times at 30-second intervals.
- If all retries fail:
Cannot execute until policy server recovers. No funds were spent.
Primary Endpoint: POST /validate
Policy check before every transaction. Works with any wallet type (custodial or self-custodial).Parameters
| Field | Required | Description |
|---|---|---|
action | Yes | What you are doing: transfer, swap, buy, bridge, stake, bet (free text) |
reason | Yes | Why you are doing it (max 1000 chars). Scanned for prompt injection. |
amount | No | USD value (assumes stablecoins) |
to | No | Recipient address (checked against allowlist) |
token | No | Token address |
Response
Registration: POST /agents/register
No auth required. Creates an agent identity and returns credentials.Response
claimUrl to the human owner so they can link the agent to their dashboard. Store runtimeKey in ~/.mandate/credentials.json with chmod 600.
Agents use POST /agents/register, not dashboard login. Dashboard login is for humans only.
For details, see Register an Agent.
Activation: POST /activate
Set the EVM address for a registered agent. Call once after registration. Requires auth.Status Polling: GET /intents/{id}/status
Poll the state of a validated intent.approved, then proceed. See Handle Approvals.
Validation Flow
Error Handling
HTTP Status Codes
| Status | Meaning | Common Cause |
|---|---|---|
| 400 | Bad Request | Missing or invalid fields |
| 401 | Unauthorized | Missing or invalid runtime key |
| 403 | Forbidden | Circuit breaker active |
| 404 | Not Found | Intent not found |
| 409 | Conflict | Duplicate intentHash or wrong status |
| 410 | Gone | Approval expired |
| 422 | Policy Blocked | Validation failed (see blockReason) |
| 429 | Rate Limited | Too many requests, back off and retry |
| 500 | Server Error | Transient, retry later |
{ "error": "message" } or { "allowed": false, "blockReason": "..." }
For error handling guidance, see Handle Errors and Common Errors.
SDK Error Types
Block Reason Values
| Value | Meaning |
|---|---|
circuit_breaker_active | Agent is circuit-broken (dashboard to reset) |
no_active_policy | No policy set (visit dashboard) |
intent_hash_mismatch | Client hash does not match server recompute (raw validate only) |
gas_limit_exceeded | Gas too high per policy |
value_wei_exceeded | Native ETH value too high |
outside_schedule | Outside allowed hours/days |
address_not_allowed | Recipient not in allowlist |
selector_blocked | Function selector is blocked |
per_tx_limit_exceeded | Amount exceeds per-tx USD limit |
daily_quota_exceeded | Daily USD limit reached |
monthly_quota_exceeded | Monthly USD limit reached |
reason_blocked | Prompt injection detected in reason field |
aegis_critical_risk | Transaction flagged as CRITICAL risk by security scanner |
Intent States
| State | Description | Expiry |
|---|---|---|
allowed | Validated via /validate | 24 hours |
reserved | Raw validated, waiting for broadcast | 15 min |
approval_pending | Requires owner approval via dashboard | 1 hour |
approved | Owner approved, broadcast window open | 10 min |
broadcasted | Tx sent, waiting for on-chain receipt | none |
confirmed | On-chain confirmed, quota committed | none |
failed | Reverted, dropped, policy violation, or envelope mismatch | none |
expired | Not broadcast in time, quota released | none |
Chain Reference
Test keys (mndt_test_*): Sepolia (11155111), Base Sepolia (84532).
Live keys (mndt_live_*): Ethereum (1), Base (8453).
| Chain | Chain ID | USDC Address | Decimals |
|---|---|---|---|
| Ethereum | 1 | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 | 6 |
| Sepolia | 11155111 | 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 | 6 |
| Base | 8453 | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 | 6 |
| Base Sepolia | 84532 | 0x036CbD53842c5426634e7929541eC2318f3dCF7e | 6 |
Default Policy
After registration, every agent gets:| Setting | Default |
|---|---|
| Per-transaction limit | $100 |
| Daily limit | $1,000 |
| Address restrictions | None (all allowed) |
| Approval required | No |
Tool-to-Endpoint Map
| CLI Command | Method | Path |
|---|---|---|
mandate login | POST | /api/agents/register |
mandate activate <address> | POST | /api/activate |
mandate validate | POST | /api/validate |
mandate validate-raw | POST | /api/validate/raw (deprecated) |
mandate event <id> --tx-hash 0x... | POST | /api/intents/{id}/events |
mandate status <id> | GET | /api/intents/{id}/status |
mandate approve <id> | GET | /api/intents/{id}/status (poll) |
mandate scan [dir] | local | Scan codebase for unprotected wallet calls |
mandate --llms | local | Machine-readable command manifest |
mandate --mcp | local | Start as MCP stdio server |
The reason Field
Every validation call requires areason string (max 1000 chars). This is what session keys cannot capture: the intent behind a transaction.
Mandate uses the reason to:
- Scan for prompt injection (18 hardcoded patterns + optional LLM judge)
- Return a
declineMessageon block to counter manipulation - Show it to the owner on approval requests (Slack, Telegram, dashboard)
- Log it in the audit trail permanently
Integration Plugins
For platforms with hook support, use the plugin instead of raw API calls. Plugins enforce validation automatically.| Platform | Install | Docs |
|---|---|---|
| OpenClaw | openclaw plugins install @mandate.md/mandate-openclaw-plugin | OpenClaw |
| Claude Code | claude plugin:install claude-mandate-plugin | Claude Code |
| GOAT SDK | @mandate.md/goat-plugin | GOAT SDK |
| AgentKit | @mandate.md/agentkit-provider | AgentKit |
| ElizaOS | @mandate.md/eliza-plugin | ElizaOS |