The Complete Guide to Agent-to-Agent Marketplaces in 2026
Target audience: developers building autonomous AI agents who need to consume or expose services programmatically.
---not speculative futurism, but concrete patterns that work today.*
1. Why Agent‑to‑Agent (A2A) Marketplaces Matter
Agents increasingly perform narrow, repeatable tasks (data extraction, model inference, workflow orchestration). Instead of each developer re‑implementing these primitives, a marketplace lets agents discover, negotiate payment for, and invoke remote capabilities in a standardized way. The value proposition is simple: reduce duplicated engineering effort and enable composable automation.
The trade‑off is added latency and operational overhead. Every extra hop introduces network jitter, potential points of failure, and a need for reliable accounting. If your agent’s SLA tolerates sub‑second response times, a local library may still be preferable.
2. Core Technical Stack (2026)
| Layer | Typical Technology | Reason for Choice |
|---|---|---|
| Discovery | Decentralized service registry (IPFS‑based DHT) + optional centralized fallback | Censorship‑resistant, low‑cost look‑ups; fallback for low‑latency intranet use |
| Identity | DID‑based keys (did:key or did:ethr) stored in agent wallet | Cryptographically verifiable, works across chains |
| Authentication | Mutual TLS + signed JWT (JWS) using agent DID | Prevents man‑in‑the‑middle; JWT carries scopes and expiration |
| Payment Protocol | x402 (HTTP 402 Payment Required) with USDC on Base | Micropayment‑friendly, deterministic settlement, no custodial escrow |
| Transport | HTTP/2 or QUIC (via undici/h3) |
Multiplexed, low‑overhead, works behind most firewalls |
| Observability | OpenTelemetry traces exported to a collector (optional) | Enables SLA debugging without coupling to a vendor |
All components are deliberately pluggable: you can swap the registry for a Consul cluster if you run a private marketplace, or replace USDC with another stablecoin if your jurisdiction requires it.
3. Discovery: Finding the Right Agent
A minimal registry entry looks like this (JSON‑LD, stored on IPFS):
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Sentiment‑Analysis‑Agent",
"description": "Returns polarity score (−1 to 1) for English text.",
"version": "1.2.0",
"applicationCategory": "AIService",
"offers": {
"@type": "Offer",
"priceCurrency": "USDC",
"price": "0.03",
"eligibleRegion": { "@type": "Country", "name": "US" }
},
"endpoint": {
"@type": "EntryPoint",
"url": "https://agent.sentiment.example.com/infer",
"encodingType": "application/json"
},
"authentication": {
"type": "DIDJWT",
"did": "did:key:z6Mk..."
}
}
Lookup flow (pseudo‑code, TypeScript):
import { CID } from 'multiformats/cid';
import { create as createIpfsHttp } from 'ipfs-http-client';
const ipfs = createIpfsHttp({ url: 'https://ipfs.infura.io:5001' });
async function findService(name: string): Promise<ServiceRecord> {
// Query a DHT‑based index; here we simplify to a known CID
const registryCid = await ipfs.dag.get(
'bafybeigdyrzt5wfp7ud7g3vue2v6ft6b4lf4vjylzqpjk6yusoyk6bza3a'
);
const services = await ipfs.dag.get(registryCid.value.services as CID);
for (const rec of services.value as ServiceRecord[]) {
if (rec.name === name && rec.offers.price <= maxPrice) {
return rec;
}
}
throw new Error('No matching service found');
}
Trade‑off: Using a public DHT adds ~100‑200 ms lookup latency but removes a single point of failure. For latency‑critical internal tools, a Consul or etcd cluster behind a VPN can cut this to <5 ms.
4. Authentication & Authorization
Agents present a signed JWT that includes:
-
iss– the agent’s DID -
sub– the target service’s DID (optional, for audience restriction) -
iat,exp– typical timestamps -
scope– e.g.,sentiment:read
The JWT is signed with the agent’s private key; the service verifies the signature against the DID document retrieved from the registry (or a DID resolver).
Express middleware example (Node.js):
import { verifyJwt } from 'did-jwt-vc';
import { Resolver } from 'did-resolver';
import { getResolver } from 'ethr-did-resolver';
const resolver = new Resolver(getResolver());
export async function didAuth(req, res, next) {
const auth = req.headers.authorization?.split(' ')[1];
if (!auth) return res.status(401).send('Missing token');
try {
const payload = await verifyJwt({ jwt: auth, resolver });
// optional scope check
if (!payload.scope?.includes('sentiment:read')) {
return res.status(403).send('Insufficient scope');
}
req.agentDid = payload.iss;
next();
} catch (e) {
return res.status(401).send('Invalid token');
}
}
Trade‑off: JWT verification adds ~0.5‑1 ms CPU overhead per request. For high‑frequency micro‑calls (<10 ms latency budget) you may cache the public key for a short TTL (e.g., 30 s) after the first verification.
5. Payment & Settlement with x402
The x402 protocol turns HTTP 402 into a lightweight invoicing mechanism. When a client lacks sufficient funds, the service responds:
HTTP/1.1 402 Payment Required
Content-Type: application/json
Pay: ["https://wallet.example.com/pay", {"scheme":"erc20","address":"0x...","asset":"USDC","amount":"0.03"}]
The client then signs a payment payload (ERC‑4337 UserOperation or simple ECDSA signature) and resends the request with a Pay header.
Client‑side payment helper (TypeScript, using viem):
import { privateKeyToAccount } from 'viem/accounts';
import { http, createWalletClient, custom } from 'viem';
import { mainnet } from 'viem/chains';
const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY');
const wallet = createWalletClient({
account,
chain: mainnet,
transport: http('https://base-mainnet.g.alchemy.com/v2/your-key')
});
async function payAndRetry(url: string, body: any, maxPrice: string) {
let attempts = 0;
while (attempts < 3) {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (res.ok) return await res.json();
if (res.status === 402) {
const payInfo = await res.json();
const { asset, amount, address } = payInfo.Pay[1];
// Build ERC‑20 approve + transfer (simplified)
const { request } = await wallet.simulateContract({
address: asset as `0x${string}`,
abi: [
"function approve(address spender, uint256 amount) external returns (bool)",
"function transfer(address to, uint256 amount) external returns (bool)"
],
functionName: 'approve',
args: [address as `0x${string}`, BigInt(amount) * 10n ** 6n] // USDC 6 decimals
});
await wallet.writeContract(request);
// second call with Pay header
const payHeader = await wallet.signMessage({
message: JSON.stringify({ url, body, nonce: Date.now() })
});
const res2 = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Pay': payHeader
},
body: JSON.stringify(body)
});
if (res2.ok) return await res2.json();
}
attempts++;
}
throw new Error('Payment failed after retries');
}
Trade‑off: Each paid request incurs an on‑chain transaction (≈ $0.0005 gas on Base) plus the service fee. For high‑volume, low‑value interactions you can batch multiple calls into a single signed payload (x402 supports batched payments) to amortize gas.
6. Example: Exposing a Simple Agent Service
Below is a minimal Express service that offers a sentiment‑analysis model, requires DID‑JWT auth, and
Top comments (0)