Skip to content

Proxy URL

On this page

A URL prefix that checks every payment on its way to the seller. It is live, and the responses below are the shapes it answers with. You can also check payments with the SDK hook or by calling the API yourself. The proxy supports exact EVM payments on Base and Base Sepolia, plus standard sponsored exact USDC transfers on Solana Mainnet and Solana Devnet.

What it is

A URL prefix in front of the seller. Your agent asks the proxy, the proxy asks the seller, and the retry that carries the signed payment is checked before it is forwarded.

The URL is the seller's, and the token in front of it is yours. The seller is not asked to change anything and does not need an account here. It sees an ordinary request for its resource, answers its own 402, and is paid or is not.

  • The first request is forwarded, and the seller's 402 is relayed once the proxy has read its body. The proxy replaces the response's content-security-policy with sandbox, so a page opened in a browser runs no script on our address.
  • On the retry the proxy reads the payee and the amount (and on Solana the payer) from the signed payment, takes the asset and the network from the 402 it filed for that URL, checks that the terms the payment accepted are ones that 402 offered, runs the same decision as POST /api/v1/decisions under your policy, and forwards it only on allow.
  • The seller's 402 body and resource description are filed as untrusted content for the session, so the content check reads the seller's own terms. Every other response, a page fetched through the proxy included, is relayed without being scanned, read whole under the 4 MiB cap below.
  • On the way back the proxy reads the payment receipt and records the settlement, so the ledger fills in without the agent reporting anything.
  • A URL that never answers 402 passes straight through and makes no decision.

What it costs you to add

One string. The seller's URL becomes https://vulsight-guard.vercel.app/p/<proxy token>/<seller url>. No library, no language requirement, no wallet change. Your proxy token is on the dashboard home next to your API key, and it is a different secret: it authorizes checks, never payments, so it is safe to carry in a URL.

Try it against the demo seller

Terminal
GUARD=https://vulsight-guard.vercel.app/p/vsp_test_xxxxxxxx/
MERCHANT=vulsight-guard.vercel.app/merchant/dataset

# The seller's own 402, relayed.
curl -i "$GUARD$MERCHANT"
Answer
HTTP/2 402
cache-control: no-store
content-type: application/json
payment-required: eyJ4NDAyVmVyc2lvbiI6...

{}

The demo seller offers test data for test USDC on Base Sepolia. Point your own x402 client at the same prefixed URL and it pays as usual, with the check in between.

A missing or malformed seller URL gets 400 bad_merchant_url, so check that the seller's URL follows the token.

Malformed seller URL
HTTP/2 400
content-type: application/json

{
  "error": {
    "code": "bad_merchant_url",
    "message": "Put the seller URL after the token, like /p/<token>/merchant.example/data."
  }
}

Denied and review answers

A denied payment comes back as a 403 carrying the same error envelope the API uses, with the reasons joined into one message and the id of the decision that made them. The signed authorization is never forwarded. Nothing moves.

Denied
HTTP/2 403
content-type: application/json
guard-decision-id: 9107cf25-aa34-4d7b-a546-c8b5253634a3
guard-status: denied

{
  "error": {
    "code": "payment_denied",
    "message": "The amount 2.50 USDC is over the per-transaction limit of 0.10 USDC. This payment of 2.50 USDC would bring today's total to 2.50 USDC, over the daily cap of 1.00 USDC. First payment to 0x2222…2222. Auto-allow for first payments is off in your policy. The payee 0x2222…2222 is not in your allowlist."
  },
  "decisionId": "9107cf25-aa34-4d7b-a546-c8b5253634a3"
}

An EVM payment waiting for your review is held while you decide, for the shortest of 35 seconds, the time left on your review timeout, and 5 seconds before the agent's authorization expires. Approve in time and the proxy forwards it and you get the paid body. When the hold ends with the review still open, the seller's original 402 comes back with retry-after: 0 and a guard-status header, so a standard client retries and signs a fresh authorization that picks up the same review. When the review timeout runs out first, the review expires and the request in hand is answered 403 payment_denied. An expired authorization is never forwarded.

Waiting for review
HTTP/2 402
cache-control: no-store
guard-decision-id: daf814c5-e82b-41d3-8af2-cb71b5ccd6d8
guard-status: review_pending
payment-required: eyJlcnJvciI6IlBheW1l...
retry-after: 0

{}

Errors

Every failure the proxy raises itself comes in the same envelope the API uses, with a code you can branch on. Anything else is the seller's answer, relayed.

  • unknown_proxy_token (401). The token is not recognized, or was rotated on the Keys page. Use the token shown once beside the key when it was made or rotated.
  • revoked_proxy_token (401). The token was revoked. Make a new key on the Keys page.
  • bad_merchant_url (400). The seller's URL after the token is missing or malformed, is not https, carries a username or password, names a port, points at a private or loopback address, points at this site outside the demo sellers under /merchant/, or runs over 2000 characters, the most the proxy accepts. Put a public https seller URL after the token, its scheme left out. A seller URL written with its scheme is answered with a 308 to the same URL with the scheme's double slash collapsed, and a client that follows it reaches the seller.
  • bad_request (400). The request carries a header the proxy cannot send to a seller, named in the message. Remove it and send the request again.
  • bad_required_mode (400). The request carries an x-vulsight-require-mode header, a header that asks the proxy to answer only for an agent in enforce mode, with a value other than enforce, the only value it takes. Send enforce, or leave the header out.
  • proxy_not_enforced (409). The request carries x-vulsight-require-mode: enforce and the token's agent is in observe mode, so nothing was relayed, no signable 402 returned, and no payment forwarded. Switch the agent to enforce on the Agents page, or leave the header out to let observe mode relay.
  • decision_mode_changed (409). The agent switched from observe to enforce after this allowance was made. Nothing was forwarded. Retry under the current mode; the saved decision may now be denied or require review.
  • decision_not_finalized (409). The guard cannot prove the saved allowance completed its reservation check. Nothing was forwarded. Request a fresh quote and start a new payment attempt; the old allowance cannot be reused. Both 409 codes carry guard-decision-id and guard-status: not_forwarded. Their messages name the proxy retry steps, which differ from the API's new-decision instructions.
  • x402_v1 (400). The payment came in an X-PAYMENT header. The proxy speaks x402 v2 only, so send it in PAYMENT-SIGNATURE.
  • key_in_request (400). The request carries a VulSight key in Authorization. The token in the URL is all the proxy needs, and the seller must not receive your key.
  • no_challenge (400). The proxy has not seen a 402 for this URL. Request it through the proxy first, then retry with the payment.
  • unreadable_payment (400). The PAYMENT-SIGNATURE header is not an x402 v2 exact EVM or Solana payment the proxy can read, accepts terms the seller's 402 did not offer, or on Solana signs a transaction that is not a standard sponsored exact USDC transfer. The message says which.
  • payment_denied (403). Your policy denied the payment, or its review was denied or ran out. The message carries the reasons, and decisionId beside it names the decision.
  • rate_limited (429). Too many calls this minute. Wait the number of seconds in the retry-after header, then send it again. Three kinds of ceiling apply: every request the proxy accepts counts on the relay meter, a 402 the proxy files counts on the context meter, and a signed retry counts on the decision meter. The message says which count, and whether the key or the account reached it. The ceilings are on the API page.
  • relay_budget_exceeded (429). This answer would take your account past the 256 MiB of seller answers the proxy relays in a UTC day (a paid request needs 4 MiB and a 16 KiB header allowance free before the payment is forwarded), so it was not passed on and no payment was forwarded. The retry-after header counts to midnight UTC, when the day starts over.
  • merchant_unreachable (502, or 504 when the seller did not answer in time). The seller's name did not resolve, it did not answer, or the proxy ran out of time before forwarding. Check the URL, then try again. On the retry, see below.
  • bad_402 (502). The seller's 402 carries no x402 v2 terms the proxy can read, or a body over 64 KiB, so it was not passed on.
  • merchant_answer_too_large (502). The seller's answer is larger than the 4 MiB the proxy relays, so it was not passed on. Ask the seller for a smaller resource.
  • merchant_answer_cut (502). The seller stopped answering part way through its body, so it was not passed on.
  • merchant_answer_too_slow (504). The seller sent its headers and then stopped sending its body, so the answer did not finish in the time the proxy waits and none of it was passed on.
  • solana_rpc_unavailable (503). The proxy could not check the Solana blockhash before forwarding, so nothing was forwarded; the decision waits under the guard-decision-id in the answer. Request a fresh quote and try again once the RPC answers.
  • internal (500). Something failed on our side. Try again. On the retry that carries the payment, read the decision on the Decisions page in your account first, since this answer carries no guard-decision-id.

On the retry that carries the payment, merchant_answer_too_large, merchant_answer_cut, and merchant_answer_too_slow carry the decision headers and their message says what became of the payment. A guard-decision-id is the authorization the proxy gave, not proof the money moved, so the message reads the settlement instead: when the seller's receipt was filed it says so and names the decision the receipt sits under, which is how you know asking again costs a second payment. When no receipt came back, or the one that did could not be filed, it says the payment could not be confirmed and points you at that decision, on the Decisions page in your account or at GET /api/v1/decisions/{id}, before you ask again. If that execution check refuses an old allowance with a 409, use the Decisions page to read its settlement history and check your wallet before paying again. merchant_unreachable on the retry carries the same headers and note when the signed request left; when guard-status is not_forwarded the payment never left, so re-sign and send it again. With neither header the seller's name did not resolve, so nothing was sent; check the URL and send it again.

Solana transactions and reviews

The proxy checks the buyer signature and actual TransferChecked amount, USDC mint, associated token accounts and quoted fee payer. Only compute-budget instructions, one classic SPL Token transfer and an optional memo are accepted. Token-2022, lookup tables, smart wallets and arbitrary programs are not supported.

A pending Solana review returns 402 immediately with the decision id in guard-decision-id and retry-after: 2. Send the same request again after retry-after seconds until the review is answered, or read the decision at GET /api/v1/decisions/{id} with your API key, then obtain a fresh quote and sign again with a fresh blockhash. An expired blockhash answers with guard-status: resign_required and guard-next-step: fresh_quote, so obtain a new unsigned quote before signing; the old blockhash hints are removed from its payment terms. Never retry an uncertain submitted payment without checking the wallet.

The proxy records a Solana settlement only after the network confirms the same transaction message. guard-settlement-status: confirmed means that check and the ledger write succeeded. unverified means confirmation or recording failed, not that no money moved. RPC failure before forwarding blocks the request.

Its limit

  • Only traffic through this URL is checked. The same agent calling the seller directly is not, so the proxy is worth only as much as the coverage you give it.
  • The proxy checks, it never signs. Your key stays on your side, and the guard cannot pay for you or claw a payment back.
  • The proxy token sits in a URL, so it lands in logs and shell history. Treat it as a secret you rotate, not one you can hide, and rotate it on the Keys page if it leaks, which stops the old pair at once.
  • The proxy speaks x402 v2 only. A v1 client is refused with x402_v1, above.

The rest of the limits are on the security model and limits page.