A2A Protocol

The Agent-to-Agent (A2A) Protocol is a message-based communication standard for AI agents, based on Google's Agent Payments Protocol (AP2). AutopayOS implements A2A for secure, interoperable agent communication.

Overview

A2A enables agents to:

  • Discover each other's capabilities
  • Exchange structured messages
  • Request and grant permissions
  • Execute payments across trust boundaries
Diagram
┌──────────────┐                    ┌──────────────┐
│   Shopping   │  A2A Messages      │   Merchant   │
│    Agent     │◀──────────────────▶│    Agent     │
└──────────────┘                    └──────────────┘
       │                                   │
       │                                   │
       ▼                                   ▼
┌──────────────┐                    ┌──────────────┐
│  AutopayOS   │                    │   Merchant   │
│   Gateway    │                    │    Server    │
└──────────────┘                    └──────────────┘

Agent discovery

Agents discover each other using Agent Cards at well-known URLs.

Agent Card endpoint

GET https://agent.example.com/.well-known/agent-card.json

AutopayOS Agent Card

JSON
{
  "@context": "https://schema.org/",
  "@type": "SoftwareApplication",
  "name": "AutopayOS Payment Agent",
  "description": "Secure payment authorization for AI agents",
  "url": "https://api.autopayos.com",
  "provider": {
    "@type": "Organization",
    "name": "AutopayOS"
  },
  "capabilities": [
    "merchant",
    "payment-processor",
    "intent-issuer",
    "cart-validator"
  ],
  "protocols": ["ap2", "a2a"],
  "endpoints": {
    "a2a": "https://api.autopayos.com/a2a/gateway",
    "discovery": "https://api.autopayos.com/.well-known/agent-card.json"
  },
  "authentication": {
    "methods": ["api-key", "did-auth", "oauth2"]
  }
}

Fetch Agent Card

TypeScript
const response = await fetch('https://api.autopayos.com/.well-known/agent-card.json');
const agentCard = await response.json();

console.log('Capabilities:', agentCard.capabilities);
console.log('A2A Endpoint:', agentCard.endpoints.a2a);

Message structure

A2A messages follow a standard format:

JSON
{
  "jsonrpc": "2.0",
  "method": "a2a/message",
  "params": {
    "message": {
      "id": "msg_abc123",
      "type": "IntentMandate",
      "from": "did:key:z6MkAgentDid...",
      "to": "did:key:z6MkAutoPayOS...",
      "created": "2025-12-17T10:00:00Z",
      "body": {
        // Message-specific payload
      }
    }
  },
  "id": "req_xyz789"
}
FieldDescription
idUnique message identifier
typeMessage type (IntentMandate, CartMandate, etc.)
fromSender DID
toRecipient DID
createdISO 8601 timestamp
bodyMessage payload

Message types

IntentMandate

Request permission to make purchases:

JSON
{
  "type": "IntentMandate",
  "body": {
    "agentDid": "did:key:z6MkAgent...",
    "principalDid": "did:key:z6MkUser...",
    "request": {
      "maxAmount": 100.00,
      "currency": "USD",
      "vendorHints": {
        "domain": "amazon.com",
        "mcc": "5411"
      }
    },
    "expiresAt": "2025-12-17T11:00:00Z"
  }
}

IntentResult

Response to IntentMandate:

JSON
{
  "type": "IntentResult",
  "body": {
    "allowed": true,
    "intentVc": {
      "type": ["VerifiableCredential", "IntentMandate"],
      "credentialSubject": {
        "mandateId": "intent_abc123",
        "maxAmount": 100.00
      },
      "proof": { ... }
    },
    "attestation": {
      "policyId": "pol_xyz",
      "policyHash": "sha256:..."
    }
  }
}

CartMandate

Submit a verified cart:

JSON
{
  "type": "CartMandate",
  "body": {
    "intentVc": { ... },
    "cartVc": {
      "type": ["VerifiableCredential", "CartMandate"],
      "credentialSubject": {
        "merchant": "amazon.com",
        "items": [
          { "name": "USB Cable", "price": 12.99, "quantity": 2 }
        ],
        "total": 25.98,
        "currency": "USD"
      },
      "proof": { ... }
    }
  }
}

CartResult

Response to CartMandate:

JSON
{
  "type": "CartResult",
  "body": {
    "allowed": true,
    "approvalToken": "apt_abc123...",
    "railDecision": {
      "rail": "STRIPE",
      "reason": "Optimal for merchant"
    }
  }
}

PaymentMandate

Execute payment with approval:

JSON
{
  "type": "PaymentMandate",
  "body": {
    "approvalToken": "apt_abc123...",
    "userAuthorization": "sig_xyz..."
  }
}

PaymentResult

Response with Payment Mandate VC:

JSON
{
  "type": "PaymentResult",
  "body": {
    "pmVc": {
      "type": ["VerifiableCredential", "PaymentMandate"],
      "credentialSubject": {
        "mandateId": "pm_xyz789",
        "amount": 25.98,
        "currency": "USD"
      },
      "proof": { ... }
    },
    "railResponse": {
      "rail": "STRIPE",
      "paymentIntentId": "pi_..."
    }
  }
}

Send A2A messages

Using the SDK

TypeScript
import { A2AClient } from '@autopayos/a2a-client';

