$agentpaypump.fun ↗Solscan ↗
Make a link

docs · v1

The agentpay API

Base URL https://agentpay.wtf. No API key, no signup, open CORS. Machine-readable: /llms.txt · /openapi.json.

how an agent uses this

When your agent wants to charge for something, it sends POST /api/invoice with its wallet as to, an amount in SOL, a label (its name) and an optional memo (an order id). It gives the returned url (or qr) to whoever is paying. It then polls GET /api/invoice/status with the returned reference, to and amount every 2–5 seconds. When the response says "paid": true, the payment has been checked on-chain to the recipient for at least that amount, and it includes the signature, payer and slot. The agent then delivers. agentpay never holds funds and takes no fee.

1 · simple links (no API)

A pay link carries everything in its URL. Leave out amount for a tip jar where the payer picks the amount.

https://agentpay.wtf/pay/<AGENT_WALLET>?amount=0.05&label=research-bot-7&message=1%20report&memo=order-1234
https://agentpay.wtf/pay/<AGENT_WALLET>?label=tip%20my%20bot        # tip jar

The page shows a pay card, a Pay with Phantom button, a Solana Pay QR and a live status. If there is no reference in the link, the page makes a fresh one for each visitor and polls it.

2 · POST /api/invoice

Creates a single-use invoice: a Solana Pay transfer request with a fresh random reference public key.

fieldtypenotes
tostring, requiredrecipient wallet (base58, 32 bytes)
amountstringSOL, e.g. "0.05". Optional (tip invoice). >0, ≤10000, ≤9 decimals
labelstring≤64 chars, shown to the payer
messagestring≤140 chars, shown to the payer
memostring≤120 chars, written on-chain (public)
clusterstring"mainnet-beta" (default) or "devnet"
curl -s -X POST https://agentpay.wtf/api/invoice \
  -H 'content-type: application/json' \
  -d '{"to":"<AGENT_WALLET>","amount":"0.05","label":"research-bot-7","memo":"order-1234"}'
{
  "url": "https://agentpay.wtf/pay/<AGENT_WALLET>?amount=0.05&label=research-bot-7&memo=order-1234&reference=<REF>",
  "reference": "<REF>",
  "solanaPayUrl": "solana:<AGENT_WALLET>?amount=0.05&reference=<REF>&label=research-bot-7&memo=order-1234",
  "qr": "data:image/png;base64,iVBORw0…",
  "statusUrl": "https://agentpay.wtf/api/invoice/status?reference=<REF>&to=<AGENT_WALLET>&amount=0.05&memo=order-1234",
  "to": "<AGENT_WALLET>", "amount": "0.05", "label": "research-bot-7", "memo": "order-1234", "cluster": "mainnet-beta"
}

Errors return HTTP 400 {"error": "...", "field": "to"}, for example for an invalid address, a negative or absurd amount, or too many decimals.

3 · GET /api/invoice/status

curl -s "https://agentpay.wtf/api/invoice/status?reference=<REF>&to=<AGENT_WALLET>&amount=0.05&memo=order-1234"
statusmeaning
pendingno transaction references this key yet
confirmingsignature seen, transaction not readable yet
invalida tx with the reference exists but does not match (underpaid, wrong recipient, memo mismatch, failed). reason says why
paidvalidated on-chain: signature, amount, payer, slot, blockTime, confirmationStatus
{
  "paid": true, "status": "paid",
  "signature": "<tx signature>", "amount": "0.05", "lamports": "50000000",
  "payer": "<payer wallet>", "slot": 453190539, "blockTime": 1791099256,
  "confirmationStatus": "confirmed", "validator": "@solana/pay validateTransfer"
}

how verification works

4 · paywall recipe

const API = 'https://agentpay.wtf'
async function charge(wallet, amount, orderId) {
  const inv = await fetch(API + '/api/invoice', { method: 'POST', headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ to: wallet, amount, label: 'my-agent', memo: orderId }) }).then(r => r.json())
  return inv  // send inv.url to the user, keep inv.statusUrl
}
async function waitPaid(statusUrl, ms = 15 * 60e3) {
  for (const end = Date.now() + ms; Date.now() < end;) {
    const s = await fetch(statusUrl).then(r => r.json())
    if (s.paid) return s
    await new Promise(r => setTimeout(r, 3000))
  }
  return null
}

An HTTP-402 style flow works well: answer the first request with 402 plus the invoice url, and serve the content once status is paid.

5 · agentpay for X

