One request, three gates, one human answer.
- Submit
intent,claim, and the requester wallet. - If gate 1 requests payment, read the terms in
extensions["service-windows"], choose USDC or EURC fromaccepts, sign the admission, and repeat the same action. - Store
verify_id. Prefercallback_urlfor ready or failed events. - If no callback is available, or delivery fails, poll while status is
processing, waiting at leastretry_after_seconds. - If status is
failed, stop and check the entry credit. - If status is
ready, checkservice_window, then call the separate unlock action. - The unlock requests exactly one payment: the SLA or grace price, in the asset of the admission. Sign it and repeat the unlock.
- Use the answer only when status is
completed.
get_verify or GET /verify/{verify_id}. A request can wait up to 24 hours for its human, so do not impose a short fixed client deadline.Three gates, two windows.
Prices are set in euros and paid on Base mainnet in USDC or EURC. The first 402 carries every price of the chain, so the agent knows the full cost before it pays anything.
| Gate | When | Price (EUR basis) |
|---|---|---|
| 1. Admission | POST /verify | 0.10 |
| 2. SLA unlock | The human answered within 60 minutes of admission | 2.90 |
| 3. Grace unlock | The human answered later, within 24 hours of admission | 1.45 |
The request enters the human queue only after the admission settlement has been recorded. Both windows start at that moment, admitted_at. The unlock price is fixed by the time of the answer alone, ready_at. A boundary instant belongs to the earlier, higher-priced window. When the agent polls or unlocks does not change the price.
The asset of gate 1 binds the chain: the unlock is paid in the same asset on the same network. EURC amounts are the euro prices. USDC amounts are the euro prices converted at the ECB euro reference rate and rounded up to the next cent at the moment of the quote. The conversion is disclosed in the terms and never changes after admission. With 1 EUR = 1.17 USD, a USDC chain costs 0.12, then 3.40 or 1.70.
There is no cancellation. Paying the admission is a commitment: a ready result unlocks at the SLA price or at the grace price. A ready result stays unlockable at its fixed price. If no human answers within 24 hours, the chain fails and the wallet's next admission is free.
The terms in the first 402
POST /verify without a payment answers 402. The PAYMENT-REQUIRED header carries one exact option per asset, USDC first and EURC second, and the terms in the service-windows extension:
{
"x402Version": 2,
"accepts": [
{ "scheme": "exact", "network": "eip155:8453",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "amount": "120000",
"extra": { "name": "USD Coin", "version": "2", "terms_id": "st_..." } },
{ "scheme": "exact", "network": "eip155:8453",
"asset": "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42", "amount": "100000",
"extra": { "name": "EURC", "version": "2", "terms_id": "st_..." } }
],
"extensions": {
"service-windows": {
"info": {
"version": 1,
"terms_id": "st_...",
"terms_valid_until": "2026-10-01T09:10:00.000Z",
"windows": { "sla": { "within_seconds": 3600 }, "grace": { "within_seconds": 86400 } },
"price_basis": { "currency": "EUR", "admission": "0.10", "sla": "2.90", "grace": "1.45" },
"prices": [
{ "network": "eip155:8453", "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"admission": "120000", "sla": "3400000", "grace": "1700000",
"conversion": { "from": "EUR", "rate": "1.17", "source": "ECB euro reference rate",
"as_of": "2026-09-30", "rounding": "up_to_cent" } },
{ "network": "eip155:8453", "asset": "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42",
"admission": "100000", "sla": "2900000", "grace": "1450000" }
],
"asset_binding": "admission_asset",
"expiry": { "admission_credit": "next_admission" },
"cancellation": "none",
"status_url_template": "https://verifi.cloud/verify/{work_id}",
"unlock_url_template": "https://verifi.cloud/verify-unlock?id={work_id}"
}
}
}
}
Amounts are strings in atomic units, 6 decimals for both assets. A quote can be paid until terms_valid_until, about ten minutes. A payment signed against an expired quote is not settled: the answer is a fresh 402 with fresh terms, and the agent signs again.
Agents work through MCP.
Connect to https://verifi.cloud/mcp. The same tools handle every paid review, and discovery plus the price check cost nothing.
verify_claimSubmit the request, optionally register a callback, and pass gate 1.get_verifyPoll the request without payment.unlock_verifyPay the SLA or grace unlock and receive the answer.verifi_infoRead the service rules and the current terms.For a paid gate, the tool returns the standard x402 requirement understood by x402-aware MCP clients, with the terms in extensions:
{
"x402Version": 2,
"accepts": [
{ "scheme": "exact", "amount": "120000", "network": "eip155:8453", "extra": { "name": "USD Coin" } },
{ "scheme": "exact", "amount": "100000", "network": "eip155:8453", "extra": { "name": "EURC" } }
],
"extensions": { "service-windows": { "info": { "terms_id": "st_..." } } },
"resource": { "url": "mcp://tool/verify_claim" }
}
The MCP payment client signs the selected requirement with the requester wallet and repeats the same tool call using x402/payment metadata. A successful call returns x402/payment-response with the settlement receipt. Generic MCP clients can use the optional payment_signature argument instead.
isError: true, in both structuredContent and content[0].text. It means the tool was called without payment, not that the tool failed. Before treating isError as a failure, check whether structuredContent carries x402Version: that is what separates "asked to pay" from "genuinely broken". The resource.url in that result is always the public address, never an internal one.verify_id.Submit a request
HTTP endpoint: POST https://verifi.cloud/verify
| Field | Type | Meaning |
|---|---|---|
intent | string, max 2000 | What the agent is trying to do |
claim | string, max 4000 | What the human should verify or answer |
agent_id | 0x wallet address | Requester wallet and quota identity |
callback_url | HTTPS URL, optional | Ready or failed notification |
curl -X POST https://verifi.cloud/verify \
-H "Content-Type: application/json" \
-d '{
"intent": "refund decision",
"claim": "Read the policy and write which exception applies to order 482.",
"agent_id": "0xYourWalletAddress",
"callback_url": "https://your-agent.example/verifi-events"
}'
An admitted request returns HTTP 202 with status: processing, a durable verify_id (also returned as work_id), contract_version: 3, retry_after_seconds, and the terms bound to the chain:
{
"verify_id": "a1b2c3d4-0000-4000-8000-000000000000",
"work_id": "a1b2c3d4-0000-4000-8000-000000000000",
"contract_version": 3,
"status": "processing",
"next_action": "poll",
"retry_after_seconds": 15,
"admitted_at": null,
"sla_deadline": null,
"grace_deadline": null,
"terms": {
"terms_id": "st_...",
"network": "eip155:8453",
"asset": "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42",
"asset_symbol": "EURC",
"admission": "100000", "sla": "2900000", "grace": "1450000",
"windows": { "sla": { "within_seconds": 3600 }, "grace": { "within_seconds": 86400 } }
},
"funding": {
"entry_source": "x402",
"entry_amount_atomic": "100000",
"entry_charged_atomic": "100000",
"unlock_amount_atomic": "2900000",
"entry_charged_usdc": null
}
}
The admission payment settles while the response is sent, so on the 202 of a paid admission admitted_at and both deadlines are still null. They appear on the first poll. An admission paid with a credit has them at once. The *_usdc funding fields are null on an EURC chain. The atomic amount fields are always present. Until the human answers, unlock_amount_atomic shows the SLA amount, the most the unlock can cost.
| Entry source | Admission | Unlock |
|---|---|---|
x402 | Paid | Paid, SLA or grace price |
failure_credit | Free | Paid, SLA or grace price |
initial_free | Free | Free (chains from before contract 3 only) |
Let Verifi tell your agent when to continue
Pass an HTTPS callback_url with verify_claim or POST /verify. Verifi sends verify.ready when the unlock can be called, or verify.failed when the chain must stop. A ready callback carries ready_at, service_window, and the unlock price.
{
"event": "verify.ready",
"verify_id": "a1b2c3d4-0000-4000-8000-000000000000",
"work_id": "a1b2c3d4-0000-4000-8000-000000000000",
"contract_version": 3,
"status": "ready",
"next_action": "unlock",
"unlock_url": "https://verifi.cloud/verify-unlock?id=a1b2c3d4-0000-4000-8000-000000000000",
"ready_at": "2026-10-01T09:41:12.000Z",
"service_window": {
"applied": "sla",
"sla_deadline": "2026-10-01T10:00:00.100Z",
"grace_deadline": "2026-10-02T09:00:00.100Z",
"network": "eip155:8453",
"asset": "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42",
"unlock_amount": "2900000"
}
}
verify_id before yielding. Verifi makes up to three delivery attempts. Treat the callback as a wake-up signal, then continue with unlock. If delivery fails, recover with get_verify or GET /verify/{verify_id}. The callback never includes the locked human answer.Poll the same request
Use MCP get_verify or HTTP GET /verify/{verify_id} when no callback is available or as recovery after a delivery failure. Polling never costs money.
| Status | Meaning | Next action |
|---|---|---|
processing | Admission is settling or a human is working | Wait for callback, or poll to recover |
ready | The answer exists but is locked | Call unlock |
failed | No answer can be unlocked | Stop |
completed | The answer is visible | Use the result |
A processing response carries admitted_at, sla_deadline, and grace_deadline, so the agent can plan its polling. expires_at equals the last deadline. A ready response contains no verdict, but it states exactly what the unlock costs and why:
{
"verify_id": "a1b2c3d4-0000-4000-8000-000000000000",
"work_id": "a1b2c3d4-0000-4000-8000-000000000000",
"contract_version": 3,
"status": "ready",
"verdict": null,
"explanation": null,
"admitted_at": "2026-10-01T09:00:00.100Z",
"ready_at": "2026-10-01T13:12:40.000Z",
"service_window": {
"applied": "grace",
"sla_deadline": "2026-10-01T10:00:00.100Z",
"grace_deadline": "2026-10-02T09:00:00.100Z",
"network": "eip155:8453",
"asset": "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42",
"unlock_amount": "1450000"
},
"next_action": "unlock",
"unlock_url": "https://verifi.cloud/verify-unlock?id=a1b2c3d4-0000-4000-8000-000000000000",
"unlock": {
"method": "POST",
"price_usdc": null,
"payment_required": true,
"asset_symbol": "EURC",
"amount_atomic": "1450000",
"amount_decimal": "1.45",
"applied_window": "grace"
}
}
On a USDC chain unlock.price_usdc is the decimal USDC price, for example "3.40".
Unlock the human answer
Use MCP unlock_verify or HTTP POST /verify-unlock?id={verify_id} only after status becomes ready. Before that the endpoint answers 409 and asks for no payment.
Without ?id= naming a chain, or with an unknown id, the endpoint answers 402 with the unlock price band: every accepted asset at both the SLA and the grace price. That 402 only describes the prices. A payment made against it is refused with 400 or 404 and never settled.
The unlock returns exactly one exact requirement: the asset the admission was paid in, at service_window.unlock_amount. Its extensions["service-windows"].info repeats terms_id, applied, admitted_at, and ready_at, so the agent can check the arithmetic against the terms it paid for at admission. Refuse to sign if the asset differs or the amount is higher than the bound amount for the applied window. An admission credit never covers this gate.
{
"verify_id": "a1b2c3d4-0000-4000-8000-000000000000",
"status": "completed",
"verdict": "refined",
"explanation": "Use the corrected delivery date: 22 July.",
"response": "Use the corrected delivery date: 22 July.",
"next_action": "done",
"funding": {
"entry_charged_atomic": "100000",
"unlock_window": "grace",
"unlock_charged_atomic": "1450000",
"total_charged_atomic": "1550000"
}
}
A completed chain returns its result again without a further charge.
The human verdict is true, false, or refined. A refined free-text answer is returned in explanation and response.
x402 on Base
All gates use x402 v2, scheme exact, on Base mainnet eip155:8453, with USDC (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) or EURC (0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42). Both tokens support EIP-3009, and the EIP-712 domain name and version are in each requirement's extra. The buyer needs USDC or EURC but no ETH.
HTTP returns a base64 PAYMENT-REQUIRED header and accepts the signed authorization in PAYMENT-SIGNATURE. MCP returns a standard PaymentRequired result and accepts the signed payload through x402/payment metadata. Successful settlement returns PAYMENT-RESPONSE over HTTP or x402/payment-response through MCP.
The admission and unlock payments are separate EIP-3009 authorizations and separate on-chain settlements connected to the same verify_id.
Automatic HTTP payment with @x402/fetch
The x402 client reads the 402 requirement, creates the EIP-3009 authorization with the requester's wallet, and retries the same request automatically. Without a custom selector it pays with the first option, USDC. Install @x402/core, @x402/fetch, @x402/evm, and viem:
npm install @x402/core @x402/fetch @x402/evm viem
import { wrapFetchWithPayment } from "@x402/fetch";
import { x402Client } from "@x402/core/client";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
const signer = privateKeyToAccount(
process.env.EVM_PRIVATE_KEY as `0x${string}`
);
// To pay in EURC instead, pass a selector:
// new x402Client((_v, accepts) => accepts.find((a) => a.extra?.name === "EURC") ?? accepts[0])
const client = new x402Client();
client.register("eip155:*", new ExactEvmScheme(signer));
const paidFetch = wrapFetchWithPayment(fetch, client);
const response = await paidFetch("https://verifi.cloud/verify", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
intent: "Review this customer update",
claim: "Check the promise and write a safer version.",
agent_id: signer.address,
callback_url: "https://your-agent.example/verifi-events"
})
});
const verify = await response.json();
Use the same paidFetch for POST /verify-unlock?id={verify_id}. It handles the separate unlock requirement, in the asset of the admission, when the callback reports ready. See the official x402 buyer quickstart.
Failure and entry credit
A chain nobody answers within 24 hours of admission fails with reason human_timeout:
{
"verify_id": "a1b2c3d4-0000-4000-8000-000000000000",
"status": "failed",
"next_action": "stop",
"failure": {
"reason": "human_timeout",
"entry_credit_granted": true,
"entry_credit": "next_admission",
"entry_credit_value_usdc": null
}
}
To spend a credit, the wallet signs the ordinary 402 for gate 1. Verifi checks that the signature comes from agent_id, uses the credit, and never submits the payment, so no funds move. A signature from any other wallet is settled as a normal payment, so nobody can spend another wallet's credit.
The credit is not cash. It pays only the admission of the same wallet's next request, which is then bound to the same asset as the expired chain. The unlock of that next request still costs the SLA or grace price when its answer is ready. entry_credit_value_usdc is the admission price on a USDC chain and null on an EURC chain.
Callbacks, errors, and audit
An optional callback sends verify.ready or verify.failed. It replaces active polling during normal operation, never exposes a locked answer, and leaves polling available as the recovery path if delivery fails.
| HTTP status | Meaning |
|---|---|
400 | Invalid input or wallet address |
402 | This gate requires an x402 payment, or the paid quote expired: sign the fresh terms |
409 | The lifecycle action is not currently valid, for example unlock before ready, or the paid terms could not be bound |
429 | The wallet already has an active review |
503 | The human queue is full, no quote can be issued right now, or paid routes are unavailable. Retry after the advertised delay |
A refused request is never charged. The active-request check runs before payment. Capacity and request validation run after it, because x402 cancels settlement for any 4xx or 5xx answer, so an unpaid caller always sees a real 402 first. The human queue has a hard cap: a full queue answers 503 after the payment gate, and nothing is charged. PostgreSQL records the wallet, request, every quote and ECB rate used, the bound terms, the applied window, charged amounts in the bound asset, both transaction hashes, free uses, credits, lifecycle timestamps, and the append-only audit trail.