The Complete Guide to Agent‑to‑Agent Marketplaces in 2026
For developers who are building autonomous AI agents and need a realistic view of how agent‑to‑agent (A2A) markets actually work today.
1. What an A2A Marketplace Actually Is
An A2A marketplace is a decentralized or semi‑centralized platform that lets one autonomous agent discover, negotiate, pay for, and invoke services offered by another agent. Unlike a classic API marketplace, the participants are software agents that can:
- Publish a service descriptor that includes capability signatures, pricing, SLA, and required credentials.
- Consume descriptors, formulate a request, and automatically handle payment and attestation.
- Operate without a human in the loop for the discovery‑to‑execution cycle.
In 2026 the dominant pattern is request‑response over HTTP/2 with JSON‑LD service descriptors, x402 HTTP‑based micropayment headers, and Verifiable Credentials for trust. The stack is deliberately minimal: no proprietary SDKs, just standards that any language can implement.
2. Core Building Blocks
| Block | Purpose | Typical Spec (2026) |
|---|---|---|
| Service Descriptor | Advertises what the agent can do | JSON‑LD schema https://schema.org/Service + custom agent:capability field |
| Discovery Protocol | Lets buyers find sellers |
Agent‑Discovery‑Protocol (ADP) over DHT or a lightweight federated index (e.g., IPFS‑pinning + signed CID list) |
| Payment Layer | Enables trustless, sub‑cent transfers |
x402 – HTTP 402 Payment Required with Payload: header containing a signed USDC transfer on Base (ERC‑20) |
| Identity & Attestation | Verifies that the caller is who it claims | Verifiable Credentials (VC) – issuer = agent’s DID, verifier = marketplace or counterparty |
| Invocation Contract | Defines retry, timeout, and SLA handling | Simple JSON‑RPC over HTTP/2 with x-agent-retry and x-agent-timeout headers |
All of these pieces can be assembled with under 200 lines of code; the rest is plumbing.
3. Service Descriptor Example
Below is a minimal descriptor for an agent that offers sentiment analysis on short text. It lives at https://agent.example.com/descriptor.jsonld.
{
"@context": [
"https://schema.org/",
{ "agent": "https://w3id.org/agent#" }
],
"@type": "Service",
"name": "SentimentAnalyzer",
"description": "Returns a sentiment score (−1 to 1) for English sentences ≤ 200 tokens.",
"agent:capability": {
"input": { "type": "string", "maxLength": 200 },
"output": { "type": "number", "minimum": -1, "maximum": 1 }
},
"offers": {
"@type": "Offer",
"priceSpecification": {
"@type": "UnitPriceSpecification",
"price": "0.02",
"priceCurrency": "USDC",
"valueAddedTaxIncluded": false
},
"eligibleRegion": { "@type": "Country", "name": "US" },
"availability": "https://schema.org/AlwaysAvailable"
},
"agent:endpoint": "https://agent.example.com/invoke",
"agent:did": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
}
Why JSON‑LD? It lets any agent merge the descriptor with its own ontology without custom parsers. The agent:capability block is deliberately tiny; you can extend it with input/output schemas using JSON Schema if you need stricter validation.
4. Publishing to a Marketplace
A marketplace is simply a hosted index that accepts signed descriptors and makes them searchable. The following Python snippet shows how an agent can register itself with a marketplace that implements the ADP over IPFS (using ipfshttpclient).
import json, os, ipfshttpclient
from eth_account.messages import encode_defunct
from web3 import Web3
# ------------------------------------------------------------------
# Config – replace with your own values
# ------------------------------------------------------------------
IPFS_API = "/ip4/127.0.0.1/tcp/5001/http"
PRIVATE_KEY = os.getenv("AGENT_PRIVATE_KEY") # 0x-prefixed hex
W3 = Web3(Web3.HTTPProvider("https://base-mainnet.infura.io/v3/<PROJECT_ID>"))
def sign_descriptor(descriptor: dict) -> str:
"""Return a hex‑encoded ECDSA signature over the canonical JSON."""
payload = json.dumps(descriptor, sort_keys=True, separators=(",", ":")).encode()
message = encode_defunct(text=payload.decode())
signed = W3.eth.account.sign_message(message, private_key=PRIVATE_KEY)
return signed.signature.hex()
def publish(descriptor_path: str):
with open(descriptor_path, "r") as f:
desc = json.load(f)
desc["agent:signature"] = sign_descriptor(desc)
client = ipfshttpclient.connect(IPFS_API)
res = client.add_json(desc)
cid = res["cid"]
print(f"Published descriptor → ipfs://{cid}")
# OPTIONAL: push CID to a federated index (e.g., a simple HTTP POST)
# requests.post("https://marketplace.example.com/index", json={"cid": cid})
if __name__ == "__main__":
publish("descriptor.jsonld")
Trade‑offs
| Aspect | Benefit | Cost / Risk |
|---|---|---|
| IPFS storage | Immutable, censorship‑resistant, no vendor lock‑in | Retrieval latency (seconds) if not pinned; you must run a pinning service or rely on the marketplace to do it. |
| ECDSA signature | Simple, works with any Ethereum‑compatible key | No built‑in revocation; if the key is compromised you must issue a new DID and update all descriptors. |
| Federated index | Enables keyword search without scanning all CIDs | Requires trust in the index operator; you can mitigate with multi‑signature validation of posted CIDs. |
5. Discovering and Invoking a Service
Discovery is usually a two‑step process: (1) query the index for matching CIDs, (2) fetch the descriptor, verify its signature, then compose an x402‑paid request.
5.1 Querying the Index (pseudo‑REST)
GET https://marketplace.example.com/search?q=sentiment&capability.output.type=number
Response (trimmed):
{
"results": [
{
"cid": "bafybeigdyrzt5wfp7ud7g2u2v6bheswtcq6g7namr4vj5v3j5tbkaef4e",
"score": 0.92
}
]
}
5.2 Fetching & Verifying the Descriptor
import json, ipfshttpclient
from eth_account import Account
from eth_account.messages import encode_defunct
def fetch_and_verify(cid: str) -> dict:
client = ipfshttpclient.connect("/ip4/127.0.0.1/tcp/5001/http")
desc = client.get_json(cid)
# Verify signature
sig = desc.pop("agent:signature")
payload = json.dumps(desc, sort_keys=True, separators=(",", ":")).encode()
message = encode_defunct(text=payload.decode())
recovered = Account.recover_message(message, signature=bytes.fromhex(sig))
expected = desc["agent:did"].split(":")[-1] # simplified for did:key
assert recovered.lower() == expected.lower(), "Invalid signature"
return desc
5.3 Making the x402‑Paid Call
When the descriptor indicates a price, the client must include an x402 header. The server replies with 402 Payment Required and a Payload: header containing the signed USDC transfer. The client then resends the request with the payment proof.
python
import requests
from eth_account import Account
from eth_account.messages import encode_defunct
BASE_URL = "https://agent.example.com/invoke"
USDC_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" # Base USDC
CHAIN_ID = 8453
def make_payment(amount_usdc: str) -> str:
"""Create a signed ERC‑20 transfer (USDC) and return hex‑encoded data."""
from web3 import Web3
w3 = Web3(Web3.HTTPProvider("https://base-mainnet.infura.io/v3/<PROJECT_ID>"))
usdc = w3.eth.contract(address=USDC_CONTRACT, abi=[
{"constant":False,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],
"name":"transfer","outputs":[{"name":"","type":"bool"}],"type":"function"}
])
nonce = w3.eth.get_transaction_count(Account.from_key(PRIVATE_KEY).address)
tx = usdc.functions.transfer(
Web3.toChecksumAddress("0xAgentServiceAddress"), # replace with service’s wallet
int(float(amount_usdc) * 1e6) # USDC has 6 decimals
).buildTransaction({
"chainId": CHAIN_ID,
"gas
Top comments (0)