AI agents have X accounts. agentpay ties a pay page to an agent's X handle: https://agentpay.wtf/@<handle> (also /x/<handle>). The page shows the handle, a verified badge, the wallet, a tip jar (or the invoice amount), Pay with Phantom and a Solana Pay QR, with live paid status through reference keys. A handle only gets a page after its owner proves both sides:

Handles are lowercased. Re-claiming needs fresh proofs (a new signature and a new post) and replaces the wallet. Claim endpoints are IP rate-limited. Humans can use the claim page.

how an X agent uses this

Claim once: POST /api/x/claim/start with {"handle","wallet"}, sign the returned message with the wallet, post tweetText from the handle, then POST /api/x/claim/finish with {handle, wallet, signature, tweetUrl}. After that, when someone tags the agent asking to pay, the agent replies with its page (https://agentpay.wtf/@handle, a tip jar) or, for a priced job, with an invoice from POST /api/x/<handle>/invoice {"amount":"0.05","memo":"job-12"}. It keeps the invoice's statusUrl, polls it every 3–5 seconds, and delivers when it says "paid": true. Only reply to posts that tag you (X only allows automated replies when summoned).

POST /api/x/claim/start

fieldtypenotes
handlestring, requiredX handle, with or without @ (1–15 of a-z 0-9 _)
walletstring, requiredSolana wallet that receives payments

Returns {handle, wallet, code, message, tweetText, tweetIntentUrl, issuedAt, expiresAt}. The code rotates every 15 minutes; a started claim can be finished for about an hour.

POST /api/x/claim/finish

fieldtypenotes
handle, walletstring, requiredsame as start
signaturestring, required64-byte ed25519 signature of the exact message, base58 (or base64/hex)
tweetUrlstring, requiredhttps://x.com/<handle>/status/<id> of the post containing the code
biostringoptional line shown on the page, ≤140 chars (plain text)
suggestedAmountstringoptional SOL amount preselected in the tip jar

Saves only when both checks pass and returns the public record. Errors: 400 {error, field, reason} where reason is wrong_author, code_missing, stale_tweet, tweet_not_found or reused_tweet; 502 x_unreachable if X can't be read right now (nothing is saved); 429 when rate-limited.

GET /api/x/<handle>

{
  "handle": "research_bot", "verified": true, "wallet": "<WALLET>",
  "payUrl": "https://agentpay.wtf/@research_bot",
  "solanaPayUrl": "solana:<WALLET>?label=%40research_bot",
  "invoiceUrl": "https://agentpay.wtf/api/x/research_bot/invoice",
  "displayName": "Research Bot", "bio": …, "suggestedAmount": …, "verifiedAt": "…", "tweetUrl": "https://x.com/research_bot/status/…"
}

404 {"verified": false, "claimUrl": …} if the handle hasn't been claimed. Never pay an address that isn't returned here or shown on the verified page.

POST /api/x/<handle>/invoice

Body {amount?, memo?, message?}. Same response as POST /api/invoice (url, reference, solanaPayUrl, qr, statusUrl, to, amount, memo) paying the handle's verified wallet. The url is the handle page, so the payer sees the verified @.

curl -s -X POST https://agentpay.wtf/api/x/research_bot/invoice -H 'content-type: application/json' -d '{"amount":"0.05","memo":"job-12"}'

bots that answer tags (UseTaggedBot-style)

Tag-to-act bots such as @UseTaggedBot poll their mentions and reply only to one exact format. POST /api/x/mention does the agentpay part for any bot: send the tagged post's text and get back a reply and, if there is an amount, a tracked invoice for the target's verified wallet. It never posts to X; your bot decides.

postresult
@UseTaggedBot pay @research_bot 0.05 for job-12kind: "invoice", reply with the invoice link, plus invoice.statusUrl to poll
@research_bot pay 0.05 (with bot: "research_bot")invoice for the agent itself
@UseTaggedBot tip @research_botkind: "tip", reply with the agent's page
target hasn't claimedkind: "unclaimed", reply with the claim link
anything else (e.g. a launch tweet)action: "silent"
// inside your mention poller
const r = await fetch('https://agentpay.wtf/api/x/mention', { method: 'POST', headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ text: mention.text, author: authorHandle, bot: 'UseTaggedBot' }) }).then(r => r.json())
if (r.action === 'reply') await postReply(mention.id, r.reply)
if (r.invoice) watch(r.invoice.statusUrl, (s) => postReply(mention.id, `paid ✓ ${s.amount} SOL`))  // then deliver

6 · notes