POST /v1/verify-claimSend a 1–500 character claim for an evidence-based verdict. The same verdict is available from the free claim API.
X402 / LIVE ON BASE MAINNET
Voidpay accepts x402 payments at x402.voidly.ai. A wallet-enabled agent can pay 0.01 USDC per claim on Base mainnet, with no Voidpay account or per-call human approval required by the resource. The example below adds an upfront operator opt-in and client limits.
CLAIM VERIFICATION PROOF · 0.01 USDC settled in Base block 52,226,978. View the transaction ↗ This proves that payment path, not customer demand or a mainnet paid call to the other resources.
BASE MAINNET / RESOURCE INDEX
The served x402 discovery lists these POST routes on eip155:8453. Prices are per request in USDC. Only the claim route has a published mainnet paid-call receipt.
POST /v1/verify-claimSend a 1–500 character claim for an evidence-based verdict. The same verdict is available from the free claim API.
POST /data/incidents/reportSend an incidentId for a bounded evidence audit linked to the free citable report. Paid test evidence is from Base Sepolia; no mainnet paid-call receipt is published for this route.
POST /v1/accessibility/checkSend domain and two-letter country. It compares stored corpus and in-country observations; it does not run a new probe. Paid test evidence is from Base Sepolia; no mainnet paid-call receipt is published for this route.
01 / SETUP
Run this only on your backend or development machine. Keep CDP credentials out of browser code, prompts, logs, and source control.
Use Node.js 22+ and a CDP API key and wallet secret. Set CDP_API_KEY_ID, CDP_API_KEY_SECRET, and CDP_WALLET_SECRET privately. Set BASE_MAINNET_USDC to the canonical Base USDC contract in Circle's contract list; it is an asset ID, not a payment destination. The client explicitly selects the CDP production environment.
npm install @coinbase/cdp-sdk@1.57.1 \
@x402/core@2.28.0 @x402/evm@2.28.0 @x402/svm@2.28.0 \
@x402/extensions@2.28.0 @x402/fetch@2.28.0Save the source below as voidpay-mainnet.mjs. Run node voidpay-mainnet.mjs --terms to print the chain, asset, amount, and payTo from an unpaid 402 response. Cross-check the destination against the recipient in the first transaction, then set VOIDPAY_MAINNET_PAY_TO to that full destination. Run node voidpay-mainnet.mjs --address to see the CDP paying address; this may provision a wallet but makes no payment. This is your CDP paying wallet, which must hold enough Base USDC for the call. Voidpay provides no deposit address or stored balance for this resource.
Confirm the resource URL, chain, USDC contract, 0.01 USDC price, 402 payment destination, and paying wallet. The client allows only that chain, asset, and payee. Set the opt-in flag when starting this example; an agent can then make its one bounded attempt without a per-call human approval step. The in-memory controls are not a durable wallet budget:
VOIDPAY_X402_AUTOPAY_ENABLED=yes node voidpay-mainnet.mjs "Is YouTube blocked in China?"02 / SOURCE
@x402/fetch handles the payment challenge and retry. Coinbase CDP supplies the wallet and client-side spend controls. The example checks an upfront operator flag and allows one attempt per process.
// voidpay-mainnet.mjs — Node.js 22+; real USDC on Base mainnet
import { pathToFileURL } from 'node:url';
import { CdpX402Client } from '@coinbase/cdp-sdk/x402';
import { wrapFetchWithPayment } from '@x402/fetch';
const RESOURCE = 'https://x402.voidly.ai/v1/verify-claim';
const BASE_USDC = process.env.BASE_MAINNET_USDC;
const PAY_TO = process.env.VOIDPAY_MAINNET_PAY_TO;
let attempted = false;
function newMainnetClient() {
if (!BASE_USDC || !/^0x[0-9a-fA-F]{40}$/.test(BASE_USDC)) {
throw new Error('Set BASE_MAINNET_USDC to the canonical Base mainnet USDC contract');
}
if (!PAY_TO || !/^0x[0-9a-fA-F]{40}$/.test(PAY_TO)) {
throw new Error('Set VOIDPAY_MAINNET_PAY_TO from the reviewed 402 terms');
}
return new CdpX402Client({
environment: 'production',
spendControls: {
maxAmountPerPayment: { atomic: 10_000n, asset: BASE_USDC },
maxCumulativeSpend: { atomic: 10_000n, asset: BASE_USDC },
maxCumulativeSpendWindow: '24h',
allowedNetworks: ['eip155:8453'],
allowedAssets: [BASE_USDC],
allowedPayees: [PAY_TO],
},
});
}
export async function paymentTerms() {
const response = await globalThis.fetch(RESOURCE, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ claim: 'A censorship claim to check' }),
});
if (response.status !== 402) throw new Error('Expected an unpaid 402 response');
const encoded = response.headers.get('payment-required');
if (!encoded) throw new Error('Missing PAYMENT-REQUIRED header');
const challenge = JSON.parse(Buffer.from(encoded, 'base64').toString('utf8'));
if (!Array.isArray(challenge.accepts)) throw new Error('Missing payment terms');
return challenge.accepts.map(({ network, asset, amount, payTo }) => ({ network, asset, amount, payTo }));
}
export async function payingAddress() {
const { evmAddress } = await newMainnetClient().getAddresses();
return evmAddress;
}
export async function verifyClaim(claim) {
const cleanClaim = String(claim).trim();
if (!cleanClaim || [...cleanClaim].length > 500) {
throw new Error('Claim must be 1–500 characters');
}
if (process.env.VOIDPAY_X402_AUTOPAY_ENABLED !== 'yes') {
throw new Error('Operator must enable this example before an agent can pay');
}
if (attempted) throw new Error('This process already attempted one mainnet payment');
const client = newMainnetClient();
attempted = true; // Keep the latch even if the settlement outcome is unclear.
const paidFetch = wrapFetchWithPayment(globalThis.fetch, client);
const response = await paidFetch(RESOURCE, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ claim: cleanClaim }),
});
if (!response.ok) throw new Error('Gateway HTTP ' + response.status);
return response.json();
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
if (process.argv[2] === '--terms') {
console.log(JSON.stringify(await paymentTerms(), null, 2)); // Unpaid 402 only.
} else if (process.argv[2] === '--address') {
console.log(await payingAddress()); // May provision a CDP wallet; does not pay.
} else {
console.log(JSON.stringify(await verifyClaim(process.argv.slice(2).join(' ')), null, 2));
}
}This example calls claim verification only. The gateway accepts a JSON claim from 1 to 500 characters. Its paid route wraps the same verdict returned by the free claim-verification API; no extra computation is established here. The terms command makes an unpaid request, and the address command may provision a CDP wallet; neither can make a paid request.
03 / AGENT FRAMEWORKS
The paid request stays in your owner-controlled runtime. A model should receive only the claim input and the result, never your CDP secrets.
Install langchain@1.5.15, @langchain/core@1.2.14, and zod@3.25.76. Add this custom tool to your agent's tools array. It inherits the upfront opt-in and mainnet limits from verifyClaim.
// voidpay-tool.mjs — use with the enabled client above
import { tool } from 'langchain';
import * as z from 'zod';
import { verifyClaim } from './voidpay-mainnet.mjs';
export const verifyClaimTool = tool(
async ({ claim }) => JSON.stringify(await verifyClaim(claim)),
{
name: 'voidpay_verify_claim_mainnet',
description: 'Check one claim on Base mainnet. Costs 0.01 USDC; requires upfront operator opt-in.',
schema: z.object({ claim: z.string().trim().min(1).max(500) }),
},
);
// Add verifyClaimTool to your LangChain agent's tools array.AgentKit exposes x402 actions, but published x402ActionProvider() has no URL allowlist or amount cap option. Its automatic action can pay without confirmation. Register this custom action around verifyClaim instead; the same operator opt-in and one-attempt latch apply. Install @coinbase/agentkit@0.10.4 with zod@3.25.76 for this adapter.
// voidpay-action.mjs — custom AgentKit action, not its stock x402 action
import agentkit from '@coinbase/agentkit';
import { z } from 'zod';
import { verifyClaim } from './voidpay-mainnet.mjs';
const { customActionProvider } = agentkit;
export const voidpayMainnetProvider = customActionProvider({
name: 'voidpay_verify_claim_mainnet',
description: 'Base mainnet claim check; 0.01 USDC, with upfront operator opt-in.',
schema: z.object({ claim: z.string().trim().min(1).max(500) }),
invoke: async ({ claim }) => JSON.stringify(await verifyClaim(claim)),
});
// Add only voidpayMainnetProvider to your AgentKit actionProviders array.Add the provider to your existing AgentKit configuration. Do not also expose the stock automatic x402 payment action.
Published AgentKit 0.10.4 ↗04 / BOUNDARIES
Review the fixed URL, paying wallet, network, real USDC contract, payment destination, and cap before running. The terms command prints an unpaid challenge and the paid example prints the protected response; neither displays an onchain receipt. Inspect settlement separately before calling it verified.
The example fixes Base mainnet and the canonical USDC asset. Its client controls limit one payment to 0.01 USDC and one attempt per process. Those in-memory controls are not a durable wallet budget.
The resource does not require a human approver. This example adds an upfront operator flag and one-attempt latch. An uncertain result still consumes the attempt; a restart resets the process-level limits.
This POST is a paid claim-verification resource at x402.voidly.ai. It is not the hosted Voidpay MCP checkout-link tool or the historical api.voidly.ai/v1/pay session rail.
NEXT STEP
Review the 402 terms, wallet, and client limits, then run the source example from your own backend.