# PickleCheck — payment checking for AI agents START HERE (all three checks): (1) Use it free: add the MCP address https://picklecheck-test.onrender.com/mcp in ChatGPT, Claude or Cursor (authentication: none), or call the web address; every day you get free calls: 3 scam checks, 1 payment check, 1 endpoint check. (2) When they run out, pay a few cents in USDC on Base: on a phone open https://picklecheck-test.onrender.com/pay in the MetaMask app, pick the check and sign; a bot or app with an x402 wallet pays by itself (how: https://picklecheck-test.onrender.com/docs#how-to-pay). Verify that a payment really landed: send its tx hash, get on-chain facts. Chains: base, ethereum, arbitrum, solana, bitcoin. POST https://picklecheck-test.onrender.com/verify-payment {"tx_hash":"","receiver":"","token":"USDC","expected_amount":"2"} { "schema_version": 2, "status": "confirmed", "verified": true, "chain": "ethereum", "detected_chain": true, "paid": "2", "paid_raw": "2000000", "expected_raw": "2000000", "amount_match": "exact", "confirmations": 40, "required_confirmations": 12, "asset": { "canonical": true, "symbol": "USDC", "decimals": 6 }, "disclaimer": "On-chain facts only; not a guarantee." } Free each day per IP (UTC), counted per route: 3 scam checks, 1 payment check, 1 endpoint check. Then $0.003 per payment check, USDC on Base, via x402. No signup, no API key. HTTP 402 with accepts[]: pay one option and retry with the payment header. pending / unknown → HTTP 503 + Retry-After; not_found → HTTP 404 + Retry-After; none are charged, and a free payment check that gets one of these is not used up. Not financial advice. DYOR. ## POST /verify-payment Required: tx_hash, receiver, expected_amount (decimal STRING, human units). Optional: chain (omit to auto-detect from the hash/receiver format; reply sets chain + detected_chain:true), token (symbol or address/mint; default USDC on EVM, SOL, BTC), exact (default paid >= expected), not_before (ISO), expected_from, min_confirmations (raise only). Aliases accepted: network=chain, amount=expected_amount, to|recipient|payTo=receiver, asset=token, txid|signature|hash=tx_hash, from|sender=expected_from. status → verified → HTTP: confirmed → true → 200 | mismatch, failed → false → 200 | pending, unknown → null → 503 + Retry-After | not_found → null → 404 + Retry-After (not charged) | invalid_request → 400. Reply (schema_version 2): status, verified, chain, paid/paid_raw/expected_raw (strings), amount_match (exact|over|short), confirmations, required_confirmations, finality, asset {canonical, symbol, address, decimals}, block_hash, observed_at, reason_code?, caveat?, sanctioned_wallets + sanctions_unchecked (base only: sender or receiver on the US sanctions list (OFAC); a wallet in sanctions_unchecked could not be read this time: unknown, not clear, retry), usdc_blacklisted_wallets + usdc_blacklist_unchecked (base USDC only: sender or receiver blocked by Circle, the USDC issuer, so a transfer to or from it fails; same unknown-is-not-clear rule), known_scam_wallets (sender or receiver we recorded deploying coins we proved to be scams), cached? + cached_at? (a repeat answer from our 5-min cache; observed_at is when it was read) reason_code (mismatch/failed): no_transfer_to_receiver | receiver_mismatch (this token went to another address) | token_mismatch (receiver got a different token) | short | not_exact | unexpected_sender | older_than_not_before | reverted | tx_failed. finality: pending | confirming | confirmed | finalized (Solana). Solana confirmations = RPC count, null once finalized; required_confirmations null (finalized is the rule). invalid_request: error (all problems, '; '-joined), fields[], errors[{field, error}]; error unknown_token = token not in our registry for that chain (e.g. USDC on bitcoin). Lookalike tokens: symbols come only from our registry of official contracts; anything else is asset.canonical=false, symbol=null. Confirmations (defaults, measured over time): base 3, arbitrum 3, ethereum 12, solana finalized, bitcoin 3 (never 0-conf; rbf flagged while unconfirmed, false once mined). Matching a payment to an order (unique amount, memo, not_before) is the caller's job. ## Rules for bots (all PickleCheck endpoints, now and later) 1. Branch on machine fields: /verify-payment → status/verified; /scam-check → action. Text is for humans. 2. null = unknown, never safe. 3. HTTP 503 + Retry-After = retry later, not charged. HTTP 400 = fix the request. 4. Ignore unknown fields; new fields and codes are added without breaking existing ones. Codes: GET /codes 5. x402: v2 (PAYMENT-REQUIRED / PAYMENT-SIGNATURE) or v1 (X-PAYMENT). Pay to: base-sepolia 0x350d2922ad59fadf029fdddec0dac181be91a306. Header X-Free-Calls-Remaining shows the free calls left today on that route. ## GET /endpoint-check: is it safe to pay this x402 endpoint? $0.005 per check (1 free check a day per IP) GET https://picklecheck-test.onrender.com/endpoint-check?url=https://picklecheck.onrender.com/scam-check This example checks our own scam-check address and works as written: replace the url with the https address of the x402 endpoint YOU want checked (it must be live; an address that does not answer gets 503 and is never charged). We make one unpaid request (we never pay), read what it asks you to pay and to whom, and compare with what we saw before. Actions: AVOID (its Base pay-to wallet is on the US sanctions list (OFAC), is blocked by Circle for USDC, or belongs to a wallet we recorded deploying coins we proved to be scams; or its pay-to changed together with another warning sign (a price jump, a redirect, a changed asset or network) and the new terms are still in place at a later look: flag pay_to_change_persisted; or it moved repeatedly) | CAUTION (new or little history, changed terms, a changed pay-to (confirm the new address with the provider; a change that comes with another warning sign shows pay_to_change_recent until a later look settles it), redirect, not payable, unknown asset, the payment is for another site (resource_elsewhere) or points to plain http (resource_insecure), the public x402 directory lists a different pay-to wallet than the one it asks for (payto_differs_from_listing), or its pay-to wallet deployed a coin we proved to be a scam (payto_known_scam_wallet)) | VERIFY_MORE (could not reach it, or could not read the sanctions list for its pay-to; HTTP 503, not charged) | NO_FLAGS, label "No major flags found" (same terms on every look over 24+ hours; never OK, because we do not pay the endpoint and cannot vouch for what it delivers). Bad input is HTTP 422, not charged. Reply: action, reasons, flags, accepts (pay_to, asset, network, amount, resource_host), pay_to_history (Base pay-to: USDC paid in over the last 30 days: days_read, complete, payments_in, usdc_in, payers, and hosts_seen = how many of the endpoints we watch ask to be paid at that wallet; {status:'reading', hosts_seen} until the first read lands; no USDC in 30 fully read days is CAUTION, flag payto_no_income_30d, only when Base is the endpoint's only payment option), history (probes, hours_watched, first_seen, last_change). We keep only host + path and the payment terms, never a query string or a response body. A first sighting is never OK. ## GET /scam-check: is this Base token safe to buy? Risk check, $0.008 per check (3 free checks a day per IP) GET https://picklecheck-test.onrender.com/scam-check?target=TOKEN_ADDRESS Actions: AVOID | CAUTION | VERIFY_MORE (not rated) | OK. risk: Lower Risk | Medium Risk | Higher Risk (null on VERIFY_MORE). flags (short codes of what moves the action), info_flags (zero-point facts that do not move it; a code can be a flag on one coin and an info flag on another), fields (below), summary.text, check_id (new for every answer), cached_from (only on an answer served from our 45-second cache: the check_id of the first answer). Scoring: PickleCheck Methodology (not published). Params: target (Base 0x address or URL), quick=1 (~5s, fewer facts; skipped facts are null; adds fields.full_check_url), amount_usd (price impact at your trade size, and fields.sell_at_size: a sell quote at that size from the exchange quoters), compact=1 (no summary text, no disclaimer, no null fields: a missing key means unknown, same as null; about 30-45% smaller). Input errors: action VERIFY_MORE, error PC-ERR-NOT-BASE | PC-ERR-OTHER-CHAIN (a contract exists on Ethereum/Arbitrum, not Base) | PC-ERR-WALLET | PC-ERR-NOT-TOKEN | PC-ERR-INVALID-INPUT | PC-ERR-SOURCE-DOWN. Tokens: Base only (a token on another chain gets an error, never a verdict). A token with no DEX pool we can find (pool_type none: it may still be on a bonding curve) is CAUTION, never a pass: we could not test-sell it. A wallet address (not a token) is checked against the lists we hold: AVOID if it is on the US sanctions list (OFAC), is blocked by Circle for USDC (usdc_blacklisted), or is a wallet we recorded deploying coins we proved to be scams (known_scam_wallet; CAUTION when a proven scam is one of several coins it deployed). Any other wallet gets PC-ERR-WALLET with fields.sanctions and fields.usdc_blacklisted (null = could not read: unknown, not clear). fields (flat, api_version 2.0; null = unknown; reason = short code for the main cause of the verdict, null when nothing decided it; missing = core facts not known (null when none), a bot can retry later; a decided reply may be reused for up to 45 s, see checked_at): action, risk, why (AVOID and CAUTION: the reason in plain words, same as summary line 2; a new coin's CAUTION: its age and what is not known yet; an older coin read in full except holders: CAUTION reason holders_not_known; we read its holder history in the background, so a later check usually has them), coverage (complete | partial | incomplete: partial = decided but some facts are not known yet, see not_yet_known; incomplete = retry, never read it as a pass), final_answer (true: act on this reply, a retry will not change it; HTTP 200 even when data_complete is false), not_yet_known (plain list, e.g. holders, liquidity size), data_complete, background_finish (true: incomplete answer, the rest is being read in the background: ask again after recheck_after_s), mode (full|quick), age_hours, can_sell, buy_tax_pct, sell_tax_pct, tax_source (own_sell_test|outside_scanner) liquidity_usd, liquidity_eth (value of the ETH/WETH-paired pools in ETH, from on-chain reserves; filled even when liquidity_usd is null), lock_pct, unlock_at, lp_pullable, pool_supply_pct, pool_type, top1_pct, top10_pct, top1_type, deployer_pct, insider_pct, all_supply_in_pool owner_renounced, can_mint, upgradeable, verified_source, volume_24h_usd, price_impact_1k_usd, trade_size_usd, price_impact_at_size_pct drain_evidence + liquidity_before_usd + liquidity_before_day (on a drained pool: why we call it pulled, e.g. we saw $48K here on 2026-10-21, or the chain shows the pool was funded) tax_mutable (true = the fees can change, false = we found no way to change them (not a guarantee), null = not checked), checked_block + result_valid_until_block (the sell test is a one-block fact: valid only for checked_block when tax_mutable is true or null, about an hour later when false), code_traps (short tags for risky patterns in the contract code; [] = none found, null = not checked) sell_sizes (live sell quotes at $1, $10, $100, $1K, $10K: [{usd, back_usd, return_pct, result}], result ok (at least half came back) | hook_blocked (the pool's v4 hook refused this sell: not a thin pool, do not assume a bigger sell works) | too_big_for_pool (a smaller sell went through: the pool is thin, selling works) | no_liquidity (the exchange's own quoter says the pool holds nothing to pay out, e.g. a launch nobody has bought yet) | no_quote (nothing smaller went through); liquidity is never set from $1/$10 sells) sell_at_size (only when amount_usd is sent, not with quick=1: the same row as sell_sizes for your own trade size, from the exchange quoters; null = no size asked or no quote; it never changes the verdict), pool_id (address of the pool that pool_type names; a Uniswap v4 pool: its 32-byte pool id; null with quick=1) previous_action (our last action for this token when it differs), changed (what moved since our last check: [{field, from, to, change: better|worse}] for action, sell_tax_pct, buy_tax_pct, can_sell, lock_pct, liquidity_usd, top10_pct; a change alone never makes AVOID), checked_at, recheck_after_s, full_check_url (quick mode) Feedback on scam checks: POST https://picklecheck-test.onrender.com/feedback {"check_id","rating":"correct|wrong|unsure"} Free: /health /codes /llms.txt /openapi.json /.well-known/x402 /docs (prices, examples, how to pay) /pay (pay a check from a phone with MetaMask) ## MCP POST https://picklecheck-test.onrender.com/mcp is a hosted MCP server (Streamable HTTP, JSON-RPC 2.0, no sessions, no signup). Tools: scam_check, endpoint_check, verify_payment: the same checks, prices and free calls per route as the HTTP routes (counted per IP across both). A call that needs payment returns isError true with a PaymentRequired object (x402 v2): pay it and retry the same call with params._meta["x402/payment"]; the receipt comes back in _meta["x402/payment-response"]. No x402 wallet in your MCP client? The same result says how to pay the same call over its HTTP route with any x402 client. Not charged: the same cases as on HTTP. Telegram: https://t.me/picklecheck_bot X: https://x.com/picklecheck Not financial advice. DYOR.