Skip to content
On this pageBefore you start1. Get an API key2. See a decision with nothing installed3. Route one payment4. See a review5. Find it on HomeIf it failsNext steps

Start / Quickstart

Quickstart

Route one payment through the proxy URL to the demo seller, and watch the guard decide it. It takes about ten minutes, on Base Sepolia with test USDC.

You are on the buyer's side. Your agent pays, and the guard checks each payment before the seller sees it. The seller changes nothing, and the wallet you already use stays as it is.

Before you start

  • An account. Sign up if you have none.
  • Node 22.18 or later. Step 3 uses it to make a wallet, and Bun or Node runs the program.
  • A throwaway wallet with test USDC. Step 3 makes one, and the Circle faucet funds it.

1. Get an API key

  1. Open Home after you sign up. Copy your API key and the proxy URL beside it.

    You see: each one once, with a Copy button. The key starts with vs_test_, and the proxy URL holds a vsp_test_ token.

  2. Read your policy back to check the key.

    Terminal
    export VULSIGHT_API_KEY="<your key>"
    
    curl -s -H "authorization: Bearer $VULSIGHT_API_KEY" \
      https://vulsight-guard.vercel.app/api/v1/policy

    You see: the policy of a new key.

    Response
    {
      "allowedNetworks": ["eip155:84532"],
      "allowedAssets": [
        {
          "network": "eip155:84532",
          "address": "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
        }
      ],
      "allowedPayees": [
        {
          "network": "eip155:84532",
          "address": "0x1111111111111111111111111111111111111111"
        }
      ],
      "deniedPayees": [],
      "blocklistEnabled": false,
      "allowedResources": [],
      "perTxLimitAtomic": "100000",
      "dailyCapAtomic": "1000000",
      "holdOverAtomic": "0",
      "velocityPerHour": 20,
      "autoAllowFirstTimeUnderAtomic": "0",
      "contractDenylist": {
        "selectors": [],
        "addresses": []
      },
      "reviewTimeoutSeconds": 120
    }

A new key starts with this policy. The demo seller is on your allowlist, on the Payees page. The policy reference explains every field.

Setting on PolicyStarts at
Modeenforce
Networks and assetsBase Sepolia, test USDC
Per-transaction limit (USDC)0.10
Daily cap across all networks (USDC)1.00
Hold over (USDC)0.00, off
Auto-allow unlisted payees under (USDC)0.00, so a payment to a payee off your allowlist is held
Payments per hour per network20
Review timeout (seconds)120
note

The vs_test_ prefix marks the beta, not test money. With Base or Solana Mainnet in the policy, payments move real USDC.

2. See a decision with nothing installed

  1. On Home, press Run a blocked payment.

    You see: "Denied before anything was signed." The decision lands under Recent decisions. Open it for the reasons.

  2. Optional. Ask the guard for a decision with curl, in the shell with your key.

    Terminal
    curl -s -X POST https://vulsight-guard.vercel.app/api/v1/decisions \
      -H "authorization: Bearer $VULSIGHT_API_KEY" \
      -H "content-type: application/json" \
      -d '{
        "kind": "x402_payment",
        "payTo": "0x1111111111111111111111111111111111111111",
        "amountAtomic": "1000",
        "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
        "network": "eip155:84532",
        "scheme": "exact",
        "resourceUrl": "https://vulsight-guard.vercel.app/merchant/weather",
        "observed402PayTo": "0x1111111111111111111111111111111111111111",
        "sessionId": "sess_1",
        "contextExcerpt": "Current conditions for Singapore as JSON."
      }'

    You see: the decision, with every rule that ran.

    Response
    {
      "id": "df3c92cd-61ef-4705-8f70-a4fe15881c93",
      "channel": "api",
      "createdAt": "2026-09-03T22:54:48.482Z",
      "status": "allowed",
      "rules": [
        { "id": "network_allowed", "result": "pass" },
        { "id": "asset_allowed", "result": "pass" },
        { "id": "payee_denylisted", "result": "pass" },
        { "id": "payee_matches_402", "result": "pass" },
        { "id": "payee_allowlisted", "result": "pass" },
        { "id": "amount_per_tx", "result": "pass" },
        { "id": "amount_daily_cap", "result": "pass" },
        { "id": "amount_hold_over", "result": "pass" },
        { "id": "velocity_per_hour", "result": "pass" },
        { "id": "contract_call_denied", "result": "pass" },
        { "id": "payment_method_supported", "result": "pass" },
        { "id": "injection_suspected", "result": "pass" },
        { "id": "first_time_payee", "result": "pass" }
      ]
    }

status is the decision. A call like this is advisory, not enforced. Your code has to act on the answer, and the proxy URL acts on it for you.

3. Route one payment

