Set up your development environment
Get familiar with the Mandate Labs SDKs and start authorizing agent transactions in minutes.
Who this guide is for: agent platforms integrating pre-presentment. Sandbox is fully self-serve. If you are an issuer or processor integrating inside the authorization window, start with the issuer integration guide — production issuer deployments are sales-led and certified via MCI.
Overview
Event naming: kya.zone.* are the current event names (API v0.7). The legacy aliases cts.red / cts.critical remain accepted for 12 months under our deprecation policy.
This guide walks you through everything you need to go from zero to authorizing your first AI agent transaction. Try it instantly in the sandbox playground with no sign-up, or become a vetted Client and use the onboarding API to create a Principal and Agent, set spending limits via a mandate, and submit a transaction for real-time authorization.
What you'll learn
- Try the protocol in seconds with the MCP sandbox playground (no sign-up)
- Onboard a Principal and Agent with the Client onboarding API
- Create a spending mandate to set an agent's limits
- Authorize a transaction and interpret the response
- Set up webhooks for real-time event notifications
Sandbox keys (mdt_test_*) work identically to production keys (mdt_live_*) but never touch real payment rails. The sandbox playground is self-serve; production access is granted to vetted Clients (Book a call).
Get started
There are two ways to start. Try the protocol instantly in the sandbox playground with no sign-up, or become a vetted Client to run in production.
Path A — Try it now: sandbox playground (no sign-up)
The fastest way to feel the Decision Trust Protocol. This path is sandbox only — no real money moves and no funds are at risk.
1. Install the MCP server:
pip install mandate-labs-mcp
2. Add it to your Claude Desktop / Claude Code MCP config:
{
"mcpServers": {
"mandate-labs": {
"command": "mandate-labs-mcp",
"env": { "MANDATE_LABS_API_KEY": "mdt_test_pc1Ye9vIzy2XE2rXd7gCamFSa0oMRiRviiCXAwczxoi6AfQ37zBqfHKoXmGMFkP2" }
}
}
}
mdt_test_pc1Ye9vIzy2XE2rXd7gCamFSa0oMRiRviiCXAwczxoi6AfQ37zBqfHKoXmGMFkP2 is the public sandbox key — sandbox only, no real funds.
3. Tell your agent to call the register_agent tool. Mandate Labs onboards a Principal + Agent for you under the shared "Mandate / Mandate MCP" sandbox and returns a sandbox key the session then uses. From there create_mandate and authorize_transaction immediately work for that agent.
Path B — Go to production: become a Client
There is no self-serve sign-up for production. Clients are vetted through KYB onboarding before they are provisioned. To get started, Book a call.
Once provisioned you receive a Client API key (mdt_live_…) and use the onboarding API shown below. All Client-authed requests pass the key in the X-API-Key header against the base URL https://api.mandatelabs.ai.
Onboard a Principal and Agent
This step uses your Client API key (mdt_live_…). The POST /api/v1/onboard bundle creates a Principal, an Agent, and a Mandate in a single call.
Official SDKs are in beta; the canonical interface is the HTTP API shown here.
curl -X POST https://api.mandatelabs.ai/api/v1/onboard \ -H "X-API-Key: mdt_live_…" -H "Content-Type: application/json" \ -d '{ "principal": { "principal_kind": "merchant", "program_ids": ["prg_…"], "client_customer_ref": "acme-001", "profile": { "display_name": "Acme Corp", "email": "[email protected]", "country": "US" }, "verification": { "type": "KYB", "mode": "client_attested", "attestation": { "attested_by": "[email protected]", "scope": "Tier-2 CDD", "statement": "Acme performed CDD on this merchant", "evidence_ref": "case-123" } } }, "agent": { "name": "Shopping Assistant v1", "capabilities": ["payments.authorize"] }, "mandate": { "rails": ["card"], "limits": { "max_amount_per_transaction": 500, "max_daily_amount": 2000, "currency": "USD" } } }'
import httpx resp = httpx.post( "https://api.mandatelabs.ai/api/v1/onboard", headers={"X-API-Key": "mdt_live_…"}, json={ "principal": { "principal_kind": "merchant", "program_ids": ["prg_…"], "client_customer_ref": "acme-001", "profile": {"display_name": "Acme Corp", "email": "[email protected]", "country": "US"}, "verification": { "type": "KYB", "mode": "client_attested", "attestation": { "attested_by": "[email protected]", "scope": "Tier-2 CDD", "statement": "Acme performed CDD on this merchant", "evidence_ref": "case-123", }, }, }, "agent": {"name": "Shopping Assistant v1", "capabilities": ["payments.authorize"]}, "mandate": { "rails": ["card"], "limits": {"max_amount_per_transaction": 500, "max_daily_amount": 2000, "currency": "USD"}, }, }, ) data = resp.json() agent_id = data["agent"]["id"] # agt_…
const resp = await fetch("https://api.mandatelabs.ai/api/v1/onboard", { method: "POST", headers: { "X-API-Key": "mdt_live_…", "Content-Type": "application/json" }, body: JSON.stringify({ principal: { principal_kind: "merchant", program_ids: ["prg_…"], client_customer_ref: "acme-001", profile: { display_name: "Acme Corp", email: "[email protected]", country: "US" }, verification: { type: "KYB", mode: "client_attested", attestation: { attested_by: "[email protected]", scope: "Tier-2 CDD", statement: "Acme performed CDD on this merchant", evidence_ref: "case-123", }, }, }, agent: { name: "Shopping Assistant v1", capabilities: ["payments.authorize"] }, mandate: { rails: ["card"], limits: { max_amount_per_transaction: 500, max_daily_amount: 2000, currency: "USD" }, }, }), }); const data = await resp.json(); const agentId = data.agent.id; // agt_…
One Principal can own many Agents. To add more agents to the same principal, create the principal once with POST /api/v1/principals, then call POST /api/v1/principals/{principal_id}/agents for each agent:
# Create the principal once curl -X POST https://api.mandatelabs.ai/api/v1/principals \ -H "X-API-Key: mdt_live_…" -H "Content-Type: application/json" \ -d '{ "principal_kind": "merchant", "program_ids": ["prg_…"], "client_customer_ref": "acme-001", "profile": { "display_name": "Acme Corp", "email": "[email protected]", "country": "US" } }' # → returns "id": "prn_…" # Add an agent to that principal (repeat per agent) curl -X POST https://api.mandatelabs.ai/api/v1/principals/prn_…/agents \ -H "X-API-Key: mdt_live_…" -H "Content-Type: application/json" \ -d '{ "name": "Shopping Assistant v2", "capabilities": ["payments.authorize"] }'
Create a spending mandate
Mandates define what an agent is allowed to spend. Add or update a mandate for an existing agent with POST /api/v1/agents/{agent_id}/mandates, setting per-transaction and daily limits, allowed merchant categories, and allowed countries.
curl -X POST https://api.mandatelabs.ai/api/v1/agents/agt_…/mandates \ -H "X-API-Key: mdt_live_…" -H "Content-Type: application/json" \ -d '{ "rails": ["card"], "limits": { "max_amount_per_transaction": 500, "max_daily_amount": 2000, "currency": "USD" }, "allowed_mccs": ["5411", "5812"], "allowed_countries": ["US"] }'
import httpx resp = httpx.post( "https://api.mandatelabs.ai/api/v1/agents/agt_…/mandates", headers={"X-API-Key": "mdt_live_…"}, json={ "rails": ["card"], "limits": {"max_amount_per_transaction": 500, "max_daily_amount": 2000, "currency": "USD"}, "allowed_mccs": ["5411", "5812"], "allowed_countries": ["US"], }, ) mandate = resp.json() print(mandate["id"]) # mnd_…
const resp = await fetch("https://api.mandatelabs.ai/api/v1/agents/agt_…/mandates", { method: "POST", headers: { "X-API-Key": "mdt_live_…", "Content-Type": "application/json" }, body: JSON.stringify({ rails: ["card"], limits: { max_amount_per_transaction: 500, max_daily_amount: 2000, currency: "USD" }, allowed_mccs: ["5411", "5812"], allowed_countries: ["US"], }), }); const mandate = await resp.json(); console.log(mandate.id); // mnd_…
Authorize a transaction
This is the core operation. Submit a transaction request to POST /api/v1/authorize and receive a real-time decision with trust scoring, risk assessment, and any gates that fired. Use the agent_id returned when you onboarded the agent above.
curl -X POST https://api.mandatelabs.ai/api/v1/authorize \ -H "X-API-Key: mdt_live_…" -H "Content-Type: application/json" \ -d '{ "agent_id": "agt_…", "amount": "42.99", "currency": "USD", "merchant": "Whole Foods Market #1042", "mcc": "5411", "intent_context": { "intent_type": "PURCHASE", "task_reference": "weekly_grocery_purchase", "reasoning_summary": "Weekly organic produce restock; compared 3 nearby grocers on price and delivery window", "confidence": 0.91, "alternatives_considered": 3 } }'
import httpx resp = httpx.post( "https://api.mandatelabs.ai/api/v1/authorize", headers={"X-API-Key": "mdt_live_…"}, json={ "agent_id": "agt_…", "amount": "42.99", "currency": "USD", "merchant": "Whole Foods Market #1042", "mcc": "5411", "intent_context": { "intent_type": "PURCHASE", "task_reference": "weekly_grocery_purchase", "reasoning_summary": "Weekly organic produce restock; compared 3 nearby grocers on price and delivery window", "confidence": 0.91, "alternatives_considered": 3, }, }, ) result = resp.json() print(f"Decision: {result['decision']}") # "APPROVE" print(f"Trust Score: {result['trust_score']}") # 0.87 print(f"Risk Score: {result['risk_score']}") # 0.12 print(f"Gates Fired: {result['gates_fired']}") # []
const resp = await fetch("https://api.mandatelabs.ai/api/v1/authorize", { method: "POST", headers: { "X-API-Key": "mdt_live_…", "Content-Type": "application/json" }, body: JSON.stringify({ agent_id: "agt_…", amount: "42.99", currency: "USD", merchant: "Whole Foods Market #1042", mcc: "5411", intent_context: { intent_type: "PURCHASE", task_reference: "weekly_grocery_purchase", reasoning_summary: "Weekly organic produce restock; compared 3 nearby grocers on price and delivery window", confidence: 0.91, alternatives_considered: 3, }, }), }); const result = await resp.json(); console.log(`Decision: ${result.decision}`); // "APPROVE" console.log(`Trust Score: ${result.trust_score}`); // 0.87 console.log(`Risk Score: ${result.risk_score}`); // 0.12 console.log(`Gates Fired: ${result.gates_fired}`); // []
decision: APPROVE, DECLINE, or STEP_UP. trust_score: Current KYA Score (0-1). risk_score: Transaction risk (0-1). gates_fired: Array of triggered safety gates. recommendations: Suggested actions if declined.
Set up webhooks
Register a webhook endpoint to receive real-time notifications about important events like gate firings, trust zone changes, and declined authorizations.
curl -X POST https://api.mandatelabs.ai/api/v1/webhooks \ -H "X-API-Key: mdt_live_…" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.com/webhooks/mandate-ai", "event_types": ["gate.fired", "kya.zone.red", "kya.zone.critical", "authorization.decline", "trust.promotion"], "description": "Production alerting webhook" }' # Response: { "id": "...", "signing_secret": "whsec_..." } — the secret is shown only once.
import httpx resp = httpx.post( "https://api.mandatelabs.ai/api/v1/webhooks", headers={"X-API-Key": "mdt_live_…"}, json={ "url": "https://your-app.com/webhooks/mandate-ai", "event_types": ["gate.fired", "kya.zone.red", "kya.zone.critical", "authorization.decline", "trust.promotion"], "description": "Production alerting webhook", }, ) data = resp.json() # Save the signing secret — shown only once! print(data["id"], data["signing_secret"]) # whsec_...
const resp = await fetch("https://api.mandatelabs.ai/api/v1/webhooks", { method: "POST", headers: { "X-API-Key": "mdt_live_…", "Content-Type": "application/json" }, body: JSON.stringify({ url: "https://your-app.com/webhooks/mandate-ai", event_types: ["gate.fired", "kya.zone.red", "kya.zone.critical", "authorization.decline", "trust.promotion"], description: "Production alerting webhook", }), }); const data = await resp.json(); // Save the signing secret — shown only once! console.log(data.id, data.signing_secret); // whsec_...
Available event types:
gate.fired— A safety gate triggered during authorizationkya.zone.red— Agent's trust score dropped to RED zonekya.zone.critical— Agent's trust score dropped to CRITICAL zonesession.terminate— Agent session was terminatedauthorization.decline— A transaction was declinedtrust.promotion— Agent was promoted to a higher trust tier*— Subscribe to all event types
Next steps
Now that you can authorize transactions, explore these guides to deepen your integration: