Live truck driver position over an API. The driver connects from an SMS link in about a minute. No corporate account, no ELD provider, no annual contract.
| Operation | Price |
|---|---|
| Create a load | $0.65 |
| Read load position | $0.02 / request |
| Trip summary stats | $0.02 / request |
| Cancel a load | Free |
| Pricing | Free |
| Balance | Free |
One key lets your AI agent or software work with your loads: create loads, read live positions, pull post-trip summary stats and cancel loads you no longer need — billed to your prepaid PingPoint balance. Free operations work at zero balance.
Want to try it first? The sandbox runs on a published key — no signup, same prices, balance already paid for. No key at all? Pay per call with USDC from a wallet on Arc, Base or Solana.
Six operations, and that's the whole surface. Three things are missing on purpose:
Calling a route outside the six answers 501 OPERATION_NOT_AVAILABLE and lists what is available in the body. Nothing is charged.
How this compares to alternatives: /compare.
Paid operations debit your prepaid balance — top it up in the cabinet under Billing.
Try the API before you sign up: this key is public, works on the same endpoints, and needs no account of your own and no live driver.
It is not free, it is prepaid. The key spends a balance we fund, at production prices — $0.65 per load created, $0.02 per position or trip-stats read. You are not billed and you need no card; you are spending ours.
Test key: sup_agent_8Q3d7hfKjT2JOCQTmSBVVvXCjYK9Qcrk
Base URL: https://api.suverse.io — the same one production uses.
Create a load with it exactly as you would for real, and add a scenario field. The load then drives itself: a simulated truck walks the real route, statuses advance through the same geofence engine as a live truck (PLANNED → AT_PICKUP → IN_TRANSIT → AT_DELIVERY → DELIVERED), and every read returns real computed data.
POST https://api.suverse.io/v1/agent/loads
Authorization: Bearer sup_agent_8Q3d7hfKjT2JOCQTmSBVVvXCjYK9Qcrk
Content-Type: application/json
{
"driverPhone": "+15550001234",
"pickups": [ { "address": "1200 N Riverfront Blvd", "city": "Dallas", "state": "TX", "zip": "75207" } ],
"deliveries": [ { "address": "500 Main St", "city": "Fort Worth", "state": "TX", "zip": "76102" } ],
"scenario": "late"
}
| Scenario | What happens | Trip length |
|---|---|---|
normal | Drives the route and arrives inside the delivery window | ≈ 20 min |
late | Drives slower and arrives after the window — onTime: false | ≈ 26 min |
signal_loss | Pings stop mid-trip for about 4½ minutes, then resume — a gap in the track | ≈ 20 min |
Omit scenario and you get normal. Watch it move with the position read, or open the trackingLink from the create response.
driverLink in the response is returned and opens, but nobody is behind it. Positions come from the simulator, not from a phone.customerRef unique or leave it out. It is the dedup key: sending one that someone else already used returns their load instead of creating yours.429 SANDBOX_RATE_LIMITED. Someone else's testing can spend the hour.402 INSUFFICIENT_FUNDS until it is topped up — write to dmitrii@suverse.io and it will be refilled, usually the same day.Everything else is the production system: real geofences, real ETA math, real trip stats, real cancellation, real billing. When you're ready for a live driver, get your own key above.
The same four load operations without an account or a key: your software or agent pays each call from a wallet, in USDC, over the x402 protocol — HTTP 402 plus a signed USDC transfer. No prepaid balance, no facilitator, no gas: PingPoint's relayer submits the transfer and pays the gas, you only sign. Four networks, one price list.
Base URL: https://pingpoint.suverse.io
Routes: POST /api/internal/loads · GET /api/internal/loads/{loadNumber} · GET …/{loadNumber}/trip-stats · POST …/{loadNumber}/cancel — bodies identical to /v1/agent/loads… below
Payment header: X-PAYMENT: <base64 x402 payload>, answered with X-PAYMENT-RESPONSE (tx hash or Solana signature)
network | Chain | USDC | What you sign | Explorer |
|---|---|---|---|---|
arc-mainnet | Arc (5042) | 0x3600…0000 | EIP-3009 TransferWithAuthorization (EIP-712, domain USDC/2) | explorer.arc.io |
arc-testnet | Arc testnet (5042002) | 0x3600…0000 (faucet) | EIP-3009 TransferWithAuthorization (EIP-712, domain USDC/2) | testnet.arcscan.app |
base-mainnet | Base (8453) | 0x8335…2913 (Circle) | EIP-3009 TransferWithAuthorization (EIP-712, domain USD Coin/2) | basescan.org |
solana-mainnet | Solana mainnet | mint EPjF…Dt1v (Circle) | a partially signed transaction: one SPL TransferChecked to the treasury, fee payer = PingPoint's relayer (extra.feePayer) | solscan.io |
Arc mainnet, Base and Solana carry real USDC; Arc testnet is for trying things out with faucet USDC. Offers come in this order: arc-mainnet, base-mainnet, solana-mainnet, arc-testnet.
How a paid call works. Send the request without credentials → 402 with one offer per network in accepts[] (amount in USDC base units, payTo, asset, network, and the EIP-712 domain or the Solana fee payer) → pick the offer for your network and sign it: on Arc or Base an EIP-3009 authorization for exactly that amount; on Solana a transaction that transfers exactly that amount and carries your signature only → repeat the request with X-PAYMENT. The server verifies the signature and your USDC balance (on Solana it also simulates the transaction), settles on-chain — countersigning as fee payer on Solana — and only then runs the operation. A refused call (403, 404, 429, invalid input) happens before settlement and costs nothing.
With the SDK you pass the wallet and the network; everything above is done for you. EVM (Arc, Base) takes any EIP-712 signer, a viem local account as-is:
import { PingPointAgent } from "@suverselabs/pingpoint-sdk";
import { privateKeyToAccount } from "viem/accounts";
const pp = new PingPointAgent({ wallet: privateKeyToAccount(WALLET_KEY), network: "arc-mainnet" }); // or "base-mainnet" / "arc-testnet"
const load = await pp.createLoad({ driverPhone, pickups, deliveries }); // 0.65 USDC
console.log(load.payment.explorerUrl); // explorer.arc.io link
const pos = await pp.getPosition(load.loadNumber); // 0.02 USDC
await pp.cancelLoad(load.loadNumber); // free
Solana takes an object with the base58 publicKey and signTransaction(bytes) — a @solana/web3.js Keypair or any wallet-adapter wallet; you never hold SOL, the relayer pays the fee:
import { PingPointAgent, type X402SolanaSigner } from "@suverselabs/pingpoint-sdk";
import { Keypair, VersionedTransaction } from "@solana/web3.js";
const kp = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(SOLANA_KEY_JSON)));
const wallet: X402SolanaSigner = {
publicKey: kp.publicKey.toBase58(),
async signTransaction(bytes) { const tx = VersionedTransaction.deserialize(bytes); tx.sign([kp]); return tx.serialize(); },
};
const pp = new PingPointAgent({ wallet, network: "solana-mainnet", solanaRpcUrl: "https://<your-provider>" }); // RPC optional
The MCP server needs only a key file and the network — a 0x… private key for Arc/Base, a solana-keygen JSON (or base58) keypair for Solana:
claude mcp add pingpoint --env PINGPOINT_WALLET_KEY_FILE=$HOME/.pingpoint-wallet.key --env PINGPOINT_NETWORK=arc-mainnet -- npx -y @suverselabs/pingpoint-mcp
claude mcp add pingpoint --env PINGPOINT_WALLET_KEY_FILE=$HOME/.pingpoint-wallet.key --env PINGPOINT_NETWORK=base-mainnet -- npx -y @suverselabs/pingpoint-mcp
claude mcp add pingpoint --env PINGPOINT_WALLET_KEY_FILE=$HOME/.pingpoint-wallet.json --env PINGPOINT_NETWORK=solana-mainnet -- npx -y @suverselabs/pingpoint-mcp
| Operation | Price | Limits |
|---|---|---|
| Create a load | 0.65 USDC | per wallet and network: 20 loads per 24 hours on Arc mainnet, Base and Solana, 2 on Arc testnet; a driver phone used by another wallet within the hour is refused |
| Read load position | 0.02 USDC / request | only loads created by this wallet |
| Trip summary stats | 0.02 USDC / request | only loads created by this wallet |
| Cancel a load | Free | zero-value signature proves ownership, no transaction |
| Pricing | Free | the 402 offer itself; the SDK's getPricing() collects them |
| Balance | On-chain | your wallet's USDC; the MCP server's get_balance reads it |
An EVM offer is valid for 300 seconds and every signature carries a one-time nonce; a Solana transaction lives as long as its blockhash (about a minute) and your signature is the nonce. The SDK cross-checks the offer against its network table (asset, chain id, fee payer) and refuses anything above maxUsdcPerCall (default 1 USDC) before signing. Wallet-door error codes: 402 PAYMENT_REQUIRED (no payment yet — sign and retry), 402 INSUFFICIENT_BALANCE / NONCE_USED / AUTHORIZATION_EXPIRED / INVALID_SIGNATURE / UNSUPPORTED_NETWORK / SETTLEMENT_FAILED and on Solana WRONG_FEE_PAYER / INVALID_TRANSACTION / SIMULATION_FAILED (nothing charged), 403 X402_NOT_OWNER, 429 X402_DAILY_LIMIT / X402_DRIVER_PHONE_BUSY, 503 X402_RELAYER_LOW_GAS (the relayer is out of gas on that network — nothing charged, retry later). Keep the wallet small — enough USDC for the day is the sensible setup.
Base URL: https://api.suverse.io
Auth header: Authorization: Bearer sup_agent_…
Key format: sup_agent_ followed by the secret from the e-mail
OpenAPI 3.1 spec: /docs/openapi.json
POST/v1/agent/loads
POST https://api.suverse.io/v1/agent/loads
Authorization: Bearer sup_agent_…
Content-Type: application/json
{
"driverPhone": "+15551234567",
"pickups": [
{ "facilityName": "General Mills DC", "address": "6492 Tower Lane",
"city": "Claremore", "state": "OK", "zip": "74017",
"date": "2026-08-18T15:00:00Z", "dateTo": "2026-08-18T19:00:00Z" }
],
"deliveries": [
{ "facilityName": "Caldwell Park Warehouse", "address": "6499 Caldwell Park Dr",
"city": "Charlotte", "state": "NC", "zip": "28269",
"date": "2026-08-20T12:00:00Z", "dateTo": "2026-08-20T16:00:00Z" }
],
"customerRef": "PO-483920",
"shipperName": "General Mills",
"carrierName": "Best Carrier LLC",
"equipmentType": "VAN",
"rate": 1450,
"weight": 24000
}
Required: pickups, deliveries (address, city, state, zip each) and driverPhone — the load link is sent to that number. Everything else is optional. Loads are created under your account (the key identifies you) and are billed from your balance. Optional extras: shipperName, carrierName, equipmentType, customerRef (doubles as a dedup key — re-sending it returns the existing load instead of creating a duplicate), rate, weight, truckNumber, per-stop lat/lng, and an Idempotency-Key header for safe retries.
Stop windows. Per stop, date is the window start and dateTo is the window end; both optional. The end matters: onTime and delayMinutes on the position read are measured against the dateTo of the last delivery stop, so without it both stay null forever. A dateTo that doesn't parse, or that falls before its own date, is refused with 400 INVALID_STOP_WINDOW rather than silently dropped.
How many stops. Up to 2 pickups and up to 3 deliveries, counted independently; more answers 400 TOO_MANY_STOPS with the caps and what you sent. Stop order in the body is the stop sequence, and stops cannot be added later — a load is not editable after creation.
The response carries loadNumber, a public trackingLink for your customer and the driver app links.
GET/v1/agent/loads/{loadNumber} — $0.02 per request
curl -H "Authorization: Bearer sup_agent_…" \
https://api.suverse.io/v1/agent/loads/LD-2026-042317
Returns the load's live state: status, driver-side tracking state, GPS track (last 500 points), stop timeline with arrival/departure timestamps, distance covered, on-time flag, dwell times and an ETA block:
{
"loadNumber": "LD-2026-042317",
"status": "IN_TRANSIT",
"createdAt": "2026-08-18T14:02:11.000Z",
"deliveredAt": null,
"driverTracking": "REPORTING",
"inviteSentAt": "2026-08-18T14:02:12.000Z",
"linkOpenedAt": "2026-08-18T14:09:40.000Z",
"declinedAt": null,
"distanceMiles": 412.7,
"onTime": null,
"delayMinutes": null,
"pickupDwellMinutes": 38,
"deliveryDwellMinutes": null,
"stops": [
{ "type": "PICKUP", "sequence": 1, "city": "Claremore", "state": "OK",
"windowFrom": "2026-08-18T15:00:00.000Z", "windowTo": "2026-08-18T19:00:00.000Z",
"arrivedAt": "2026-08-18T15:47:31.000Z", "departedAt": "2026-08-18T16:25:09.000Z" },
{ "type": "DELIVERY", "sequence": 2, "city": "Charlotte", "state": "NC",
"windowFrom": "2026-08-20T12:00:00.000Z", "windowTo": "2026-08-20T16:00:00.000Z",
"arrivedAt": null, "departedAt": null }
],
"gpsTrack": [
{ "lat": 36.3126, "lng": -95.6161, "speed": 63.4, "heading": 291,
"ts": "2026-08-18T16:25:09.000Z" }
],
"pingCount": 214,
"eta": {
"nextStop": { "type": "DELIVERY", "sequence": 2, "city": "Charlotte", "state": "NC" },
"receivingWindow": { "from": "2026-08-20T12:00:00.000Z", "to": "2026-08-20T16:00:00.000Z" },
"distanceToNextStopMi": 611.4,
"driveTimeHours": 11.2,
"moving": true,
"stoppedForMinutes": null,
"etaWindow": { "from": "2026-08-20T13:05:00.000Z", "to": "2026-08-20T15:40:00.000Z" },
"tracking": { "state": "pinging", "noDataForMinutes": null },
"reason": null
}
}
gpsTrack shortened here for readability — the 500 most recent points, oldest first; speed is mph, heading is degrees (0–359). Don't poll in a loop — every call is billed.
Did the driver connect? driverTracking answers that in one word, the same one the cabinet shows (null when the load has no driver):
| driverTracking | Meaning |
|---|---|
| NOT_BOUND | The driver has not opened the link in the app yet. Check inviteSentAt (the SMS went out) and linkOpenedAt (the link was tapped in a browser, app not installed) |
| BOUND_SILENT | The app is bound to the load but has never sent a GPS position — usually location permission not granted |
| DECLINED | The driver tapped Not now on the app's location-consent screen — declinedAt carries the time. Call the driver; tracking stays off until they enable it in the app |
| REPORTING | Positions are arriving (for a driver who once declined: a position newer than declinedAt) |
| PAUSED_BY_DRIVER | The driver paused sharing from the app; it resumes automatically on the next assigned load |
The three timestamps are ISO-8601 or null. They are part of the same read — no extra charge.
501 OPERATION_NOT_AVAILABLE.
POST/v1/agent/loads/{loadNumber}/cancel — free
curl -X POST -H "Authorization: Bearer sup_agent_…" \
https://api.suverse.io/v1/agent/loads/LD-2026-815401/cancel
{
"ok": true,
"loadNumber": "LD-2026-815401",
"previousStatus": "IN_TRANSIT",
"status": "CANCELLED",
"cancelledAt": "2026-08-21T04:49:21.654Z",
"trackingEndedAt": "2026-08-21T04:49:21.654Z"
}
A cancelled load stops tracking: further driver pings are refused and the load disappears from the driver app. Nothing is deleted — the load, its stops and the GPS track recorded so far stay readable through the position read and trip stats.
Money is not returned. The $0.65 spent creating the load and anything spent on position reads stay spent; cancelling itself is free.
The call is idempotent — cancelling an already-cancelled load changes nothing and answers:
{ "ok": true, "idempotent": true, "loadNumber": "LD-2026-815401", "status": "CANCELLED" }
A delivered load cannot be cancelled — that answers 409 LOAD_ALREADY_DELIVERED, and retrying won't change it. Other errors match the position read: 401, 403 (not your load), 404, 422 UNKNOWN_BROKER.
GET/v1/agent/loads/{loadNumber}/trip-stats — $0.02 per request
Aggregated summary of the load's whole GPS trip, computed on demand over every recorded ping. Meant for a completed (DELIVERED) load — post-trip scoring, CO₂ estimation, detention evidence; on an in-progress load it returns the trip so far. For where the truck is right now, use the position read above instead.
curl -H "Authorization: Bearer sup_agent_…" \
https://api.suverse.io/v1/agent/loads/LD-2026-648319/trip-stats
A real response from a delivered load:
{
"loadNumber": "LD-2026-648319",
"loadId": "927741bc-dfd0-41ba-9e99-5c5031c756f9",
"stats": {
"dataPoints": 4785, // GPS pings recorded for this load
"durationSeconds": 69329, // lastAt − firstAt
"estimatedDistanceMiles": 486.591,// haversine over the full track
"avgSpeedMph": 25.27, // over the whole span, stops included
"maxSpeedMph": 88.22,
"hardAccelCount": 280, // > +15 mph/min while moving > 20 mph
"hardBrakeCount": 168, // < −20 mph/min while moving > 20 mph
"cityMilesPct": 9.96, // miles at 5–45 mph, % of distance
"highwayMilesPct": 88.65, // miles above 45 mph, % of distance
"parkedTimePct": 53.65, // pings at ≤ 5 mph, % of pings
"nightPct": 44.7, // pings between 23:00–07:00 UTC, % of pings
"coveragePct": 100, // pings vs. 1-per-minute expectation, capped at 100
"firstAt": "2026-08-18T16:53:25.000Z",
"lastAt": "2026-08-19T12:08:54.000Z"
}
}
All timestamps are UTC. Percentages are 0–100. Errors match the position read: 401, 402 INSUFFICIENT_FUNDS (nothing charged), 403 (not your load), 404, 422 UNKNOWN_BROKER.
| Endpoint | What it does | Price |
|---|---|---|
| GET /v1/agent/pricing | Current USD price list — read it instead of hardcoding prices | Free |
| GET /v1/agent/balance | Your prepaid balance | Free |
PingPoint pushes load events to your endpoint. Self-serve: in the cabinet open Integrations → Webhooks, paste your HTTPS URL and flip the switch — no approval step, no extra cost.
| Event | Fires when |
|---|---|
| pingpoint.load.created | A load is created — in the cabinet or through the Agent API |
| pingpoint.load.updated | Load details are updated |
| pingpoint.status.changed | The status advances from GPS / geofence events, or the load is cancelled — body carries previousStatus. Also fires, with the status unchanged, when the driver declines or later grants location consent — read driverTracking |
| pingpoint.load.completed | Delivery completes |
| pingpoint.exception.raised | PingPoint detects NO_SIGNAL (no position for 20 min), LATE (delivery window passed) or LONG_DWELL (over 60 min inside a stop zone) — body carries an exception object |
| pingpoint.exception.resolved | The condition clears (resolvedReason: "restored") or the load is delivered / cancelled while it was open ("load_closed") |
Each event is a POST to your URL with a JSON body:
{
"event": "pingpoint.status.changed",
"version": "1.0",
"id": "a3f1c2e4-…", // unique per delivery
"createdAt": "2026-08-19T14:02:11.000Z",
"account": { "userId": "…", "email": "broker@example.com", "name": "Acme Logistics" },
"data": {
"loadId": "…",
"loadNumber": "LD-2026-042317",
"reference": "PO-8841", // your customerRef, null if none
"status": "DELIVERED",
"previousStatus": "AT_DELIVERY", // status.changed / completed only
"driverTracking": "REPORTING", // same values as on the position read; null without a driver
"rateAmount": "1850", "currency": "USD",
"equipmentType": "VAN",
"shipperName": "…", "carrierName": "…",
"stops": [
{ "sequence": 1, "type": "PICKUP", "facilityName": "…", "city": "Dallas", "state": "TX",
"windowFrom": null, "windowTo": null,
"arrivedAt": "2026-08-18T16:40:00.000Z", "departedAt": "2026-08-18T17:05:00.000Z" }
]
}
}
Exception events carry the same data block plus:
"exception": {
"id": "…",
"type": "NO_SIGNAL", // NO_SIGNAL | LATE | LONG_DWELL
"detectedAt": "2026-08-19T13:41:00.000Z",
"resolvedAt": null, // set on exception.resolved
"resolvedReason": null, // "restored" | "load_closed" on exception.resolved
"details": { "lastPingAt": "2026-08-19T13:19:52.000Z", "minutesSinceLastPing": 21 }
}
Every delivery carries two headers:
X-PingPoint-Event: pingpoint.status.changed
X-PingPoint-Signature: hex(HMAC-SHA256(secret, raw request body))
Compute HMAC-SHA256 over the raw request body with your webhook signing secret from the cabinet and compare it to the header:
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const valid = expected === req.headers["x-pingpoint-signature"];
GET /v1/agent/loads/{loadNumber} as the source of truth. The signing secret rotates automatically when you change the webhook URL.@suverselabs/pingpoint-sdk — typed client, typed errors, idempotent retries, zero dependencies. API key or wallet ({ wallet, network }).
npm install @suverselabs/pingpoint-sdk
const pp = new PingPointAgent({ apiKey: "sup_agent_…" });
const load = await pp.createLoad({ driverPhone, pickups, deliveries });
const pos = await pp.getPosition(load.loadNumber);
@suverselabs/pingpoint-mcp — add one config entry and your agent gets the tools; no code required. PINGPOINT_AGENT_KEY, or PINGPOINT_WALLET_KEY_FILE to pay per call in USDC.
claude mcp add pingpoint --env PINGPOINT_AGENT_KEY=sup_agent_… -- npx -y @suverselabs/pingpoint-mcp
"pingpoint": { "command": "npx", "args": ["-y", "@suverselabs/pingpoint-mcp"],
"env": { "PINGPOINT_AGENT_KEY": "sup_agent_…" } }
Details that are easy to guess wrong. All of them are how the system runs today, not intentions.
IN_TRANSIT; arriving at the last sets AT_DELIVERY and departing it sets DELIVERED — regardless of that stop's type. Middle stops record their own arrivedAt/departedAt and leave the load's status alone.onTime, delayMinutes and deliveryDwellMinutes come from the last delivery; pickupDwellMinutes from the last pickup.ETA is always to the next unvisited stop, not to the final destination; it switches to the following stop as the truck progresses. When it can't be computed honestly, etaWindow is null and reason says why:
reason | Meaning |
|---|---|
en_route_to_pickup | Next stop is the first pickup — the stored route starts there, so the deadhead leg has no route distance to measure |
no_route_geometry | The route was never built (a stop had no coordinates, or the routing service was down at creation) |
route_stop_mismatch | The stored route doesn't match the current stop set |
stale_position | The newest position is older than 60 minutes |
no_position | No position has ever been recorded for this load |
load_not_active | The load is DELIVERED or CANCELLED |
at_final_stop | Every stop has been arrived at — there is no next stop |
eta_internal_error | The computation failed; the rest of the position read is still valid |
One gateway quirk worth knowing: on load creation the error arrives wrapped — the machine-readable code is in upstreamCode (and the original body in upstream), not at the top level. Other operations return the body as-is. Read upstreamCode first and fall back to code.
| Code | Meaning |
|---|---|
400 MISSING_FIELDS | Required fields are absent — the body lists them in fields. Also 400 INVALID_DRIVER_PHONE when the phone is not E.164. |
401 | Missing or invalid key. |
402 INSUFFICIENT_FUNDS | Prepaid balance can't cover the operation — nothing was charged. The body carries balanceUsd, priceUsd and billingUrl; top up in the cabinet under Billing and retry. On the sandbox key the balance is ours, not yours: write to dmitrii@suverse.io and it will be refilled. |
403 | The load belongs to another account. |
404 | No such load. |
400 TOO_MANY_STOPS | More than 2 pickups or more than 3 deliveries. The body carries limits and received. Nothing created, nothing charged. |
400 INVALID_STOP_WINDOW | A stop's dateTo doesn't parse, or ends before its own date. The body names the field in fields. Nothing created, nothing charged. |
409 LOAD_ALREADY_DELIVERED | Cancel on a delivered load. Delivered is final — don't retry. |
422 UNKNOWN_BROKER | The key's account is not registered on PingPoint. |
429 SANDBOX_RATE_LIMITED | Sandbox key only: more than 30 loads in the last hour. The cap is shared by everyone using the published key. Retry later; nothing was charged. |
501 OPERATION_NOT_AVAILABLE | The route isn't part of the API (status writes, delivery confirmation). The body lists what is available. Not a fault of your key or request; nothing charged; don't retry. |
503 BILLING_UNAVAILABLE | Billing backend temporarily unreachable — nothing was charged, retry later. |
402 PAYMENT_REQUIRED and other x402 codes | Wallet door only: no X-PAYMENT yet — sign the offer in accepts[0] and retry. After a payment: INSUFFICIENT_BALANCE, NONCE_USED, AUTHORIZATION_EXPIRED, SETTLEMENT_FAILED, on Solana also WRONG_FEE_PAYER / INVALID_TRANSACTION / SIMULATION_FAILED — nothing charged. 503 X402_RELAYER_LOW_GAS: relayer out of gas on that network, retry later. 403 X402_NOT_OWNER: another wallet's load. 429 X402_DAILY_LIMIT / X402_DRIVER_PHONE_BUSY: per-wallet limits. |