Your x402 client pays the demo seller 0.05 test USDC through the proxy URL. The seller is on your allowlist and the amount is under your limits, so the guard allows it.

  1. In a new folder, install an x402 client.

    bun add @x402/fetch @x402/evm viem

    You see: the three packages in your package.json.

  2. Make a throwaway wallet in the same shell. Paste its address into the Circle faucet and request test USDC on Base Sepolia.

    Terminal
    export EVM_PRIVATE_KEY="$(
      node --input-type=module -e '
        import { generatePrivateKey } from "viem/accounts";
        console.log(generatePrivateKey());
      '
    )"
    node --input-type=module -e '
      import { privateKeyToAccount } from "viem/accounts";
      const key = process.env.EVM_PRIVATE_KEY;
      console.log(privateKeyToAccount(key).address);
    '

    You see: the wallet's address. The key is exported, never printed.

  3. Export the proxy URL from step 1.

    Terminal
    export VULSIGHT_PROXY_URL="<your proxy URL>"

    You see: nothing. The URL looks like https://vulsight-guard.vercel.app/p/vsp_test_.../, ending in a slash.

  4. Save this program as agent.ts.

    agent.ts
    import { ExactEvmScheme } from "@x402/evm/exact/client";
    import { wrapFetchWithPayment, x402Client } from "@x402/fetch";
    import { privateKeyToAccount } from "viem/accounts";
    
    const need = (name: string) => {
      const value = process.env[name];
      if (!value) throw new Error(`Set ${name} before starting the agent.`);
      return value;
    };
    
    const key = need("EVM_PRIVATE_KEY") as `0x${string}`;
    const account = privateKeyToAccount(key);
    const client = new x402Client().register(
      "eip155:*",
      new ExactEvmScheme(account),
    );
    const pay = wrapFetchWithPayment(fetch, client);
    
    // The proxy URL shown with your API key, then the seller's URL
    // with its scheme left out.
    const proxy = need("VULSIGHT_PROXY_URL");
    const seller = "vulsight-guard.vercel.app/merchant/dataset";
    const res = await pay(proxy + seller);
    // 200 allowed or 200 review_approved with the dataset,
    // 403 denied with the reasons, or 402 review_pending.
    const status = res.headers.get("guard-status");
    console.log(res.status, status, await res.text());
  5. Run it with bun agent.ts, or node agent.ts on Node 22.18 or newer with "type": "module" in your package.json.

    You see: the status, the guard's decision and the dataset.

    Output
    200 allowed {"name": "product-reviews", "rows": 10000, "sample": [...]}

The wallet needs no ETH, since the seller's payment service pays the gas. The proxy URL holds a secret token, so keep it out of shared logs and screenshots. If you did not save it, rotate the key on the Keys page for a new one. Rotating replaces the API key too.

The first words the program prints are the HTTP status and the guard's decision.

It printsMeansNext step
200 allowedThe guard allowed the payment, and the seller sent the dataset.Nothing. The payment is on Home.
403 deniedThe guard refused the payment, so nothing was paid. The body lists the reasons.Look each reason up on Reason codes. Do not pay the seller another way.
402 review_pendingThe payment waits for a person on Review. Nothing was paid yet.Approve it, then run the program again. Step 4 shows how.
A denied run of the SDK demo agent, recorded on test USDC under an older policy, so its cap differs from a new key's. The guard stops that agent before it signs. Through the proxy URL your client signs, and the guard answers 403 without forwarding the payment.

4. See a review

Hold one payment on purpose, so you see a review before a real payment needs you.

  1. On Policy, set Hold over (USDC) to 0.01 and press Save policy.

    You see: "Saved. The next decision runs under these rules."

  2. Run bun agent.ts again, and open Review.

    You see: the payment as a card on Review. Its reason reads "The amount 0.05 USDC is over your hold threshold of 0.01 USDC, so it waits for a person."

  3. Press Approve. If the program printed 402 review_pending already, run it once more.

    You see: 200 review_approved and the dataset.

  4. Set Hold over (USDC) back to 0 and save.

    You see: the next run prints 200 allowed again.

The proxy holds the payment up to 35 seconds while it waits for you. Approve in that time, and the first run prints 200 review_approved. The review page works from a phone. The Reviews page covers the other buttons and the review timeout.

5. Find it on Home

Open Home. Once your agent has a decision outside the demo buttons, Home opens on Today. Recent decisions shows the five newest, and All decisions lists every one. Open one for Why this decision, with each reason.

Home for a new agent after two test payments: the key row, the ways in, and an approved and a denied row under Recent decisions
Home in an earlier release, before it opened on Today. After steps 2 to 4, yours opens on Today, with Recent decisions under it.

The same decisions come back over the API, newest first.

Terminal
curl -s -H "authorization: Bearer $VULSIGHT_API_KEY" \
  "https://vulsight-guard.vercel.app/api/v1/decisions?limit=1"

You see: an array with your latest decision. An empty array means this account has no decisions yet.

If it fails

Find the line the program printed that matches a row. curl prints only the body, so match its error code.

You seeMeansNext step
Set EVM_PRIVATE_KEY before starting the agent.The shell that runs the program has no wallet key. The same line names VULSIGHT_PROXY_URL when that one is missing.For VULSIGHT_PROXY_URL, run its export line from step 3 again. For EVM_PRIVATE_KEY, the wallet commands in step 3 make a new wallet, so request test USDC for its address at the Circle faucet before you run the program again.
{"error":{"code":"missing_api_key"This shell has no VULSIGHT_API_KEY. See missing_api_key.Run the export line from step 1 in this shell, then the command again.
{"error":{"code":"unknown_api_key"The key is mistyped or was rotated. The message says so if you sent the proxy token instead. See unknown_api_key.Copy the key again. If you lost it, rotate the key on the Keys page.
{"error":{"code":"revoked_api_key"Someone revoked this key on the Keys page. See revoked_api_key.Create a new key on the Keys page.
401 null {"error":{"code":"unknown_proxy_token"The proxy URL is mistyped, or the key was rotated. See unknown_proxy_token.Copy the proxy URL again. If you lost it, rotate the key on the Keys page.
401 null {"error":{"code":"revoked_proxy_token"Someone revoked this key on the Keys page. See revoked_proxy_token.Create a new key on the Keys page.
402 allowedThe guard allowed the payment, and the seller refused it. Most often the wallet holds no test USDC yet.Request test USDC for the wallet at the Circle faucet, then run the program again.

Next steps