Give an agent a wallet

An agent on Tollgate is a wallet with rules. It holds a balance, carries a tg_sk_… API key, and every purchase it attempts is checked server-side against your spending rules before any money moves. This guide takes you from zero to an agent that buys paid content over x402, on demand and on a schedule, with every attempt in a ledger you can export.

1. Create the agent

Go to /agents, type a name into the "New agent wallet" form (for example briefing-bot), press Create agent. That single insert gives the agent everything it needs:

There is no key ceremony and no onchain setup. Demo credits are custodial platform credits (tollgate-credit scheme), not real USDC; the go live guide covers real onchain settlement with the exact scheme.

2. Fund it

Every new agent starts with $10.00. When it runs low, press Top up on the agent console or wallet page: each press adds another $10.00 of demo credits. Balances are tracked in micro-USDC (1,000,000 micro = $1.00) and the debit is atomic: the settlement transaction locks the agent row, checks the balance, debits the agent, and credits the publisher in one transaction. An agent can never spend below zero.

3. Spending rules, exactly as enforced

Rules live on the wallet page (/agents/:id/wallet) under "Spending rules" and are enforced in settleCustodial for every custodial payment, in this order:

  1. Domain allowlist. The resource's domain is <slug>.tollgate.site. If the allowlist is non-empty and no entry matches, the payment is blocked with domain_not_allowed. Entries match exactly, or by *. wildcard suffix (*.tollgate.site matches any subdomain). An empty allowlist allows every domain.
  2. Ask above (one-time human approval). If the price is above the ask-above threshold, the engine looks for an existing approval for this agent, this resource, at this exact amount. If none exists, it records a pending_approval row (deduplicated: a retrying agent never piles up multiple approval requests for the same resource), notifies you, and returns 402 payment_requires_approval. If you have approved it, the approval is consumed and the purchase proceeds. One approval releases exactly one purchase.
  3. Max per request. Skipped when the payment is consuming an approval: a human OK trumps the per-request cap. That is why an approved $0.40 purchase settles under a $0.05 cap while an unapproved $0.09 one is blocked with over_max_per_request.
  4. Daily budget. A hard stop, even for approved purchases. The sum of today's paid payments plus this price must stay at or under the budget, otherwise the attempt is blocked with daily_budget_exceeded. "Today" is the database day boundary, UTC in production.
  5. Funds. Inside the settlement transaction, if the locked balance is below the price the attempt is recorded as failed with reason insufficient_funds and the request gets 402 insufficient_funds.

Every rejected attempt lands in the ledger too, with its status and block reason. The 402 body the agent receives carries the machine-readable error code:

Error code Meaning Ledger status
domain_not_allowed Resource domain not on the allowlist blocked
payment_requires_approval Price above ask-above, owner approval requested pending_approval
over_max_per_request Price above the per-request cap, no approval blocked
daily_budget_exceeded Would push today's paid total over budget blocked
insufficient_funds Wallet balance below the price failed
unknown_api_key The tg_sk_… key matched no agent not recorded

Edit the numbers in the "Spending rules" card and save; they apply to the very next payment attempt.

4. The tg_sk key

The tg_sk_… key is the agent's payment credential: whoever holds it can spend the agent's balance, within the rules above. Retrieve it with your publisher key (tg_pk_…, shown on your dashboard):

export TOLLGATE_PUBLISHER_KEY=tg_pk_your_publisher_key
npx tollgate agents

or over HTTP:

curl -s https://<your-app>/api/agents \
  -H "Authorization: Bearer tg_pk_your_publisher_key"

The key also authenticates the status endpoint, useful for an agent that wants to check its own budget before spending:

curl -s https://<your-app>/api/agent-status \
  -H "Authorization: Bearer tg_sk_your_agent_key"
# → { "balance": "$9.99", "spentToday": "$0.01", "dailyBudget": "$5",
#     "maxPerRequest": "$0.05", "askAbove": "$0.25", "allowedDomains": ["*.tollgate.site"], ... }

Treat it like a bearer token with a spending limit: rotate by creating a new agent if it leaks. Losses are capped by the rules, which is the point of giving agents wallets instead of your card.

5. Pay with curl

The whole protocol is two HTTP requests. First, ask for a paid resource without payment (send Accept: application/json; a browser-style Accept: text/html gets the free human preview instead of a 402):

curl -si https://<your-app>/r/eu-rates/today -H "Accept: application/json"

You get HTTP/1.1 402 Payment Required with display headers (x402-price: $0.004, x402-asset: USDC) and a JSON offer. The part that matters for credit payments:

