Intent Mandates

An Intent Mandate is a permission request from an agent to make purchases within specified constraints. It's the first step in the payment flow and determines whether the agent can proceed.

Overview

When an agent wants to shop, it first requests permission by creating an Intent Mandate:

  1. Agent submits request — "I want to spend up to $50 at amazon.com"
  2. Policy check — AutopayOS evaluates against spending rules
  3. Decision — Either approved (returns Intent VC) or denied (returns reason codes)

Create an Intent

<tabs> <tab title="TypeScript">
TypeScript
const permission = await client.requestPermission({
  agentDid: 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK',
  principalDid: 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH',
  amount: 100.00,
  currency: 'USD',
  merchantDomain: 'amazon.com',
  merchantMcc: '5411',  // Optional: Grocery stores
});

if (permission.allowed) {
  console.log('Approved! Intent VC:', permission.intentVc);
} else {
  console.log('Denied:', permission.reasonCodes);
}
</tab> <tab title="cURL">
Bash
curl https://api.autopayos.com/ap2/intents \
  -H "Authorization: Bearer $AUTOPAYOS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentDid": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
    "principalDid": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
    "request": {
      "maxAmount": 100.00,
      "currency": "USD",
      "vendorHints": {
        "domain": "amazon.com",
        "mcc": "5411"
      }
    }
  }'
</tab> </tabs>

Request parameters

ParameterTypeRequiredDescription
agentDidstringYesDID of the agent making the request
principalDidstringYesDID of the human who authorized the agent
amountnumberYesMaximum amount for the purchase
currencystringYesISO 4217 currency code (USD, EUR, GBP)
merchantDomainstringNoSpecific merchant domain
merchantMccstringNoMerchant Category Code
idempotencyKeystringNoPrevent duplicate requests

Response

Approved

JSON
{
  "allowed": true,
  "intentVc": {
    "@context": ["https://www.w3.org/2018/credentials/v1"],
    "type": ["VerifiableCredential", "IntentMandate"],
    "issuer": "did:key:z6MkAutoPayOSIssuer...",
    "issuanceDate": "2025-12-17T10:00:00Z",
    "expirationDate": "2025-12-17T11:00:00Z",
    "credentialSubject": {
      "mandateId": "intent_abc123xyz",
      "agentDid": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
      "principalDid": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
      "maxAmount": 100.00,
      "currency": "USD",
      "merchantDomain": "amazon.com"
    },
    "proof": {
      "type": "Ed25519Signature2020",
      "created": "2025-12-17T10:00:00Z",
      "verificationMethod": "did:key:z6MkAutoPayOSIssuer...#key-1",
      "jws": "eyJhbGciOiJFZERTQSJ9..."
    }
  },
  "attestation": {
    "policyId": "pol_abc123",
    "policyVersion": 3,
    "policyHash": "sha256:def456789...",
    "evaluatedAt": "2025-12-17T10:00:00Z"
  },
  "hpRequired": false
}

Denied

JSON
{
  "allowed": false,
  "reasonCodes": [
    "AMOUNT_OVER_CAP",
    "MERCHANT_NOT_ALLOWED"
  ],
  "details": {
    "AMOUNT_OVER_CAP": {
      "requested": 100.00,
      "cap": 50.00
    },
    "MERCHANT_NOT_ALLOWED": {
      "domain": "casino.com",
      "allowed": ["amazon.com", "walmart.com"]
    }
  }
}

Policy checks

When you create an intent, AutopayOS evaluates it against your policy:

CheckPolicy FieldReason Code
Amount within capspend.amount_capAMOUNT_OVER_CAP
Currency allowedspend.currencyCURRENCY_NOT_ALLOWED
Merchant on allowlistmerchant.allow_listMERCHANT_NOT_ALLOWED
Merchant not on denylistmerchant.deny_listMERCHANT_DENIED
MCC category allowedspend.categoriesMCC_DENIED
Within velocity limitsrisk.velocity_limitVELOCITY_EXCEEDED
Time window validcontext.time_windowTIME_WINDOW_VIOLATED
Geo restrictioncontext.geoGEO_RESTRICTED
Agent not revokedAgent statusAGENT_REVOKED

Human Presence

If the policy requires Human Presence for this request, the response will indicate:

