Integration guide
How to connect your agents to Lumière PayCheck. All examples use https://lumierepaycheck.org. The complete API reference and runnable examples (TypeScript and Python) are on GitHub.
The flow
- Your agent calls a paid API and gets
402 Payment Requiredwith a price, a payout wallet (payTo), and a network. - Before paying, it asks PayCheck:
POST /v1/authorize. - On
"allow", it pays (optionally attaching our receipt so your wallet can verify it); on"deny", it doesn't; on"review", it waits for a person on your team.
Authorize a payment
curl https://lumierepaycheck.org/v1/authorize \
-H "Authorization: Bearer $PAYCHECK_AGENT_KEY" -H "content-type: application/json" \
-d '{"url":"https://api.seller.example/data","amount":"10000","payTo":"0xSELLER_WALLET","network":"eip155:8453"}'amount is in the token's smallest unit (USDC: 1000000 = $1). The response:
{
"decision": "dec_3f1c...",
"outcome": "allow",
"reasons": ["all rules passed"],
"receipt": "eyJ2IjoxLCJraWQiOi...signature",
"verdict": "proceed",
"observed": { "payTo": "0xSELLER_WALLET", "amount": "10000", "network": "eip155:8453" }
}| Outcome | What to do |
|---|---|
allow | Pay exactly this amount to exactly this wallet. The receipt is valid for 5 minutes. |
deny | Don't pay. reasons lists every rule that failed. |
review | Don't pay yet. Poll GET /v1/decisions/{decision} until it becomes allow or deny (expires after 24 hours). |
In TypeScript:
async function mayPay(url: string, amount: string, payTo: string, network: string) {
const r = await fetch("https://lumierepaycheck.org/v1/authorize", {
method: "POST",
headers: { authorization: `Bearer ${process.env.PAYCHECK_AGENT_KEY}`, "content-type": "application/json" },
body: JSON.stringify({ url, amount, payTo, network }),
});
if (!r.ok) return { outcome: "deny", reasons: [`PayCheck returned ${r.status}`] }; // fail closed
return r.json();
}Fail closed: if PayCheck can't be reached or returns an error, don't pay. That's the safe default.
Verify receipts in your wallet
Receipts are payload.signature, both base64url; the signature is Ed25519 over the payload text. Our public key is at /.well-known/paycheck-receipt-key.json (a JWK). A wallet that requires a valid receipt matching the exact URL, amount, and payout wallet can't be tricked into paying anything PayCheck didn't approve. Check exp (5 minutes), and refuse receipts with "test": true in production. Ready-to-use verifiers in TypeScript and Python are in the GitHub examples.
Use the SDK (recommended)
Our npm package wraps the standard @x402/fetch client so every payment asks PayCheck first and only approved payments are signed:
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { withPayCheck } from "x402-paycheck";
const client = withPayCheck(new x402Client().register("eip155:8453", new ExactEvmScheme(signer)),
{ agentKey: process.env.PAYCHECK_AGENT_KEY, reviewWaitMs: 60_000 });
const fetchWithPay = wrapFetchWithPayment(fetch, client);- A denied payment throws before anything is signed; a review waits up to
reviewWaitMsfor a person. - If PayCheck can't be reached it fails closed, except payments under the no-check amount your workspace sets (Policies → "If PayCheck can't be reached").
- Agents running it show Enforced by SDK on the account page.
- Wallet code that checks receipts itself can call
consumeReceipt(receipt): a receipt approves exactly one payment.
Register your agents' wallets
So you also see payments made without asking PayCheck: on the account page open the agent → Wallets, enter the wallet address, and sign the message shown (free, moves no funds). Or by API:
curl -X POST https://lumierepaycheck.org/v1/agents/ag_123/wallets -H "Authorization: Bearer $OWNER_KEY" \
-H "content-type: application/json" -d '{"network":"base","address":"0xAGENT_WALLET"}'
# sign the returned "challenge" with personal_sign, then:
curl -X POST https://lumierepaycheck.org/v1/agents/ag_123/wallets/verify -H "Authorization: Bearer $OWNER_KEY" \
-H "content-type: application/json" -d '{"address":"0xAGENT_WALLET","signature":"0x..."}'From then on every x402 payment from that wallet is matched to your decisions. Payments that weren't approved send a payment.outside_paycheck event to your alert webhook and security stream (see [webhook events](#webhook-events)).
Agent credentials
If your Owner turned on agent credentials (Identity tab), an agent can prove to a seller that it belongs to your verified company. It asks for a credential for that seller and sends it as the X-PayCheck-Agent header:
curl -X POST https://lumierepaycheck.org/v1/agents/credential -H "Authorization: Bearer $PAYCHECK_AGENT_KEY" \
-H "content-type: application/json" -d '{"audience":"api.seller.example"}'With the SDK: wrapFetchWithPayCheck(fetch, client, wrapFetchWithPayment, { agentKey, agentCredential: true }) adds the header to every paid request. Credentials last 15 minutes and only work with the agent's registered wallet. Sellers: see Accept PayCheck-verified agents.
Ask for a higher limit for a while
Business and Enterprise. An agent that needs more for a batch job asks; an Approver decides; the limit drops back by itself:
curl -X POST https://lumierepaycheck.org/v1/agents/me/limit-increase -H "Authorization: Bearer $PAYCHECK_AGENT_KEY" \
-H "content-type: application/json" -d '{"dailyLimit":"25000000","hours":6,"reason":"month-end price refresh"}'
# 202 pending; poll GET /v1/agents/me for limits.dailyLimit and limitIncrease.statusAgents using MCP with their agent key get request_limit_increase, get_agent_credential and my_payment_compliance tools.
Key-less sign-in
Instead of storing an agent key, an agent can send a short-lived token from the platform it runs on. Link the workload once (account page → open the agent → Sign-in, or POST /v1/agents/{id}/identities), then send the platform token as the bearer token to /v1/authorize. The token's audience must be https://lumierepaycheck.org.
GitHub Actions
The job needs permissions: id-token: write. Link with platform GitHub Actions and subject repo:ORG/REPO:ref:refs/heads/main (or repo:ORG/REPO:environment:production).
- name: Authorize payment (no stored key)
run: |
TOKEN=$(curl -sS -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://lumierepaycheck.org" | jq -r .value)
curl -sS https://lumierepaycheck.org/v1/authorize -H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" -d '{"url":"...","amount":"10000","payTo":"0x..."}'Google Cloud
Cloud Run, GKE, and Compute Engine can fetch an identity token from the metadata server. Link with platform Google Cloud and the service account's unique ID (a long number) as the subject.
TOKEN=$(curl -sS -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://lumierepaycheck.org")Microsoft Azure
Managed identities receive tokens for an application registered in your tenant. Register an application, set its Application ID URI, request tokens for that resource, and link with platform Microsoft Azure, your tenant ID, the managed identity's object ID as the subject, and that Application ID URI as the audience (set via the API).
Kubernetes and others
Use platform Other with your cluster's OIDC issuer URL (for example, an EKS cluster's issuer), and the service account as the subject: system:serviceaccount:NAMESPACE:NAME. Mount a projected service account token with audience https://lumierepaycheck.org. The issuer's discovery document and keys must be reachable over HTTPS from the internet.
Tokens must be signed (RS, PS, or ES algorithms), unexpired, and valid for at most 24 hours. We record which workload made each request, never the token.
Paying us over x402
Our paid routes (full endpoint reports, batch lookups, bulk catalog data, watches, and Builder or Business plans in USDC) answer 402 Payment Required with two options: USDC on Base and USDC on Solana, at the same price. Any x402 client picks the network its wallet supports; on Solana, the facilitator pays the transaction fee, so your wallet only needs USDC.
Bulk data: the whole catalog
Need grades for every endpoint we watch (for research, a directory, or your own routing)? Don't crawl the endpoint pages: they're rate limited. Use the bulk route instead, 1,000 endpoints per paid call, sorted by URL:
curl "https://lumierepaycheck.org/v1/catalog/info" # free: total, page size, page count, price
curl "https://lumierepaycheck.org/v1/catalog?page=1" # 402: pay $0.10 over x402, then the same call returns the pageEach row has the URL, score, grade, verdict, paid-delivery status and rate, uptime, median latency, number of checks, unexplained payout-wallet changes and price increases. A bad page number or a warm-up after a restart answers with an error and you're not charged.
Check any endpoint for free
No account needed, 60 requests per minute:
curl "https://lumierepaycheck.org/v1/score?url=https://api.seller.example/data"
curl -X POST https://lumierepaycheck.org/v1/check-payment -H "content-type: application/json" \
-d '{"url":"https://api.seller.example/data","amount":"10000","payTo":"0xSELLER_WALLET","network":"eip155:8453","asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"}'Send every field from the quote. Each one blocks a known way to make an agent overpay:
| Field | Denied when |
|---|---|
amount | it's above the price we've seen this endpoint charge (small drift allowed) |
payTo | it isn't the payout wallet we've observed (possible hijack) |
network | it's a network this endpoint has never quoted |
asset | it's a different token than this endpoint charges (asset swap) |
New endpoints that imitate an established seller (a name one character off, lookalike characters, or a copied listing) and pay a different wallet are flagged as lookalike; check-payment asks for review unless you send "rules": { "allowLookalike": true }. Score answers also say whether a browser-based agent can pay the endpoint (browser) and which x402 version it speaks (x402Version).
The re-test window. A first failed paid test doesn't change a grade: we re-test about 2 hours later, and only a second failure counts (over 30 days, 94% of first failures were confirmed and 6% delivered on the re-test). While that re-test is pending, GET /v1/score, check_endpoint and POST /v1/check-payment return a retest field (firstFailureAt, retestDueBy, moneyTaken, reason), check-payment adds a note, and the endpoint's page shows a banner. To hold payments until it settles, send "rules": { "pauseDuringRetest": true }.
Every answer says why the endpoint has its grade:
headline: the main reason in one sentence, e.g. *"Avoid: our latest paid test failed (HTTP 200): none of the example's fields (data) in the response. The seller kept the payment."*reasons: every factor (paid tests with their date and what came back, uptime, speed, payout wallet, price, listing), each with its effect and any limit it puts on the grade.points: the points behind the score.decidedBy: which rule set the final score.
check-payment adds the same as gradeHeadline and gradeReasons.
Use it from AI assistants (MCP)
Add the MCP server to Claude Desktop, Cursor, or any MCP client:
{ "mcpServers": { "lumiere-paycheck": { "type": "http", "url": "https://lumierepaycheck.org/mcp" } } }Tools: check_endpoint, check_payment, top_endpoints, catalog_stats, list_sellers, seller_endpoints, paid_test_receipts, reliability_report, get_full_report, and report_outcome.
Verifying webhooks
Alerts, review notifications, and decision streaming are signed with your workspace's secret (admins see it on the account page). The header is x-paycheck-signature: sha256=<hex>, an HMAC-SHA256 of the raw request body:
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyPayCheckWebhook(rawBody: string, header: string | undefined, secret: string) {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
return !!header && header.length === expected.length && timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}Webhook events
type | Sent when |
|---|---|
decision | Every decision (security stream only), with its chain hash |
budget_alert · spike_alert | A cap or limit threshold is crossed; an agent spends far more than usual |
payment.outside_paycheck | A registered wallet paid without an approval: status is not_checked, paid_after_deny or paid_more, with agent, wallet, tx, seller, amount, frozen |
agent.high_risk | An agent's risk turns High (once a day per agent), with reasons |
limit_increase.requested | An agent asks for a temporary limit increase |
Sellers that need their own sign-in
Some x402 endpoints also need the seller's own account, API key or wallet signature. When our paid test is refused for that reason (HTTP 401/403, no money taken), checks return authRequired: true with a note, and the grade shows delivery as not confirmed (C, caution). If your agent has access, send "hasAccess": true with POST /v1/check-payment or POST /v1/authorize (or the check_payment MCP tool): strict rules like allowCaution: false and requireVerified then won't block it. On team plans, an agent whose allowed sellers include that endpoint's host counts as having access.
Report outcomes (optional)
After paying, tell us whether you got what you paid for. It's free, optional, and open to every agent:
- With a receipt (paid and trial plans):
POST /v1/reportwith{ "receipt": "...", "answers": { ... } }. - Without one (anyone, including free MCP users):
POST /v1/reportwith{ "url": "<endpoint>", "tx": "<payment transaction hash or Solana signature>", "answers": { ... } }. We find the payment on-chain (Base or Solana, within 24 hours) before counting it.
With the npm package it's one line after the paid call: await reportOutcome({ url, response, answers }) from x402-paycheck (0.2.0+) reads the payment's transaction from the paid response for you.
The answers: gotResponse (true/false), matchedListing (yes/partly/no), charged (as_quoted/more/twice), dataUsable (yes/unsure/no), wouldPayAgain (true/false). The MCP tool is report_outcome.
What reports do: confirmations from independent buyers move the endpoint to the front of our paid-test queue; problem reports from several buyers, or one serious report (nothing came back, charged more or twice) from an established buyer or proven on-chain, trigger a re-test right away. Reports never change a grade on their own: only our own paid test does. One report per payment; the seller's own wallet can't report on itself; fresh wallets count half.
Workspace owners can turn receipt reporting off on the account page.
Errors
| Status | Meaning |
|---|---|
401 | Missing or invalid key or identity token (detail says which check failed) |
402 | The workspace's plan has expired |
403 | Your role doesn't allow this action |
429 | Rate limit reached (per agent, set by your plan) |