CLI and SDK
The tollgate npm package ships both a command line tool and a small SDK in one module: publish paid resources from a folder, and fetch x402-protected URLs with automatic payment from any Node script. It requires Node 20 or newer. The only dependency is viem, and it is loaded lazily, only when you pay on-chain with the exact scheme.
npx tollgate # prints usage and the active base URL
# or, from a checkout of this repo:
node sdk/bin/tollgate.js
Environment variables
| Variable | Format | Used by | Purpose |
|---|---|---|---|
TOLLGATE_URL |
URL | publish, me, agents |
Platform base URL. Defaults to https://web-production-095eb.up.railway.app. A trailing slash is stripped. get and pay take full URLs, so they ignore this. |
TOLLGATE_PUBLISHER_KEY |
tg_pk_ + 32 hex chars |
publish, me, agents |
Publisher key, sent as Authorization: Bearer. Find it on your storefront dashboard. Required for these commands, the CLI exits 1 without it. |
TOLLGATE_API_KEY |
tg_sk_ + 32 hex chars |
get, pay |
Agent key for the custodial tollgate-credit scheme. The platform settles from the agent's balance. |
TOLLGATE_EVM_KEY |
0x… private key |
get, pay |
EVM private key for the exact scheme, real USDC signed locally via EIP-3009. The key never leaves your machine, only the signature is sent. |
Commands
tollgate publish
tollgate publish <dir> --price 0.004 [--title T] [--slug s] [--description D] [--kind folder] [--unit request]
Walks <dir> recursively, collects text files, and POSTs them to POST /api/resources as one resource. Requires TOLLGATE_PUBLISHER_KEY.
Flags:
| Flag | Required | Default | Notes |
|---|---|---|---|
--price |
yes | none | USD per unit, for example 0.004. Minimum 0.001, up to 6 decimal places. |
--title |
no | basename of <dir> |
Trimmed server-side, must be non-empty. |
--slug |
no | slugified title | Lowercased, non-alphanumerics become hyphens, max 40 chars. The resource goes live at /r/<slug>. |
--description |
no | empty | Truncated to 300 characters server-side. |
--kind |
no | folder |
One of feed, folder, dataset, site, tool. Anything else silently falls back to folder. |
--unit |
no | request |
One of request, document, copy, query. Anything else falls back to request. |
Publishing to a slug you already own updates that resource in place (title, description, kind, price, unit, and the full file set are replaced). Publishing to a slug owned by someone else fails with 409 slug_taken.
Which files get picked up:
- Only these extensions:
.md,.txt,.json,.html,.css,.js,.csv,.xml,.yaml,.yml,.svg - Anything else is skipped with a
skipping <path> (binary or unsupported extension)note on stderr - Files and directories whose names start with
.are skipped entirely - Files are read as UTF-8, there is no binary upload path
Server-side caps (from src/routes/publisher-api.ts):
MAX_FILES = 200files per resource, exceeding it returns400 too_many_filesMAX_TOTAL_BYTES = 2 * 1024 * 1024, so 2 MiB of total file content per resource, exceeding it returns400 payload_too_large- The JSON request body itself is capped at 4 MB
- Each file path must match
^\/[\w./-]{0,200}$and must not contain.. - A
contentTypeis guessed from the extension if not supplied (.md,.json,.html,.csv,.txtget proper types, everything elseapplication/octet-stream)
On success the CLI prints the live URL, price, unit, and paths:
live at https://web-production-095eb.up.railway.app/r/eu-rates
price $0.004 / request · paths: /today /history.csv
The first line says updated at … instead of live at … when an existing slug was overwritten.
tollgate get
tollgate get <url> [--scheme credit|exact]
Fetches a URL through payFetch (see below). If the server answers 200 directly, the body is printed and nothing is paid. If it answers 402, the CLI pays once using the selected scheme and prints the body to stdout. The receipt line goes to stderr, so piping stdout gives you clean content:
TOLLGATE_API_KEY=tg_sk_... npx tollgate get https://web-production-095eb.up.railway.app/r/eu-rates/today > today.md
# stderr: paid · receipt tg_9f2e4c1a
With --scheme exact the receipt line also includes the transaction hash when the settlement response carries one.
Scheme selection: --scheme accepts exactly credit or exact. Any other value (or no flag) falls through to auto-detection: exact if TOLLGATE_EVM_KEY is set and TOLLGATE_API_KEY is not, otherwise credit.
tollgate pay
tollgate pay <url> [--scheme credit|exact]
Same as get, but prints only the receipt line, never the body. Useful for scripted spending checks. On a free resource it prints nothing and exits 0.
tollgate me
TOLLGATE_PUBLISHER_KEY=tg_pk_... npx tollgate me
Prints your display name, email, and balance, then one line per resource with live status, title, price per unit, and URL. Calls GET /api/me.
tollgate agents
TOLLGATE_PUBLISHER_KEY=tg_pk_... npx tollgate agents
Lists your agents with id, name, balance, wallet address, the tg_sk_ API key, and the schedule if one is set. Calls GET /api/agents.
Flag parsing
Flags take the next argument as their value (--price 0.004). A flag immediately followed by another flag becomes a boolean true, so always give value-flags an explicit value.
Exit behavior
- Exit 0 on success. Running
tollgatewith no command prints usage and also exits 0. - Exit 1 on every error: missing arguments, missing env keys, HTTP errors from the API, failed payments, and unknown commands (which print usage first). All error text is written to stderr prefixed with
tollgate:. - For
getandpay, a non-200 response after paying exits 1 with the status and the server'serroranddetailfields. - Content goes to stdout, diagnostics (receipts, skip notes, errors) go to stderr.
SDK
import { payFetch, buildCreditHeader, buildExactHeader } from 'tollgate';
payFetch(url, opts)
Fetches a URL and transparently pays a 402 exactly once: first request without payment, and if the response is 402 it reads the JSON offer, builds an X-PAYMENT header for the chosen scheme, and retries the same URL once. There is no retry loop beyond that single paid attempt.
Options:
| Option | Type | Meaning |
|---|---|---|
apiKey |
string | tg_sk_ agent key for the tollgate-credit scheme. |
evmKey |
string | 0x… private key for the exact scheme. |
scheme |
'credit' or 'exact' |
Force a scheme. Omitted: exact when only evmKey is given, otherwise credit. |
fetchFn |
function | Replacement for global fetch, useful in tests. |
headers |
object | Extra request headers, merged over the default Accept: application/json. |
Return shape:
| Field | Present | Meaning |
|---|---|---|
response |
always | The final Response. The first response if no 402 happened, the post-payment response otherwise. |
paid |
always | false if the first response was not a 402. After a payment attempt, true only when the retried request returned 200. |
offer |
after a 402 | The parsed 402 body, including the accepts array of payment requirements. |
receipt |
after a 402 | Value of the x402-receipt response header, undefined if absent. |
settlement |
after a 402 | The x-payment-response header, base64-decoded and JSON-parsed. For exact payments it includes the transaction hash under transaction. |
Errors are thrown, not returned: no apiKey when the credit scheme is needed, no evmKey for exact, an offer that lacks the requested scheme, or an unknown scheme value all throw with a descriptive message.
buildCreditHeader(apiKey)
Synchronous. Returns the base64 X-PAYMENT header value for the custodial scheme: a JSON envelope with x402Version: 1, scheme: "tollgate-credit", network: "tollgate", and the key in payload.apiKey. Use it when you want to drive fetch yourself.
buildExactHeader(requirement, evmKey)
Async. Takes one entry from the offer's accepts array (the one with scheme: "exact") and a private key, signs an EIP-3009 TransferWithAuthorization with viem, and returns the base64 header value. Behavior details, all from sdk/index.js:
- Supported networks:
base(chain id 8453) andbase-sepolia(84532). Any otherrequirement.networkthrows. - The authorization is valid from 60 seconds in the past until
maxTimeoutSeconds(default 60) plus 240 seconds in the future, with a random 32-byte nonce. valueisrequirement.maxAmountRequired,toisrequirement.payTo,verifyingContractisrequirement.asset.- The EIP-712 domain name and version come from
requirement.extrawith fallbacks"USDC"and"2". Always let the offer drive these, mainnet Base USDC uses a different domain name than testnet.
The signature is created locally, nothing is broadcast by the SDK. Settlement happens server-side via the facilitator when you submit the header.
Examples
Credit: pay with an agent key
// buy.mjs, run with: node buy.mjs
import { payFetch } from 'tollgate';
const url = 'https://web-production-095eb.up.railway.app/r/eu-rates/today';
const { response, paid, receipt } = await payFetch(url, {
apiKey: process.env.TOLLGATE_API_KEY,
});
if (!response.ok) {
const body = await response.json().catch(() => ({}));
throw new Error(`${response.status} ${body.error ?? 'payment failed'}`);
}
if (paid) console.error(`paid, receipt ${receipt}`);
console.log(await response.text());
TOLLGATE_API_KEY=tg_sk_your_agent_key node buy.mjs
Exact: pay with real USDC from a funded wallet
// buy-onchain.mjs, run with: node buy-onchain.mjs
import { payFetch } from 'tollgate';
const url = 'https://web-production-095eb.up.railway.app/r/eu-rates/today';
const { response, paid, receipt, settlement } = await payFetch(url, {
evmKey: process.env.TOLLGATE_EVM_KEY,
scheme: 'exact',
});
if (!paid) {
const body = await response.json().catch(() => ({}));
throw new Error(`${response.status} ${body.error ?? 'payment failed'}`);
}
console.error(`paid, receipt ${receipt}, tx ${settlement?.transaction}`);
console.log(await response.text());
# The wallet must hold USDC on the offer's network (Base Sepolia by default).
TOLLGATE_EVM_KEY=0x...buyer_private_key... node buy-onchain.mjs
The same two flows are available without writing code: npx tollgate get <url> and npx tollgate get <url> --scheme exact. See Go live with real USDC for funding a test wallet.