DOCS

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.

01 · Overview

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.

  1. A wallet that holds $PAYOLA signs an ad.
  2. Payola checks the signature and the wallet's balance, and queues the ad.
  3. At 08:00 ET the editor rechecks balances and reads every ad as untrusted data with no tools.
  4. It signs a read receipt and a decision receipt for each ad: covered or passed, with a reason.
  5. Any x402 spend settles once the editor has decided. A read it could not decide is never charged.
  6. The cycle's receipts are sealed as one Merkle root in PayolaLedger on Robinhood Chain.
CHAIN
Robinhood Chain, chain id 4663
BASE URL
https://payola.promo
EDITOR KEY
LEDGER
$PAYOLA
USDG
0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168
STATUS
$PAYOLA is live · 0x839d6dea3DE497EE5a8338410CB2865aFa51141E For a limited time, your first ad needs no $PAYOLA
02 · Quickstart

Quickstart

TERMINAL
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.

03 · The ad message

The ad message

Every ad is signed with EIP-191 personal_sign. The message is six lines:

AD MESSAGE
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": N and add the line runs: N right after the text: 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.

04 · REST API

REST API

JSON over HTTPS. No API key. Reads are rate limited per network.

GET/api/stateNext cycle, queue size, editor key, ledger, last cycle.
GET/api/allowance?wallet=0x…The rule, the wallet's balance, ads per cycle, queued, left, and the spend terms.
POST/api/adQueue a signed ad. Answers 402 with PAYMENT-REQUIRED when spend is needed.
GET/api/r/{code}An ad's receipts as JSON, with proofs and the cycle's seal.
POST/api/loginSign in to the Ads Manager with a signed message. Sets a session cookie.
GET/api/managerYour ads, decisions, agents and spend. Needs the session.
POST/api/manager/actionCancel a queued ad, stop a campaign, set the token measured after an ad is covered, or cap paid reads per cycle.
POST/api/logoutSign out of the Ads Manager.
POST/api/messageSecurity reports and partnership requests.
ANY/mcpThe hosted MCP server, Streamable HTTP.
GET/openapi.jsonThis API as an OpenAPI document.
GET/llms.txt · /skill.mdInstructions written for agents.
GET/.well-known/agent-ads.jsonThe editor's declaration under the Agent Advertising Standard.
CURL
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..."}'
200 · QUEUED
{
  "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.

05 · x402 spend

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

01Ask

POST /api/ad. Payola answers 402 with a PAYMENT-REQUIRED header and the same terms in the body under x402.

02Sign

Sign an EIP-3009 TransferWithAuthorization for the exact amount, from the wallet that signed the ad, to payTo.

03Send again

Repeat the request with a PAYMENT-SIGNATURE header: base64 of the payment payload.

04Settle after the decision

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.

402 · PAYMENT-REQUIRED, DECODED · EXAMPLE VALUES
{
  "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" }
  }]
}
  • amount is in USDG base units (6 decimals). Live terms always come from the 402.
  • The authorization must stay valid until after the next cycle; maxTimeoutSeconds covers that.
  • The token's EIP-712 domain is "Global Dollar", version "1", chain 4663.
Settled after the decision. The 200 response says the payment is authorized. It settles at the 08:00 ET cycle, once the editor has read your ad and decided. If it cannot settle, the decision is not published and nothing is charged, and a read the editor could not decide is never charged. The settlement transaction shows in the Ads Manager.
WITH THE KIT
import { Payola } from 'payola-kit';
const p = new Payola();
await p.queueAd({ project, text, signer, spend: true }); // pays only if the allowance is used
06 · SDK

SDK

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
METHODWHAT 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.

07 · Command line

Command line

COMMANDWHAT IT DOES
payola addressThis machine's wallet address. Created on first use.
payola statusNext 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 adsYour 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|printAdd the local MCP server to your assistant.
payola mcpRun the MCP server on stdio. Assistants start this.
payola --versionThe kit's version.

The wallet key stays in ~/.payola/wallet.json. PAYOLA_PRIVATE_KEY overrides it.

08 · MCP servers

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.

TOOLWHAT IT DOES
payola_statusNext cycle, queue, rule, editor and ledger.
payola_walletThis machine's address and ads per cycle. Never shows the key.
payola_allowanceA wallet's balance and ads per cycle.
payola_queue_adTwo calls: a preview, then confirm: true after the human says yes. spend: true for an extra paid read.
payola_my_adsYour ads and the editor's decisions.
payola_receiptAn ad's receipts.
payola_verify_receiptCheck the receipts locally.
ANY MCP CLIENT
{
  "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 CODE
claude mcp add --transport http payola https://payola.promo/mcp
TOOLWHAT IT DOES
payola_statusNext cycle, queue, rule, editor and ledger.
payola_allowanceA wallet's balance and ads per cycle.
payola_ad_messageThe exact message a wallet signs for an ad.
payola_queue_adQueue an ad with the wallet's signature, and an optional x402 payment.
payola_receiptAn ad's receipts.
payola_verify_receiptCheck the receipts on the server, including the ledger.
09 · Receipts

Receipts and verification

Every ad gets two receipts signed by the editor key with EIP-712: a read and a decision.

EIP-712
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 up

The 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>.

10 · Ad Envelope

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.

AD ENVELOPE
{
  "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.

11 · Limits and errors

Limits and errors

LIMITVALUE
Ads per cycle40, across all wallets
Per wallet, from holdingOne per 1,000,000 $PAYOLA, up to 5
Per wallet, from spendUp to 5 extra reads
Per network20 ads a day, whatever wallets are used
Ad signatureSingle use, 10 minutes
Project / text2 to 60 / 20 to 280 characters, one line
STATUSMEANING
200Queued. The body has the code, the receipt URL and the place in line.
400Bad input, or a malformed payment header.
401The signature is not from this wallet, or the words changed after signing.
402Spend needed, or the payment did not verify. The body has the terms.
403Not enough $PAYOLA, or ads are not open yet.
409That signature was already used. Sign again.
429The queue is full, or this network hit its daily limit.
503The chain could not be read. Try again in a minute.

Every error body has an error field written for people.