const a2a = new A2AClient({
  endpoint: 'https://api.autopayos.com/a2a/gateway',
  agentDid: 'did:key:z6MkMyAgent...',
  privateKey: myPrivateKey,
});

// Send IntentMandate
const result = await a2a.send({
  type: 'IntentMandate',
  to: 'did:key:z6MkAutoPayOS...',
  body: {
    agentDid: 'did:key:z6MkMyAgent...',
    principalDid: 'did:key:z6MkUser...',
    request: {
      maxAmount: 100.00,
      currency: 'USD',
    },
  },
});

console.log('Intent approved:', result.body.allowed);

Using cURL

Bash
curl https://api.autopayos.com/a2a/gateway \
  -H "Content-Type: application/json" \
  -H "X-Agent-DID: did:key:z6MkMyAgent..." \
  -d '{
    "jsonrpc": "2.0",
    "method": "a2a/message",
    "params": {
      "message": {
        "id": "msg_123",
        "type": "IntentMandate",
        "from": "did:key:z6MkMyAgent...",
        "to": "did:key:z6MkAutoPayOS...",
        "body": {
          "agentDid": "did:key:z6MkMyAgent...",
          "principalDid": "did:key:z6MkUser...",
          "request": {
            "maxAmount": 100.00,
            "currency": "USD"
          }
        }
      }
    },
    "id": "req_1"
  }'

Session management

A2A sessions track multi-message conversations:

Diagram
Session (contextId: ctx_abc123)
    │
    ├── Message 1: IntentMandate
    │       └── Response: IntentResult
    │
    ├── Message 2: CartMandate
    │       └── Response: CartResult
    │
    └── Message 3: PaymentMandate
            └── Response: PaymentResult

Create session

TypeScript
const session = await a2a.createSession({
  contextId: `session_${Date.now()}`,
  agentDid: 'did:key:z6MkMyAgent...',
  userDid: 'did:key:z6MkUser...',
});

Track session

TypeScript
const status = await a2a.getSession(contextId);

console.log('Status:', status.status);
console.log('Messages:', status.messageCount);
console.log('Intent:', status.intentMandateId);
console.log('Permit:', status.permitId);

Error handling

A2A errors follow JSON-RPC format:

JSON
{
  "jsonrpc": "2.0",
  "error": {
    "code": -32000,
    "message": "Policy violation",
    "data": {
      "reasonCodes": ["AMOUNT_OVER_CAP", "MERCHANT_NOT_ALLOWED"],
      "details": {
        "requested": 500.00,
        "cap": 100.00
      }
    }
  },
  "id": "req_1"
}

Error codes

CodeDescription
-32700Parse error
-32600Invalid request
-32601Method not found
-32602Invalid params
-32000Policy violation
-32001Authentication failed
-32002Session expired
-32003Rate limited

Security

Message signing

All A2A messages should be signed:

TypeScript
const signedMessage = await a2a.signMessage({
  type: 'IntentMandate',
  body: { ... },
}, privateKey);

// Message includes proof
console.log(signedMessage.proof);
// {
//   type: "Ed25519Signature2020",
//   verificationMethod: "did:key:z6Mk...#key-1",
//   jws: "eyJhbGciOiJFZERTQSJ9..."
// }

Verify signatures

TypeScript
const isValid = await a2a.verifyMessage(message);

if (!isValid) {
  throw new Error('Invalid message signature');
}

DID authentication

Authenticate using DID:

Bash
curl https://api.autopayos.com/a2a/gateway \
  -H "X-Agent-DID: did:key:z6MkMyAgent..." \
  -H "X-DID-Proof: eyJhbGciOiJFZERTQSJ9..."

Complete flow example

TypeScript
import { A2AClient } from '@autopayos/a2a-client';

async function completePurchase() {
  const a2a = new A2AClient({
    endpoint: 'https://api.autopayos.com/a2a/gateway',
    agentDid: myAgentDid,
    privateKey: myPrivateKey,
  });

  // Step 1: Request permission
  const intentResult = await a2a.send({
    type: 'IntentMandate',
    body: {
      agentDid: myAgentDid,
      principalDid: userDid,
      request: {
        maxAmount: 50.00,
        currency: 'USD',
        vendorHints: { domain: 'amazon.com' },
      },
    },
  });

  if (!intentResult.body.allowed) {
    throw new Error(`Denied: ${intentResult.body.reasonCodes}`);
  }

  // Step 2: Get cart from merchant
  const cartVc = await getCartFromMerchant('amazon.com');

  // Step 3: Verify cart
  const cartResult = await a2a.send({
    type: 'CartMandate',
    body: {
      intentVc: intentResult.body.intentVc,
      cartVc: cartVc,
    },
  });

  if (!cartResult.body.allowed) {
    throw new Error(`Cart rejected: ${cartResult.body.reasonCodes}`);
  }

  // Step 4: Execute payment
  const paymentResult = await a2a.send({
    type: 'PaymentMandate',
    body: {
      approvalToken: cartResult.body.approvalToken,
    },
  });

  console.log('Payment complete!');
  console.log('Mandate:', paymentResult.body.pmVc);

  return paymentResult;
}

AP2 compatibility

AutopayOS A2A is compatible with Google's AP2 specification:

AP2 FeatureAutopayOS Support
Agent CardsFull support
IntentMandateFull support
CartMandateFull support
PaymentMandateFull support
DID authenticationFull support
VC signaturesEd25519Signature2020

Next steps