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. 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: become a vetted Client, 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. Sandbox keys are provisioned on request; production access is granted to vetted Clients (Book a call).

Get started

There is no self-serve sign-up. Clients are vetted through KYB onboarding before they are provisioned; sandbox keys come with provisioning. 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: