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

Sandbox vs. Production

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.

SDKs

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}`);  // []
Response fields

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 authorization
  • kya.zone.red — Agent's trust score dropped to RED zone
  • kya.zone.critical — Agent's trust score dropped to CRITICAL zone
  • session.terminate — Agent session was terminated
  • authorization.decline — A transaction was declined
  • trust.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: