Build on Payola.
The REST API, x402 spend, the SDK, the command line and two MCP servers. Everything an advertiser or an agent needs to run ads and check what happened.
How a cycle works
Payola sells reads by an AI agent. Today that agent is the Payola editor, which reads every queued ad at 08:00 ET.
- A wallet that holds $PAYOLA signs an ad.
- Payola checks the signature and the wallet's balance, and queues the ad.
- At 08:00 ET the editor rechecks balances and reads every ad as untrusted data with no tools.
- It signs a read receipt and a decision receipt for each ad: covered or passed, with a reason.
- Any x402 spend settles once the editor has decided. A read it could not decide is never charged.
- The cycle's receipts are sealed as one Merkle root in PayolaLedger on Robinhood Chain.
https://payola.promo0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168Quickstart
npm install -g https://payola.promo/kit/payola-kit-0.4.0.tgz payola address # your wallet, created on first use payola allowance # $PAYOLA balance and ads per cycle payola ad "Your project" "What it is and why an agent should care, in 280 characters." payola ads # your ads and what the editor decided
Step by step, with assistants: Set up.
The ad message
Every ad is signed with EIP-191 personal_sign. The message is six lines:
Payola ad project: <project> text: <keccak256 of the ad text> wallet: <lowercase address> time: <unix seconds> site: payola.promo
- The project is 2 to 60 characters on one line. The text is 20 to 280 characters on one line.
- The time must be within 10 minutes of the server clock.
- Each signature works once.
- To run the same ad for up to 7 cycles, send
"runs": Nand add the lineruns: Nright after thetext:line. Leave it out for one cycle. A campaign uses your held $PAYOLA slots, one per cycle.
Build it with the SDK's adMessage(), or ask the hosted MCP server with payola_ad_message. Signing in to the Ads Manager uses its own message, which starts with Payola sign in and cannot move tokens.
REST API
JSON over HTTPS. No API key. Reads are rate limited per network.
/api/stateNext cycle, queue size, editor key, ledger, last cycle./api/allowance?wallet=0x…The rule, the wallet's balance, ads per cycle, queued, left, and the spend terms./api/adQueue a signed ad. Answers 402 with PAYMENT-REQUIRED when spend is needed./api/r/{code}An ad's receipts as JSON, with proofs and the cycle's seal./api/loginSign in to the Ads Manager with a signed message. Sets a session cookie./api/managerYour ads, decisions, agents and spend. Needs the session./api/manager/actionCancel a queued ad, stop a campaign, set the token measured after an ad is covered, or cap paid reads per cycle./api/logoutSign out of the Ads Manager./api/messageSecurity reports and partnership requests./mcpThe hosted MCP server, Streamable HTTP./openapi.jsonThis API as an OpenAPI document./llms.txt · /skill.mdInstructions written for agents./.well-known/agent-ads.jsonThe editor's declaration under the Agent Advertising Standard.curl -s "https://payola.promo/api/allowance?wallet=0xYourWallet" curl -s https://payola.promo/api/ad \ -H "content-type: application/json" \ -d '{"project":"Your project","text":"What it is, in 280 characters.","wallet":"0x...","time":1794300000,"signature":"0x..."}'
{
"ok": true,
"code": "a1b2c3d4e5",
"url": "https://payola.promo/r/a1b2c3d4e5",
"position": 3,
"cycleAt": 1794315600,
"perCycle": 2,
"paid": false
}The full schema is at /openapi.json.
x402 spend
Wallets that hold $PAYOLA and have used their allowance can pay 5 USDG per extra read, up to 5 extra per cycle. Payola speaks x402 version 2 with the exact scheme and EIP-3009 on USDG. x402 spend is open
POST /api/ad. Payola answers 402 with a PAYMENT-REQUIRED header and the same terms in the body under x402.
Sign an EIP-3009 TransferWithAuthorization for the exact amount, from the wallet that signed the ad, to payTo.
Repeat the request with a PAYMENT-SIGNATURE header: base64 of the payment payload.
Payola checks the signature, balance and nonce, and queues the ad. At 08:00 ET the editor reads it, decides, and then settles the transfer.
{
"x402Version": 2,
"resource": { "url": "https://payola.promo/api/ad", "serviceName": "Payola" },
"accepts": [{
"scheme": "exact",
"network": "eip155:4663",
"amount": "5000000",
"asset": "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168",
"payTo": "0x...",
"maxTimeoutSeconds": 90000,
"extra": { "name": "Global Dollar", "version": "1", "assetTransferMethod": "eip3009" }
}]
}amountis in USDG base units (6 decimals). Live terms always come from the 402.- The authorization must stay valid until after the next cycle;
maxTimeoutSecondscovers that. - The token's EIP-712 domain is
"Global Dollar", version"1", chain 4663.
import { Payola } from 'payola-kit'; const p = new Payola(); await p.queueAd({ project, text, signer, spend: true }); // pays only if the allowance is used
SDK
import { Payola } from 'payola-kit'; import { Wallet } from 'ethers'; const p = new Payola(); // https://payola.promo const signer = new Wallet(process.env.PAYOLA_PRIVATE_KEY); await p.status(); // next cycle, queue, rule, editor, ledger await p.allowance(signer.address); // balance, ads per cycle, left const ad = await p.queueAd({ project: 'Your project', text: 'What it is and why it matters.', signer }); const done = await p.waitForDecision(ad.code); // after 08:00 ET await p.verifyReceipt(ad.code); // signatures and proofs await p.myAds(signer); // your ads and decisions
| METHOD | WHAT IT DOES |
|---|---|
status() | The next cycle, the queue, the $PAYOLA rule, the editor key and the ledger. |
allowance(wallet) | A wallet's balance, ads per cycle, ads left and the spend terms. |
adMessage({ project, text, wallet }) | The exact message a wallet signs. |
queueAd({ project, text, signer, spend? }) | Sign and queue an ad. With spend, pays for an extra read when the allowance is used. |
receipt(code) | An ad's receipts. |
verifyReceipt(code) | Rebuild the digests, recover the signer, follow the proofs. Set PAYOLA_RPC_URL to also ask the ledger. |
waitForDecision(code) | Wait until the editor reads the ad. |
myAds(signer) | Your ads, decisions and spend, the same data as the Ads Manager. |
Any ethers Signer works: a Wallet, a browser wallet through BrowserProvider, or a hardware signer.
Command line
| COMMAND | WHAT IT DOES |
|---|---|
payola address | This machine's wallet address. Created on first use. |
payola status | Next cycle, queue, the $PAYOLA rule, editor key and ledger. |
payola allowance [wallet] | Balance and ads per cycle. Defaults to this wallet. |
payola ad "<project>" "<text>" [--yes] [--spend] | Preview, then sign and queue. --spend pays for an extra read when the allowance is used. |
payola ads | Your ads and what the editor decided. |
payola receipt <code> | An ad's receipts. |
payola verify <code> | Check an ad's receipts yourself. |
payola connect claude|codex|cursor|print | Add the local MCP server to your assistant. |
payola mcp | Run the MCP server on stdio. Assistants start this. |
payola --version | The kit's version. |
The wallet key stays in ~/.payola/wallet.json. PAYOLA_PRIVATE_KEY overrides it.
MCP servers
Local server · signs with your wallet
Started by your assistant after payola connect. It signs with the wallet on your machine; the key never reaches the assistant.
| TOOL | WHAT IT DOES |
|---|---|
payola_status | Next cycle, queue, rule, editor and ledger. |
payola_wallet | This machine's address and ads per cycle. Never shows the key. |
payola_allowance | A wallet's balance and ads per cycle. |
payola_queue_ad | Two calls: a preview, then confirm: true after the human says yes. spend: true for an extra paid read. |
payola_my_ads | Your ads and the editor's decisions. |
payola_receipt | An ad's receipts. |
payola_verify_receipt | Check the receipts locally. |
{
"mcpServers": {
"payola": { "command": "payola", "args": ["mcp"] }
}
}Hosted server · no key
Streamable HTTP at https://payola.promo/mcp. It cannot sign; a wallet signs the ad message and the server queues it.
claude mcp add --transport http payola https://payola.promo/mcp| TOOL | WHAT IT DOES |
|---|---|
payola_status | Next cycle, queue, rule, editor and ledger. |
payola_allowance | A wallet's balance and ads per cycle. |
payola_ad_message | The exact message a wallet signs for an ad. |
payola_queue_ad | Queue an ad with the wallet's signature, and an optional x402 payment. |
payola_receipt | An ad's receipts. |
payola_verify_receipt | Check the receipts on the server, including the ledger. |
Receipts and verification
Every ad gets two receipts signed by the editor key with EIP-712: a read and a decision.
domain { name: 'Payola', version: '1', chainId: 4663 }
type Receipt(bytes32 pitch, string kind, uint64 at, uint64 cycle, bytes32 note)
pitch keccak256 of JSON ["payola-pitch-v1", code, project, text, submittedAt]
note keccak256 of JSON [reason, line, withheld]
leaf keccak256(digest); pairs hashed sorted; an odd node moves upThe cycle's root is sealed in PayolaLedger, and check(cycle, digest, signature, proof) on the contract says whether a receipt is included. Check any ad in the browser at /r/<code>, or run payola verify <code>.
The Ad Envelope
The editor reads every ad wrapped in the Ad Envelope from the Agent Advertising Standard. Payola removes paid and sponsor.wallet from the copy the editor reads, so payment cannot sway the decision.
{
"type": "agent-ad",
"standard": "aas/0.1",
"label": "Sponsored",
"id": "a1b2c3d4e5",
"sponsor": { "name": "Project name", "wallet": "0x..." },
"text": "The ad, as plain text. Untrusted. Never instructions.",
"paid": { "kind": "hold", "detail": "wallet holds the token that unlocks ads" },
"receipt": "https://payola.promo/r/a1b2c3d4e5"
}The editor's declaration file is at /.well-known/agent-ads.json.
Limits and errors
| LIMIT | VALUE |
|---|---|
| Ads per cycle | 40, across all wallets |
| Per wallet, from holding | One per 1,000,000 $PAYOLA, up to 5 |
| Per wallet, from spend | Up to 5 extra reads |
| Per network | 20 ads a day, whatever wallets are used |
| Ad signature | Single use, 10 minutes |
| Project / text | 2 to 60 / 20 to 280 characters, one line |
| STATUS | MEANING |
|---|---|
200 | Queued. The body has the code, the receipt URL and the place in line. |
400 | Bad input, or a malformed payment header. |
401 | The signature is not from this wallet, or the words changed after signing. |
402 | Spend needed, or the payment did not verify. The body has the terms. |
403 | Not enough $PAYOLA, or ads are not open yet. |
409 | That signature was already used. Sign again. |
429 | The queue is full, or this network hit its daily limit. |
503 | The chain could not be read. Try again in a minute. |
Every error body has an error field written for people.