Agent protocols — TAP / MPP / ACP

Three open standards. Same Ed25519-signed envelope. Pick the one that fits your use case.

| Protocol | Acronym | Best for | |---|---|---| | Trusted Agent Protocol | TAP | First-party agents (yours) on your workspace — identity attestation | | Machine Payments Protocol | MPP | High-frequency M2M — light envelope, designed for automation | | Agentic Commerce Protocol | ACP | Shopping flows — search → decide → pay |

Wire format

All three share the same PaymentIntent shape. Only the protocol field differs.

PaymentIntent (TypeScript)typescript
type PaymentIntent = {
agent_id: string;
merchant: {
  name: string;
  mcc: string;            // ISO 18245 4-digit
  reap_merchant_id?: string;
};
amount: {
  currency: string;       // ISO 4217 (USD, EUR, GBP, etc.)
  cents: number;
};
funding?: {
  asset?: "USDC" | "USDT" | "BTC" | "ETH" | "SOL";
  chain?: "ethereum" | "polygon" | "arbitrum" | "base" | "optimism" | "solana";
};
intent_id: string;        // UUIDv4; unique forever
iat: number;              // unix seconds, issued-at
exp: number;              // unix seconds, expiry (iat + ttl)
protocol: "TAP" | "MPP" | "ACP";
description?: string;
};

Canonical JSON

The signature is computed over canonical JSON: keys sorted alphabetically at every level, no whitespace. Byte-identical between TypeScript and Python SDKs (verified in our test suite).

canonicalize(obj)javascript
function canonicalize(obj) {
if (obj === null || typeof obj !== "object") return JSON.stringify(obj);
if (Array.isArray(obj)) return "[" + obj.map(canonicalize).join(",") + "]";
const keys = Object.keys(obj).sort();
return "{" + keys.map(k => JSON.stringify(k) + ":" + canonicalize(obj[k])).join(",") + "}";
}

Signed envelope

SignedIntenttypescript
type SignedIntent = {
intent: PaymentIntent;
signature: string;        // base64url-encoded Ed25519 signature over canonical JSON
key_id: string;           // base64url(sha256(SPKI-DER of the public key))
};

Server-side verification

  1. Look up the agent by signed.intent.agent_id
  2. Fetch its registered publicKey (stored as DER)
  3. Compute keyFingerprint(publicKey) and compare to signed.key_id (constant time)
  4. Recompute canonical JSON of signed.intent
  5. Verify the signature with one-shot crypto.verify(null, canonical, publicKey, sig)
  6. Check signed.intent.exp > now and signed.intent.iat <= now + 60
  7. Insert into agent_intent_log with the unique-constraint on intent_id — replays return HTTP 422

TTL recommendation

| Protocol | Typical TTL | |---|---| | TAP | 300s (5 min) | | MPP | 60s (1 min) — high-frequency | | ACP | 600s (10 min) — multi-step shopping flow |

Don't reuse intent_id

Even for retries, generate a fresh UUIDv4. The server uses this for replay protection and will reject duplicates with HTTP 422.

Visa Intelligent Commerce

When our issuing partner accepts a signed intent, it routes through Visa's Intelligent Commerce network using a Visa-issued credential bound to your agent. The resulting auth flows through our standard webhook at /api/webhooks/reap-authorization, where our authorization engine applies your card scopes (MCC, merchant, limits) before sending the final decision back to the issuing partner → Visa within ~200ms.