{
  "x402Version": 1,
  "error": "X-PAYMENT or PAYMENT-SIGNATURE header is required",
  "accepts": [
    { "scheme": "exact", "network": "base-sepolia", "...": "onchain USDC option" },
    {
      "scheme": "tollgate-credit",
      "network": "tollgate",
      "maxAmountRequired": "4000",
      "asset": "USDC-CREDIT",
      "extra": { "hint": "Pay with a Tollgate agent API key: payload = { \"apiKey\": \"tg_sk_...\" }" }
    }
  ]
}

Second, retry with an X-PAYMENT header: base64 of a JSON envelope naming the scheme and carrying the key. This exact snippet works as written:

KEY=tg_sk_your_agent_key
BASE=https://<your-app>

PAYMENT=$(printf '{"x402Version":1,"scheme":"tollgate-credit","network":"tollgate","payload":{"apiKey":"%s"}}' "$KEY" | base64 | tr -d '\n')

curl -si "$BASE/r/eu-rates/today" \
  -H "Accept: application/json" \
  -H "X-PAYMENT: $PAYMENT"

On success you get 200 OK, the content, and two headers: x402-receipt: tg_9f2e4c1a (the receipt id, also in the ledger) and X-PAYMENT-RESPONSE, a base64 JSON settlement proof:

{ "success": true, "transaction": "credit:tg_9f2e4c1a", "network": "tollgate", "payer": "incr:0x..." }

If a rule fires you get 402 again with the error code from the table above, and the attempt is already in your ledger. Nothing was charged: a miss on the path is never charged either, path resolution happens before settlement.

6. Pay from code with payFetch

The SDK wraps the two-request dance in one call. payFetch fetches, and only if it hits a 402 does it build the payment header and retry once:

import { payFetch } from 'tollgate';

const { response, paid, receipt, settlement } = await payFetch(
  'https://<your-app>/r/eu-rates/today',
  { apiKey: process.env.TOLLGATE_API_KEY },  // tg_sk_…
);

console.log(paid);      // true if a 402 was paid, false if the URL was free
console.log(receipt);   // "tg_9f2e4c1a"
console.log(settlement); // decoded X-PAYMENT-RESPONSE
console.log(await response.text());

Scheme selection: passing apiKey uses tollgate-credit; passing only evmKey (an 0x… private key) switches to the onchain exact scheme and signs an EIP-3009 authorization with viem; scheme: 'credit' | 'exact' forces either. buildCreditHeader(apiKey) is exported separately if you want to construct the header yourself.

The same flow from the terminal:

export TOLLGATE_API_KEY=tg_sk_your_agent_key
npx tollgate get https://<your-app>/r/eu-rates/today

7. The live console and Run now

Each agent has a console at /agents/:id. Press Run now and the demo runner executes a real shopping trip, not a mock: it selects up to three of the cheapest live resources in the market (preferring ones you do not own, so it works on a fresh account), hits each URL, receives a real 402, and pays with a real X-PAYMENT header through the full engine. Rules are enforced, receipts are issued, and every purchase lands in the ledger. Only the pacing between log lines is theatrical.

The console streams the run log live: the GET, the 402 · $0.004 · USDC, the paid $0.004 · receipt tg_…, the 200 OK. The runner also mirrors the per-request cap client-side and declines before sending payment when a price is over the cap, the way a well-behaved agent should; the server would have blocked it anyway.

8. Schedules

The schedule field on the console accepts three forms, checked by a one-minute scheduler tick:

A daily agent runs on the first tick at or after the target time that day; an every N agent runs when at least N minutes have passed since its last scheduled run. The scheduler claims the slot before running, so a crash-restart loop cannot double-run an agent. Anything else in the field is rejected with the grammar in the error message.

9. Notifications

The bell (/notifications) collects everything that needs your attention, newest first; opening the page marks all read. Agent activity produces three kinds:

10. Ledger, approvals, CSV export

The wallet page at /agents/:id/wallet shows this month's totals (spent, purchases, blocked count) and the full payment ledger, 12 rows per page: paid purchases with receipts, plus every blocked, failed, and pending attempt with its reason.

Rows in status pending_approval show a Needs OK badge and an Approve button. Approving flips the row to approved; the agent completes the purchase on its next try (retry the request, press Run now, or wait for the schedule). Remember the two settlement subtleties: the approval bypasses the per-request cap but not the daily budget, and it is consumed by exactly one purchase.

Export ledger downloads the complete history as CSV:

timestamp,resource,path,receipt,amount_usd,status,scheme,network,tx_ref
"2026-07-12T07:00:14.000Z","Dutch contract boilerplate","/updates","tg_9f2e4c1a","0.020000","paid","tollgate-credit","tollgate","credit:tg_9f2e4c1a"

Amounts are in USD with six decimals; tx_ref is credit:<receipt> for custodial payments and a real transaction hash for onchain exact payments. Feed it to your accounting, or diff it against what your agent claims it spent.

Where to next