verify_before_pay in ten minutes
You have a Paddock design-partner key and have been asked to put verify_before_pay in front of your agent’s payment path. This page is that, and nothing else. The general builder guide is the API reference; connecting over MCP is on its own page.
Your key
Your key goes in a header on every request:
X-Paddock-Key: pk_proagent_YOUR_KEY
No OAuth, no signing, no session. The key also works as an ?api_key= query parameter for browser testing, but use the header in production — query strings land in access logs, browser history and Referer headers.
401 invalid_key rather than degrading quietly. Ask at any time and we will confirm in writing what is live under your name.You do not need the key to try the tool. verify_before_pay is on the free tier, so a free pk_free_ key reaches it against a shared 10 calls a day. Your partner key exists so you are not rationed while you build.
Two calls, refusal first
route: true tells you the endpoint is reachable and nothing else. The failure mode that costs money is an integration that treats every response as a go — so the first thing worth seeing is this check saying no, and your code doing something about it.Ask with a price you know is wrong
Tell the check you expect to pay $99.00 for an endpoint that charges cents. The contract on the wire cannot match that, so the only correct answer is a refusal that names the field it disagreed on. This is the call that proves the check can say no — and that your code notices when it does.
curl -s -H "X-Paddock-Key: pk_proagent_YOUR_KEY" \
'https://paddock.finance/api/mcp/verify-before-pay?endpoint_url=https%3A%2F%2Fpaddock.finance%2Fapi%2Fpaddock%2Fmcp%2Fgaps&expect_network=eip155%3A8453&expect_price_usdc=99.00' \
| jq '{route, reason_codes, summary}'{
"route": false,
"reason_codes": [
"live_402_observed",
"contract_mismatch_price"
],
"summary": "Do not route: the live 402 contract does not match on price_usdc."
}If that comes back route: true, stop and tell us — something is wrong at our end, and it is exactly the kind of thing a design partner is for.
Ask the same question honestly
Drop the wrong expectation and keep the network. The same endpoint, the same key, one parameter different — and now the contract matches what you asked for. Compare the two responses side by side: the difference between them is the whole product.
curl -s -H "X-Paddock-Key: pk_proagent_YOUR_KEY" \
'https://paddock.finance/api/mcp/verify-before-pay?endpoint_url=https%3A%2F%2Fpaddock.finance%2Fapi%2Fpaddock%2Fmcp%2Fgaps&expect_network=eip155%3A8453' \
| jq '{route, reason_codes, schema_version, checked_at}'{
"route": true,
"reason_codes": [
"live_402_observed",
"contract_matches_expectations",
"settlement_recent",
"price_within_category_range"
],
"schema_version": "verify_before_pay/0.7",
"checked_at": "2026-08-28T…Z"
}The one rule that matters
route is three-valued: the JSON literal true, the JSON literal false, or the STRING "inconclusive".
// The only correct test:
if (result.route === true) { pay(); } else { doNotPay(); }
// NEVER this — "inconclusive" is a non-empty string, so it is truthy,
// and this pays exactly when we have told you we could not check.
if (result.route) { pay(); } // ✗"inconclusive" means we could not read a 402 from the endpoint at query time. Not that it failed — that we do not know. Everything else in that response is history, and history is not proof of what an endpoint will charge you now. Treat it as a no for payment purposes and a look-again for yours.
false is the opposite: it means we know something. A contract mismatch, a flagged circular cluster, or a 404/410 from the endpoint. A hard false outranks an unreadable probe — if the price is wrong, an unparseable response elsewhere never softens that into a shrug.
Since verify_before_pay/0.7, a call that states no expectations is "inconclusive" too. Every contract check compares the live 402 against something you told us — pay_to for the wallet, expect_network for the chain, expect_asset for the token, expect_price_usdc for the price. Send none of them and there was nothing to compare, so the verdict says so rather than passing you on a readable 402 alone. The reason code is no_expectations_supplied and the summary reads Nothing was compared. Pass pay_to or expect_* for a verdict; for liveness alone call get_liveness.
If you are running shadow mode, this is the one thing to check in your harness. A stored verdict at verify_before_pay/0.6 or older could be a true for a URL-only call; the same call today is "inconclusive", and nothing about the endpoint changed — we did. Verdicts for calls that did state an expectation are unaffected and stay comparable across the boundary. That is what the version bump is for.
What the call actually does
The pre-payment check no seller can self-report. Call it before your agent authorizes an x402 payment. It sends an unpaid request to the endpoint you name at the moment you call, decodes the 402, and checks network, asset, seller, and price against what you expected and against the seller's published config. It then adds three things only an independent observer can: whether the endpoint has settled real volume recently from wallets that are not its operator's, whether it sits in a circular-settlement cluster, and how its price compares to its category. The answer is `route: true`, `false`, or `"inconclusive"`, each with the evidence and its date. Liveness and contract are as of the call; settlement and circular signal are snapshot-dated, and each block says which. There is no success-rate field, on purpose: Paddock observes settlements, not failed calls, so it returns recency and frequency, which it can actually see. Test for `route === true`; inconclusive is the one case where you must not pay on our say-so. `route: true` scopes to one claim — the live contract matched what you expected — and is not an all-clear on every signal: stale settlement, a circular flag, a payTo divergence and a failed request pre-flight can each coexist with it, and each block says whether it moved the verdict, so read the evidence rather than `route` alone. A second, narrower verdict rides alongside it: `contract_and_request_ok` is `true` only when `route` is `true` and your own declared request passed pre-flight, `false` when either is bad, and `"not_assessed"` when a term was never established; `contract_and_request_ok_excludes` names the risk categories it is silent on, so it can never be read as a full clearance.
price_basis on every response says which basis was used, and where no settled price exists the check returns "unavailable" rather than falling back to the advertised figure.Shadow mode — evaluate without betting on it
Do not gate payments on us in week one. Run the call, log the verdict, pay as you would have anyway, and grade us later. Two fields exist for exactly this: schema_version, bumped only on a breaking shape change and never for an added field — store it with every verdict, because two verdicts are only comparable at the same major version — and checked_at, the instant the probe ran. Responses are never cached; the entire value of the answer is that it was taken then.
{ "schema_version": "...", "checked_at": "...", "route": "...",
"reason_codes": ["..."], "endpoint_url": "...", "pay_to": "...",
"your_outcome": "settled | failed | refunded | not_attempted" }Store reason_codes as an array, not a joined string — the vocabulary is append-only and you will want to group by individual codes later. After a few weeks you can answer the only question that matters: when we said false or "inconclusive", would paying have gone badly? Tell us what you find, including where we were wrong. Publishing our own bad news is a standing rule here, and a design partner’s disconfirming evidence is the most useful thing we can be handed.
What we deliberately do not tell you
There is no success rate, and there never will be one from this record. Paddock observes settlements, not attempted calls. A payment that was tried and failed leaves no row in our data, so a success rate computed from it could only ever be 100% — a number that would look like reassurance and mean nothing. You get recency and frequency instead: last_observed_settlement_date, observed_settlements_7d / _30d, and days_present_7d / _30d.
No settlement history is not a veto. A brand-new honest seller and a fake one look identical on day one. Refusing every unseen seller would make this an incumbency filter rather than a fraud check, so absence is reported loudly in reason_codes and kept away from the verdict. That call is yours, with your own risk appetite.
Outside coverage is said out loud. Our circular detector reads one facilitator on one chain. A wallet settling elsewhere gets "not_assessed" with a basis naming what was in scope — never a silent skip, and never a “clean” for something nobody looked at.
Support
[email protected] — for anything, including “your verdict was wrong and here is the case”. Include checked_at and the endpoint_url; the response is not cached, so those two are what let us reconstruct what we saw.
Full reference: paddock.finance/docs · OpenAPI · MCP setup