JSON
{
  "allowed": true,
  "hpRequired": true,
  "hpChallenge": {
    "challengeId": "hp_xyz789",
    "expiresAt": "2025-12-17T10:05:00Z"
  }
}

The agent must obtain HP proof before proceeding:

TypeScript
if (permission.hpRequired) {
  const hpProof = await getHumanPresenceProof(permission.hpChallenge);
  
  const verification = await client.verifyCart({
    intentVc: permission.intentVc,
    cartVc: cartVc,
    hpProof: hpProof,  // Include HP proof
  });
}

See Human Presence guide for implementation details.

Intent lifecycle

StatusDescriptionNext States
PENDINGIntent created, awaiting evaluationACTIVE, DENIED
ACTIVEIntent approved and validUSED, EXPIRED
USEDCart verified with this intent
EXPIREDTime limit exceeded
DENIEDRejected by policy
StatusDescription
PENDINGIntent created but not yet evaluated
ACTIVEIntent approved and ready to use
USEDIntent was used for a cart verification
EXPIREDIntent exceeded its time limit
DENIEDIntent was rejected by policy

Intent expiration

Intents have a limited lifetime to prevent stale permissions:

TypeScript
// Default: 1 hour
const permission = await client.requestPermission({
  agentDid: '...',
  principalDid: '...',
  amount: 100,
  currency: 'USD',
  expiresIn: 3600,  // 1 hour in seconds
});

console.log(permission.intentVc.expirationDate);
// "2025-12-17T11:00:00Z"

If an intent expires, the agent must request a new one.

Query intents

List intents

Bash
curl "https://api.autopayos.com/ap2/intents?status=ACTIVE&limit=10" \
  -H "Authorization: Bearer $AUTOPAYOS_API_KEY"

Response:

JSON
{
  "data": [
    {
      "mandateId": "intent_abc123",
      "status": "ACTIVE",
      "agentDid": "did:key:z6Mk...",
      "maxAmount": 100.00,
      "currency": "USD",
      "createdAt": "2025-12-17T10:00:00Z",
      "expiresAt": "2025-12-17T11:00:00Z"
    }
  ],
  "hasMore": false,
  "cursor": null
}

Get intent by ID

Bash
curl "https://api.autopayos.com/ap2/intents/intent_abc123" \
  -H "Authorization: Bearer $AUTOPAYOS_API_KEY"

Advanced: Natural language parsing

AutopayOS can parse natural language intent requests:

TypeScript
const result = await client.parseIntent({
  text: "Buy groceries from Walmart, up to fifty dollars",
  agentDid: 'did:key:z6Mk...',
});

console.log(result);
// {
//   amount: 50.00,
//   currency: 'USD',
//   merchantDomain: 'walmart.com',
//   categories: ['groceries']
// }

Best practices

1. Request minimum permissions

Only request the amount and scope needed:

TypeScript
// Good: Specific amount and merchant
await client.requestPermission({
  amount: 25.00,
  merchantDomain: 'walmart.com',
});

// Bad: Overly broad
await client.requestPermission({
  amount: 10000.00,
  // No merchant restriction
});

2. Handle denials gracefully

TypeScript
const permission = await client.requestPermission({ ... });

if (!permission.allowed) {
  for (const code of permission.reasonCodes) {
    switch (code) {
      case 'AMOUNT_OVER_CAP':
        // Suggest a lower amount
        break;
      case 'MERCHANT_NOT_ALLOWED':
        // Suggest alternative merchants
        break;
      case 'VELOCITY_EXCEEDED':
        // Wait and retry later
        break;
    }
  }
}

3. Use idempotency keys

Prevent duplicate intents from network retries:

TypeScript
const permission = await client.requestPermission({
  ...params,
  idempotencyKey: `intent-${Date.now()}-${agentDid}`,
});

Evidence events

Intent operations produce these evidence events:

EventDescription
INTENT_ISSUEDIntent was created and approved
INTENT_DENIEDIntent was rejected by policy
INTENT_EXPIREDIntent exceeded time limit
INTENT_USEDIntent was used for cart verification

Query the evidence chain:

Bash
curl "https://api.autopayos.com/ap2/evidence/search?eventType=INTENT_ISSUED" \
  -H "Authorization: Bearer $AUTOPAYOS_API_KEY"

Next steps