# OneSource REST API Documentation
> Reference and guides for the OneSource REST API and MCP Server: REST and MCP access to live Ethereum data. API-key subscription (Stripe), x402, or MPP payment.
This file contains all documentation content in a single document following the llmstxt.org standard.
## Allowance
Returns how much of an ERC20 token a spender is approved to transfer from an owner via eth_call on allowance(owner, spender). Use before calling transferFrom, or to audit outstanding approvals for security review.
Request
---
## Block number
Returns the latest mined Ethereum block height via eth_blockNumber against a live node. No caching - every call queries the upstream RPC fresh. Use this to sync an agent to the chain tip, to detect progress or stalls, or as a cheap liveness ping before issuing more expensive RPC calls.
Request
---
## Block by number
Returns the block header plus its transaction hashes for a given block number, tag (latest, pending, safe, finalized), or hex block number via eth_getBlockByNumber. Live from the upstream RPC node.
Request
---
## Call (simulate)
Simulates a contract read via eth_call - returns the raw ABI-encoded result without submitting a transaction. Pass 'to' (contract address) and 'data' (ABI-encoded calldata); optional 'from', 'value', and 'block' override the caller, msg.value, and block tag.
Request
---
## Chain ID
Returns the EIP-155 chain ID via eth_chainId, queried live from the upstream RPC node. Verify a backend is serving the expected chain before signing an EIP-155 transaction.
Request
---
## Contract code
Returns the deployed bytecode at an address via eth_getCode. Empty bytecode means the address is an EOA (wallet); non-empty means it's a contract. Response includes a bytecode_size field for quick classification.
Request
---
## Contract introspection
Introspects a contract via eth_call - probes name(), symbol(), decimals(), and supportsInterface() to report what token standards (ERC20, ERC721, ERC1155) the contract claims to implement. Ideal for classifying an unknown contract.
Request
---
## ENS resolve
Resolves a *.eth ENS name to its address (forward lookup) or an address to its primary ENS name (reverse lookup) via eth_call on the ENS Registry and Resolver contracts. Auto-detects direction from the input format.
Request
---
## ERC1155 balance
Fetches an account's balance of a specific ERC1155 token_id by eth_call-ing balanceOf(address, uint256). Supports any ERC1155 contract - gaming items, editions, semi-fungible assets.
Request
---
## ERC20 balance
Fetches an account's balance of a specific ERC20 token by eth_call-ing balanceOf(address). Returns the raw balance plus the token's decimals and symbol for convenience. Works for any ERC20 contract.
Request
---
## ERC20 transfers
Specialized eth_getLogs query that filters on the ERC20 Transfer(address,address,uint256) topic and optionally narrows to a token contract or a specific wallet (sender or recipient). Decodes the indexed from/to and raw value.
Request
---
## ERC721 tokens
Lists every ERC721 token_id held by an owner by combining balanceOf(owner) with tokenOfOwnerByIndex(owner, i) across a batched set of eth_call requests. Requires the contract to implement the ERC721Enumerable extension.
Request
---
## Estimate gas
Estimates the gas a transaction would consume via eth_estimateGas. Accepts the same 'to', 'data', 'from', 'value' fields as /call. A failing estimate usually means the transaction would revert - use this to pre-flight a complex interaction before paying gas.
Request
---
## Event logs
Queries contract event logs via eth_getLogs with optional filters on contract address, topic0 (event signature hash), and block range. Returns the raw logs - address, topics, data, block_number, tx_hash.
Request
---
## Live balance
Fetches the wallet's native ETH balance (eth_getBalance) and, when you pass a comma-separated list of ERC20 contract addresses in the optional 'tokens' param, batches a balanceOf eth_call per token alongside symbol() and decimals(). Returns a single snapshot of ETH plus any requested token balances. Live from the upstream RPC node.
Request
---
## Network info
Batches eth_chainId, eth_blockNumber, and eth_gasPrice into a single round-trip to the upstream RPC node. Use this instead of three separate calls when bootstrapping an agent, probing liveness, or pre-flighting a transaction.
Request
---
## NFT metadata
Calls tokenURI(tokenId) (ERC721) or uri(tokenId) (ERC1155) via eth_call, resolves ipfs:// URIs through a gateway, and returns the resolved metadata JSON alongside the raw URI. Works for most compliant NFT contracts.
Request
---
## NFT owner
Returns the current owner of a specific ERC721 NFT via eth_call on ownerOf(tokenId). Reverts for unminted or burned token IDs - the error surfaces as an RPC error.
Request
---
## Nonce
Returns the number of transactions sent from this address via eth_getTransactionCount. Pass block="pending" (default) for the next-nonce an EIP-1559 or legacy transaction should use, or a confirmed tag for historical nonces. Use this to build a correctly-nonced transaction or to diagnose stuck transactions.
Request
---
## Pending block
Returns the pending block as seen by the upstream RPC node - transactions that are in the mempool but not yet included on-chain, via eth_getBlockByNumber("pending"). Use this to monitor unconfirmed activity, spot front-running risk, or peek at what a node plans to mine next.
Request
---
## Proxy implementation
Reads well-known proxy storage slots via eth_getStorageAt to detect EIP-1967, UUPS, and Transparent proxy patterns, and returns the current implementation address. Also surfaces the admin slot when present.
Request
---
## Transaction receipt
Returns the mined transaction receipt - status (success/failed), gas used, effective gas price, emitted event logs, and block inclusion data - via eth_getTransactionReceipt. Returns null if the transaction is pending or unknown to the node.
Request
---
## Storage slot
Reads a 32-byte storage slot directly from a contract's storage via eth_getStorageAt. Useful for inspecting EIP-1967 proxy implementation slots, packed variables, and storage layouts that aren't exposed through a public function.
Request
---
## Total supply
Returns the total supply of a token contract via eth_call on totalSupply(). Works for ERC20 (raw token units) and ERC721 (count of minted NFTs) contracts that implement the standard.
Request
---
## Transaction
Returns the full transaction object - from, to, value, gas, nonce, calldata, block inclusion - via eth_getTransactionByHash. Returns null if the transaction is pending eviction or unknown to the node.
Request
---
## API Reference
This section is auto-generated from the OneSource OpenAPI 3.1 spec served at [`https://api.onesource.io/openapi.json`](https://api.onesource.io/openapi.json).
Regenerate locally with:
```bash
npm run gen-api
```
That command runs `docusaurus-plugin-openapi-docs` against the spec (or the committed snapshot in `openapi-snapshot.json` if you're offline) and writes one MDX page per endpoint into this folder, grouped by tag.
## Tag groups
OneSource tags operations along several axes: `blockchain`, `ethereum`, `onesource`, `rpc`, `live-data`, plus narrower domain tags (`token`, `smart-contract`, `ens`, `identity`, `erc20`, …). The generated sidebar groups by the most specific tag.
## Payment metadata
Each endpoint carries Bazaar-specific extensions:
- `x-payment-info`: price (USDC), `payTo` address, supported protocols (x402, MPP)
- `x-keywords`: search keywords surfaced by the MCP and Bazaar
- `x-use-cases`: natural-language examples of when to call this endpoint
The reference pages render these alongside the standard parameter / response tables.
## Until the auto-generated pages land
The complete endpoint catalog is also available in machine-readable form at:
- **OpenAPI spec:** [`/openapi.json`](https://api.onesource.io/openapi.json) (live) or the committed snapshot at `openapi-snapshot.json`
- **AI-optimized index:** [`/llms-full.txt`](pathname:///llms-full.txt)
---
## OneSource - Pay-per-call Ethereum RPC for Agents
Pay-per-call Ethereum RPC for AI agents, on mainnet and the Sepolia testnet (select with the network query parameter; defaults to ethereum mainnet). Every endpoint proxies a single live RPC call - wallet and token balances, blocks, transactions, receipts, event logs, ENS name resolution, contract introspection, and NFT metadata - returning fresh data from a live Ethereum node. No API keys, no accounts, no subscriptions; pay per request in USDC on Base via x402, or pathUSD or USDC.e on Tempo via MPP.
---
## Getting Started
Three access paths share the same REST API. Pick the one that matches your application:
## API Key
Sign up at [app.onesource.io/signup](https://app.onesource.io/signup), subscribe to the Developer plan, and authenticate with a Bearer API key. Predictable per-month pricing billed through Stripe. Your plan sets an account-wide request quota, shared across every key you create.
```bash
curl https://api.onesource.io/api/chain/network-info \
-H "Authorization: Bearer sk_your_api_key_here"
```
→ [API key setup guide](./api-key/subscribe)
## MCP Server
If your workflow is in Claude Code, Claude Desktop, Cursor, or Windsurf, install the [`@one-source/mcp`](https://www.npmjs.com/package/@one-source/mcp) MCP server. It exposes the OneSource REST API as 30 MCP tools, all prefixed `1s_` (live-chain tools end in `_live`).
```bash
claude mcp add onesource -e ONESOURCE_API_KEY=sk_… -- npx -y @one-source/mcp@latest
```
Works with either an API key (Bearer) or an x402 wallet (`X402_PRIVATE_KEY`).
→ [MCP setup guide](./mcp/install)
## x402 / MPP
Pay per call from an agent wallet, no signup. Two protocols:
- **x402** on Base mainnet, paid in USDC. Use the [`@x402/fetch`](https://www.npmjs.com/package/@x402/fetch) client or AgentCash.
- **MPP** on Tempo, paid in USDC.e (or pathUSD). Tempo CLI or MPP-aware fetch clients work.
Per-endpoint pricing is published in `meta.cost_usdc` on every response and in the `x-payment-info` extension on each OpenAPI operation.
→ [x402 / MPP setup guide](./x402-and-mpp/wallet-setup)
## Verify Access
Whichever path you pick, the same one-call sanity check works for all of them:
```bash
curl https://api.onesource.io/api/chain/network-info \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
```
Expected response:
```json
{
"data": { "chain_id": "0x1", "block_number": "0x14abcde", "gas_price": "0x3b9aca00" },
"error": null,
"meta": { "endpoint": "/api/chain/network-info", "request_id": "..." }
}
```
Calls run against Ethereum mainnet by default. Add `?network=sepolia` to any `/api/chain/*` request to target the Sepolia testnet instead. See [Choosing a network](./direct-rest/first-request#choosing-a-network).
---
## Make your first request
A single HTTP call confirms your subscription is active and your API key works.
## curl
```bash
curl https://api.onesource.io/api/chain/network-info \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
```
Expected response:
```json
{
"data": {
"chain_id": "0x1",
"block_number": "0x14abcde",
"gas_price": "0x3b9aca00"
},
"error": null,
"meta": { "endpoint": "/api/chain/network-info", "request_id": "..." }
}
```
`block_number` is the live mainnet tip; it should be close to the current block reported on https://etherscan.io. To hit the Sepolia testnet instead, add `?network=sepolia` to the URL (the `chain_id` then comes back as `0xaa36a7`). See [Choosing a network](../direct-rest/first-request#choosing-a-network).
## Node.js (fetch)
```ts
const res = await fetch('https://api.onesource.io/api/chain/network-info', {
headers: { Authorization: `Bearer ${process.env.ONESOURCE_API_KEY}` },
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(`Chain ${parseInt(data.chain_id, 16)} at block ${parseInt(data.block_number, 16)}`);
```
## Python (httpx)
```python
resp = httpx.get(
'https://api.onesource.io/api/chain/network-info',
headers={'Authorization': f"Bearer {os.environ['ONESOURCE_API_KEY']}"},
)
resp.raise_for_status()
body = resp.json()
print(f"Chain {int(body['data']['chain_id'], 16)} at block {int(body['data']['block_number'], 16)}")
```
## Go
```go
req, _ := http.NewRequest("GET", "https://api.onesource.io/api/chain/network-info", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("ONESOURCE_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil { return err }
defer resp.Body.Close()
var body struct {
Data struct{ ChainID, BlockNumber, GasPrice string } `json:"data"`
Error *struct{ Code, Message string } `json:"error"`
Meta map[string]any `json:"meta"`
}
json.NewDecoder(resp.Body).Decode(&body)
```
## Response envelope
Every `/api/*` response uses the same shape:
```json
{ "data": { ... }, "error": null, "meta": { "endpoint": "...", "request_id": "..." } }
```
On failure, `data` is `null` and `error` is an object with `code` and `message`. See [Response envelope](../direct-rest/response-envelope) for the full reference.
## What can go wrong
| Status | What it means | What to do |
|---|---|---|
| `401` | Missing or invalid `Authorization` header. | Confirm the env var is set and holds the full key you copied when you created it. If you've lost the key, create a replacement on the dashboard. |
| `403` | Your plan doesn't cover this endpoint (e.g. still on the Sandbox tier). | Subscribe to the Developer plan in [app.onesource.io](https://app.onesource.io), or call the endpoint with x402 / MPP instead. |
| `5xx` | Upstream RPC node, facilitator, or unhandled service-side issue. | Retry with exponential backoff. |
Your per-second rate limit is advisory (watch `X-RateLimit-Remaining` rather than waiting for a `429`), but your monthly quota is enforced: once it's exhausted, calls are rejected until the next cycle. See [Rate limits and quotas](./rate-limits-and-quotas).
## Next
- Browse the [API Reference](/api-reference/) for the full endpoint catalog.
- If you're wiring this into an AI assistant, switch to the [MCP server](../mcp/install) instead of raw REST.
---
## Rate limits and quotas
API-key subscriptions come with predictable account-level limits. Exact numbers depend on your plan; your active limits are shown on the OneSource dashboard under **Your Plan → Plan**. Both limits are enforced **per account** and shared across every API key you create (they key on your license, not on the individual key), and they have the same shape on every plan:
| Limit | Scope |
|---|---|
| Sustained request rate (req/s) | Per account, rolling 1-second window |
| Monthly request quota | Per account, per calendar month |
## Rate-limit headers
Every authenticated `/api/*` response carries three headers reflecting your current key state:
| Header | Value |
|---|---|
| `X-RateLimit-Limit` | Calls allowed in the current window (per your plan). |
| `X-RateLimit-Remaining` | Calls remaining in the current window. |
| `X-RateLimit-Reset` | Unix timestamp (seconds) when the window resets. |
Example:
```bash
$ curl -i https://api.onesource.io/api/chain/network-info \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
HTTP/2 200
x-ratelimit-limit: 1000
x-ratelimit-remaining: 987
x-ratelimit-reset: 1715731200
...
```
These headers are emitted by the API gateway on every Bearer-key response; read them on the success path and back off proactively when `Remaining` trends low. Calls authenticated with x402 or MPP don't carry them (those flows are per-call settled, not quota-bound).
## Current enforcement
**The per-second rate limit is advisory; the monthly quota is enforced.** The gateway tracks your account's request rate and reports it via `X-RateLimit-*`, but it does not currently reject calls with `429` for exceeding the per-second rate. The monthly quota is different: once your account's monthly request count is used up, further calls are rejected until the next calendar month. In practice that means:
- The `X-RateLimit-*` headers are accurate and worth honoring proactively in client logic.
- Bursting past your per-second rate won't (today) get a `429` back, but sustained abuse is still subject to load-balancer-level protection.
- Don't run your account's monthly quota to zero: once it's spent, every key on the account stops authenticating until the cycle resets.
Hard per-second enforcement with `429` + `Retry-After` is on the roadmap. When it lands, this page will document the exact response shape; until then, the headers are the source of truth for rate, and the dashboard usage bar is the source of truth for your monthly quota.
## Backoff pattern
Even without `429` from the gateway, you'll hit transient `502`/`503` from upstream RPC nodes and the facilitator under load. Exponential backoff with jitter handles them well:
```ts
async function callWithRetry(url: string, init: RequestInit, attempt = 0): Promise {
const res = await fetch(url, init);
if (res.status < 500 || attempt >= 5) return res;
const backoff = 2 ** attempt * 500 + Math.random() * 250;
await new Promise(r => setTimeout(r, backoff));
return callWithRetry(url, init, attempt + 1);
}
```
See [Error codes](/guides/error-codes) for the full retry catalog.
## Monitoring usage
The OneSource dashboard at [app.onesource.io](https://app.onesource.io) → **Your Plan → Account** shows a **Current Cycle Utilization** bar: the share of your account's monthly request allowance used so far this cycle (aggregated across all your keys, not per key). Each API response also carries a `meta.request_id` you can pin to log entries on your side.
Watch the `X-RateLimit-Remaining` header trend in your application logs to spot drift before you exhaust the monthly cap.
## When you need more
If your workload is steady-state above your rate limit or near the monthly cap:
- **Spike or overflow traffic:** route bursts to [pay-per-call with x402 or MPP](../x402-and-mpp/x402-base): no monthly quota, you pay per request in USDC, and it runs in parallel with your Bearer key.
- **More Bearer-key capacity:** the Developer plan is currently the only subscription tier, and there's no in-dashboard upgrade. If one account's quota isn't enough, run a second subscription under a different email, or move the overflow to x402 / MPP.
## Free endpoints
A handful of endpoints are public and don't count against your quota:
- `GET /health`: liveness check
- `GET /api/pricing`: per-endpoint pricing
- `GET /openapi.json` and `GET /.well-known/openapi.json`: OpenAPI spec
- `GET /llms.txt`: LLM-optimized index
---
## Retrieve your API key
# Create and retrieve your API key
You create and manage OneSource API keys on the **OneSource dashboard** at [app.onesource.io](https://app.onesource.io), under **API Keys**. Keys start with `sk_`.
## Create a key (copy it now: it's shown only once)
1. Sign in at [app.onesource.io](https://app.onesource.io).
2. Open **API Keys** and click **Create new API key**. Optionally name it (for example `production` or `staging`).
3. The full `sk_...` value is shown **once**, in a dialog, at creation time. Copy it then and store it somewhere safe.
After you close that dialog, the dashboard only ever shows a **masked prefix** (`sk_xxxx…yyyy`) for the key, never the full value again. There's no "reveal" button on the list and support can't recover the key for you. If you lose a key, you don't retrieve it: you [create a replacement and delete the old one](#replacing-a-key).
## Manage your keys
The **API Keys** table lists every key on your account:
| Column | What it shows |
|---|---|
| **Name** | The label you gave the key (or `default`). |
| **Key** | The masked prefix (`sk_xxxx…yyyy`), enough to tell keys apart. |
| **Created** | When the key was created. |
| **Last used** | When the key last authenticated a request (or `Never`). |
| **Enabled** | A toggle to disable or re-enable the key without deleting it. |
| **Remove** | The trash icon permanently revokes the key. |
- **Disable vs delete:** switch the **Enabled** toggle off to suspend a key while keeping it on the account (useful for parking a key you might bring back); use the trash icon to revoke it for good.
- **Revocation isn't instant.** The API gateway caches successful key validations for up to ~30 seconds, so a disabled or deleted key can keep working for that long before it's rejected.
- Create as many keys as you like (one per environment is a common pattern). All keys on your account **share the same plan rate limit and monthly quota**, see [Rate limits and quotas](./rate-limits-and-quotas).
## Store it in your application
A few patterns that work well:
- **Server-side workloads:** set `ONESOURCE_API_KEY` as an environment variable in your hosting platform (Vercel, Fly.io, Railway, Heroku config vars; ECS task definition; Kubernetes secret).
- **Local development:** copy the key into a `.env` file (and add `.env` to `.gitignore`).
- **CI:** store the key as a masked CI secret and inject as an environment variable at build time.
Treat the key like any other production secret. Don't commit it to source control, and don't log it.
### Node.js
```ts
const apiKey = process.env.ONESOURCE_API_KEY;
if (!apiKey) throw new Error('ONESOURCE_API_KEY is not set');
const res = await fetch('https://api.onesource.io/api/chain/network-info', {
headers: { Authorization: `Bearer ${apiKey}` },
});
```
### Python
```python
api_key = os.environ['ONESOURCE_API_KEY']
resp = httpx.get(
'https://api.onesource.io/api/chain/network-info',
headers={'Authorization': f'Bearer {api_key}'},
)
```
## Replacing a key
There's no in-place "rotate" button, and you can't see an existing key's full value again, so rolling a key over means creating a new one and cutting traffic across before removing the old one:
1. On the dashboard, click **Create new API key** and copy the new `sk_*` value into your secret store.
2. Deploy your application with the new key and confirm it's serving traffic.
3. Optionally switch the old key's **Enabled** toggle off first: if nothing breaks within a minute or so, you've confirmed nothing still depends on it.
4. Once the new key is live, **delete** the old key from the dashboard.
If a key is exposed, don't wait to test: delete (or at least disable) the compromised key immediately, then create and deploy a replacement.
## Next
→ [Make your first request](./first-request).
---
## Sign up for an API key
The OneSource REST API is sold as a SaaS subscription billed through Stripe. You create an account on the OneSource dashboard, confirm your email, and subscribe to the **Developer** plan; once the subscription is active your API key unlocks the full API.
## What you need
- An email address you can receive a confirmation link at.
- A payment card for Stripe Checkout, used when you subscribe to the Developer plan.
## Steps
1. **Open the signup page.** Go to the OneSource dashboard at [app.onesource.io](https://app.onesource.io) and choose **Sign Up**.
2. **Create your account.** Enter your name and email (organization is optional), then complete the reCAPTCHA. There's no password to set; OneSource signs you in by email confirmation.
3. **Confirm your email.** OneSource emails you a confirmation link. The signup screen waits for you to confirm, then signs you in.
4. **Land on the dashboard.** New accounts start on the **Sandbox** tier, which isn't yet active for live API calls.
5. **Subscribe to the Developer plan.** On the dashboard, click **Complete Checkout** and pay through Stripe. The Developer plan activates full API access: the REST API and the MCP Server. Stripe charges your card on its normal monthly cycle and emails receipts.
## Get your API key
Create and copy your key from the **API Keys** section of the dashboard. See [Retrieve your API key](./retrieve-api-key).
## Signing back in
There's no password. To return to the dashboard later, go to [app.onesource.io](https://app.onesource.io), enter your email, and OneSource emails you a single-use sign-in link. Click it to land back on the dashboard.
## Managing your subscription
Your subscription lives under **Your Plan** on the dashboard:
- **Account** shows your email, next billing date, and a **Current Cycle Utilization** bar.
- **Plan** shows your current plan with its per-second rate limit and monthly request allowance (see [Rate limits and quotas](./rate-limits-and-quotas)).
To cancel, open **Your Plan → Plan** and click **Cancel Plan**. The dashboard asks why you're leaving, then schedules the cancellation: your access and full monthly allowance continue until the end of the current billing period, after which you're not charged again. Once a cancellation is scheduled the button becomes **Manage Plan**, which opens the Stripe billing portal where you can update your card. If a renewal payment fails, the dashboard shows the date Stripe will retry.
## Next
→ [Retrieve your API key](./retrieve-api-key) from the dashboard.
---
## First request (direct REST)
The OneSource REST API is plain HTTPS + JSON: any HTTP client works. The same endpoint shape covers all three access paths; only the auth header changes.
## curl
```bash
# Bearer API key (from app.onesource.io/signup)
curl https://api.onesource.io/api/chain/network-info \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
# x402 / MPP / no auth: OneSource will return 402 with payment challenges
curl -v https://api.onesource.io/api/chain/network-info
```
For wallet-paid calls, a custom HTTP client handles the 402 → sign → retry loop. See [x402 on Base](../x402-and-mpp/x402-base) or [MPP on Tempo](../x402-and-mpp/mpp-tempo).
## Node.js (fetch)
```ts
const res = await fetch('https://api.onesource.io/api/chain/live-balance?' + new URLSearchParams({
address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
tokens: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48,0xdAC17F958D2ee523a2206206994597C13D831ec7',
}), {
headers: { Authorization: `Bearer ${process.env.ONESOURCE_API_KEY}` },
});
const { data, error, meta } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(`Request ${meta.request_id} cost ${meta.cost_usdc ?? 'free'} USDC`);
```
## Python (httpx)
```python
resp = httpx.get(
'https://api.onesource.io/api/chain/erc20-balance',
params={
'account': '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
'token': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
},
headers={'Authorization': f"Bearer {os.environ['ONESOURCE_API_KEY']}"},
timeout=10.0,
)
resp.raise_for_status()
body = resp.json()
if body['error']:
raise RuntimeError(body['error']['message'])
print(body['data'])
```
## Go
```go
req, _ := http.NewRequest("GET", "https://api.onesource.io/api/chain/tx/"+hash, nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("ONESOURCE_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil { return err }
defer resp.Body.Close()
var body struct {
Data json.RawMessage `json:"data"`
Error *APIError `json:"error"`
Meta map[string]any `json:"meta"`
}
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { return err }
if body.Error != nil { return fmt.Errorf("%s: %s", body.Error.Code, body.Error.Message) }
```
## Choosing a network
Every `/api/chain/*` endpoint accepts an optional `network` query parameter. It defaults to `ethereum` (mainnet); pass `network=sepolia` to run the same call against the Sepolia testnet.
```bash
# Mainnet (default): the parameter can be omitted
curl "https://api.onesource.io/api/chain/network-info" \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
# Sepolia testnet: add ?network=sepolia
curl "https://api.onesource.io/api/chain/network-info?network=sepolia" \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
```
For POST endpoints, `network` is still a query parameter, so it goes on the URL (for example `…/api/chain/call?network=sepolia`), not in the JSON body. To confirm which chain a backend is on, call `/api/chain/chain-id`: it returns `0x1` for mainnet and `0xaa36a7` for Sepolia.
| Network | `network` value | EIP-155 chain id |
|---|---|---|
| Ethereum mainnet | `ethereum` (default) | `1` (`0x1`) |
| Sepolia testnet | `sepolia` | `11155111` (`0xaa36a7`) |
## POST endpoints
A few endpoints accept JSON bodies, notably `/api/chain/call` (simulate `eth_call`) and `/api/chain/estimate-gas`:
```bash
curl -X POST https://api.onesource.io/api/chain/call \
-H "Authorization: Bearer $ONESOURCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"data": "0x18160ddd"
}'
```
The full request shape for each endpoint is documented in the [API Reference](/api-reference/).
## Next
- [Response envelope](./response-envelope): exact shape of `data`, `error`, `meta`
- [API Reference](/api-reference/): all 25 endpoints
---
## Response envelope
Every `/api/*` response, success or failure, wraps its payload in three fields: `data`, `error`, `meta`.
```json
{
"data": { ... },
"error": null | { "code": , "message": "string" },
"meta": { ... }
}
```
## Success
```json
{
"data": {
"chain_id": "0x1",
"block_number": "0x14abcde",
"gas_price": "0x3b9aca00"
},
"error": null,
"meta": {
"endpoint": "/api/chain/network-info",
"request_id": "00000000abcdef01",
"cost_usdc": "0.001",
"payment_chain": "base",
"payment_token": "USDC"
}
}
```
- `data`: the endpoint's actual response body. Shape varies by endpoint; see the [API Reference](/api-reference/) for each.
- `error`: `null` on success.
- `meta`: per-call telemetry; see below.
## Failure
```json
{
"data": null,
"error": {
"code": 400,
"message": "address param required"
},
"meta": {
"endpoint": "/api/chain/live-balance",
"request_id": "00000000abcdef02"
}
}
```
- `data` is `null`.
- `error.code` is the HTTP status as an integer; branch on it like you would on the status line.
- `error.message` is human-readable English from the handler. Log it, don't parse it.
- `meta.request_id` is logged on the server side; include it in support requests.
Common statuses:
| Status | Typical cause |
|---|---|
| `200` | Success |
| `400` | Missing or malformed parameter |
| `401` | Missing or invalid `Authorization` (returned by the gateway, not the envelope shape; see [Error codes](/guides/error-codes)) |
| `402` | Payment required (x402 / MPP challenge; see [Wallet setup](../x402-and-mpp/wallet-setup)) |
| `403` | Sandbox key on a paid endpoint, or subscription doesn't include this endpoint (gateway shape) |
| `404` | Resource not found on-chain (transaction, block, NFT, etc.) |
| `5xx` | Upstream RPC node, facilitator, or unhandled service-side failure |
`429` is not currently emitted; rate limits are advisory via headers today. See [Rate limits and quotas](../api-key/rate-limits-and-quotas#current-enforcement).
## `meta` fields
| Field | Notes |
|---|---|
| `endpoint` | Always present. The matched route, e.g. `/api/chain/network-info`. |
| `request_id` | Always present. Hex identifier; mirrors the server-side log entry. |
| `cost_usdc` | Present when the call was paid (x402 / MPP). The exact USDC amount settled. |
| `payment_chain` | Present on paid calls: `base` for x402, `tempo` for MPP. |
| `payment_token` | Present on paid calls: `USDC` for x402, `USDC.e` or `pathUSD` for MPP. |
Subscription Bearer-key calls don't emit `cost_usdc` / `payment_chain` / `payment_token` because settlement is billed monthly by Stripe, not per-call.
## Patterns
### Branch on HTTP status, not the message
`error.code` mirrors the HTTP status. `error.message` is freeform handler text that may change wording; never parse it.
```ts
const { data, error } = await res.json();
if (error) {
switch (error.code) {
case 400: throw new ValidationError(error.message);
case 404: return null; // resource not found on-chain
case 402: return handlePaymentChallenge(res);
default: if (error.code >= 500) return retryWithBackoff();
throw new UnknownAPIError(error);
}
}
return data;
```
The gateway uses a different shape (flat `{error, message}`) for auth and routing failures. See [Error codes](/guides/error-codes) for the full pattern that handles both.
### Always log `request_id`
When something looks off, having the `meta.request_id` in your logs lets the team find the corresponding server-side trace in one query. This is the single most useful thing to log.
### Treat `data` as the only payload
Don't depend on `error` being absent; always read `error` first and fall through to `data` only when `error === null`. The envelope is uniform across success and failure, so you can decode it once and dispatch.
## Full error reference
The two error shapes (envelope and gateway), HTTP status meanings, and recovery patterns are documented in [Guides → Error codes](/guides/error-codes).
---
## Configuration
Reference for everything you can configure on `@one-source/mcp`. See the [install guide](./install) for the basic setup.
## Environment variables
| Variable | Default | Secret | Description |
|---|---|---|---|
| `ONESOURCE_API_KEY` | - | Yes | Bearer key from your [OneSource subscription](../api-key/retrieve-api-key) (`sk_…`). Primary auth mode. |
| `X402_PRIVATE_KEY` | - | Yes | EVM private key (`0x…`) for [wallet-paid x402 access](../x402-and-mpp/x402-base) (USDC on Base). Used when `ONESOURCE_API_KEY` is not set. |
| `MPP_PRIVATE_KEY` | - | Yes | EVM private key (`0x…`) for [wallet-paid MPP access](../x402-and-mpp/mpp-tempo) (USDC.e / pathUSD on Tempo). Used when `ONESOURCE_API_KEY` is not set. If both wallet keys are set, x402 is the initial rail; switch with `1s_payment_mode`. |
| `ONESOURCE_BASE_URL` | `https://api.onesource.io` | No | API backend URL. Change only if directed by OneSource support. |
| `PORT` | `3000` | No | HTTP server port. Only applies in `--http` mode. Used by hosting platforms (Railway, Fly.io). |
| `ONESOURCE_PAYMENT_MODE` | rail default | No | Initial rail + scheme: `x402-exact`, `x402-batch`, `mpp-charge`, or `mpp-session`. Falls back to the enabled rail's own default. Switch in-session with `1s_payment_mode`. |
| `X402_PAYMENT_MODE` | `exact` | No | `batch` opens a payment channel (one deposit funds many calls); `exact` settles per call. Only applies in x402 mode. See [Batch settlement](#batch-settlement-payment-channels). |
| `X402_CHANNEL_DIR` | - (in-memory) | No | Directory to persist batch channel + voucher state across restarts. Recommended whenever `X402_PAYMENT_MODE=batch`. |
| `X402_CHANNEL_SALT` | `0x00…00` | No | 32-byte hex salt selecting which channel to use. Change it to open a fresh channel under the same wallet. |
| `X402_DEPOSIT_MULTIPLIER` | `10` | No | On channel open, deposit `price × this` (minimum 3), funding that many calls before a top-up. Higher means fewer re-deposits but more USDC locked up front. Any unused balance is reclaimable via the `1s_refund` tool or the gateway's idle auto-refund. |
| `X402_BATCH_PROMPT` | `ask` | No | How proactively the agent offers to switch to batch mode: `ask` (confirm with you first), `auto` (switch on its own), or `off` (only when you ask). Only applies in x402 mode. |
| `X402_BATCH_THRESHOLD` | `5` | No | Number of anticipated calls in a session at or above which the agent considers batch mode. Advisory: the agent estimates the count, it is not a hard runtime counter. |
| `X402_RPC_URL` | public Base RPC | No | Custom Base RPC endpoint. Used by batch mode for the on-chain deposit/claim reads. |
| `MPP_PAYMENT_MODE` | `charge` | No | Initial MPP scheme: `charge` (one payment per call) or `session` (voucher channel). Only applies in MPP mode. See [MPP settlement](#mpp-settlement-tempo). |
| `MPP_MAX_DEPOSIT` | `1` | No | `mpp-session`: the channel deposit, in token units (flat, not scaled by call price). Bounds the worst-case balance locked if the server is killed before it settles. Any unused balance is reclaimable via the `1s_refund` tool. Also settable live via `1s_batch_config`. |
| `MPP_RPC_URL` | public Tempo RPC | No | Custom Tempo RPC endpoint. Used by `mpp-session` to open and settle the channel (`mpp-charge` needs no RPC). |
| `ONESOURCE_CONFIG_DIR` | `~/.onesource` | No | Directory holding the server-managed payment config (`batch-config.json`) that `1s_batch_config` reads and writes. |
The only variable you ever have to set yourself is the credential, `ONESOURCE_API_KEY` (or a wallet key: `X402_PRIVATE_KEY` for x402 on Base, or `MPP_PRIVATE_KEY` for MPP on Tempo). Everything else has a working default. The four batch knobs (`X402_PAYMENT_MODE`, `X402_DEPOSIT_MULTIPLIER`, `X402_BATCH_PROMPT`, `X402_BATCH_THRESHOLD`) don't need to be set as env vars at all: your assistant can set and persist them in-session with the [`1s_batch_config`](#configure-batch-behavior-from-your-assistant) tool. For each of those four, a value saved by `1s_batch_config` takes priority over the env var, which in turn beats the built-in default.
### Setting environment variables
:::tip Let the server configure itself
You rarely need to set these by hand. Once the server is installed with any credential, run the [`1s_setup_check`](./tool-reference#setup-and-ops-3) tool: it walks you through every option interactively, applies what it can live (rail, scheme, batch knobs), and hands you a single ready-to-run command for anything that needs a restart, without echoing secrets or editing config files. The manual reference below is for the initial install and for self-hosted (`--http`) deployments.
:::
How you pass env vars depends on your MCP client:
**Claude Code:**
```bash
claude mcp add onesource -e ONESOURCE_API_KEY=sk_… -- npx -y @one-source/mcp@latest
```
**Claude Desktop / Cursor / Windsurf:**
```json
{
"mcpServers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "ONESOURCE_API_KEY": "sk_…" }
}
}
}
```
**VS Code:**
```json
{
"servers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "ONESOURCE_API_KEY": "sk_…" }
}
}
}
```
**Shell (for `--http` mode or testing):**
```bash
ONESOURCE_API_KEY=sk_… npx -y @one-source/mcp@latest
```
---
## Transport modes
### Stdio (default)
The standard mode used by every MCP client. The server reads from stdin, writes to stdout, and runs for the lifetime of the session.
```bash
ONESOURCE_API_KEY=sk_… npx -y @one-source/mcp@latest
```
No additional flags: this is what every [client-specific guide](./install#client-specific-setup) sets up.
### HTTP (self-hosted)
For remote deployments, shared servers, or clients that support HTTP transport.
```bash
ONESOURCE_API_KEY=sk_… npx -y @one-source/mcp@latest --http
ONESOURCE_API_KEY=sk_… npx -y @one-source/mcp@latest --http --port=8080
```
**Endpoints:**
| Endpoint | Method | Description |
|---|---|---|
| `/mcp` | POST | MCP protocol handler |
| `/health` | GET | Returns server status, version, and tool count |
| `*` | OPTIONS | CORS preflight (allows cross-origin requests) |
**Port priority:** `PORT` env var → `--port` flag → default `3000`.
**Health check:**
```bash
curl http://localhost:3000/health
```
Notes:
- HTTP mode is stateless: each request gets a fresh server instance.
- The server binds to `0.0.0.0` for deployment compatibility.
- CORS is enabled for all origins.
---
## Batch settlement (payment channels)
In x402 mode, the server can pay through a **payment channel** instead of settling every call on-chain: one deposit funds many off-chain signed vouchers, which OneSource redeems in batched on-chain claims. For a burst of calls this amortizes settlement gas. See [x402 on Base → Batch settlement](../x402-and-mpp/x402-base#batch-settlement) for the model, and [Build an x402 batch-settlement client](/guides/x402-batch-settlement-client) for the equivalent standalone client.
Start in batch mode, or flip it mid-session with the `1s_payment_mode` tool:
```bash
X402_PRIVATE_KEY=0x… X402_PAYMENT_MODE=batch X402_CHANNEL_DIR=./channel-storage \
npx -y @one-source/mcp@latest
```
- **Requires the long-lived stdio transport** (the default). The `--http` transport is stateless (each request gets a fresh server instance), so it cannot hold channel state; batch mode there falls back to per-call behavior.
- **Set `X402_CHANNEL_DIR`** to persist the channel across restarts. Without it, channel state lives only in the running process and is lost on exit.
- The first paid call opens the channel with one on-chain deposit (`price × X402_DEPOSIT_MULTIPLIER`, default 10), so a session usually over-funds the channel.
- **Reclaim the unused deposit with the `1s_refund` tool** when you're done making batch calls: it returns the full remaining channel escrow to your wallet on Base right away. Idle channels are also auto-refunded by the gateway after a few hours, so the residual is always recoverable; `1s_refund` just gets it back immediately instead of waiting.
### Configure batch behavior from your assistant
Tuning batch mode used to mean editing your MCP client config and restarting. It no longer does: the `1s_batch_config` tool lets your assistant read and change the batch preferences in-session, and persists them to a server-owned file (`batch-config.json` under `ONESOURCE_CONFIG_DIR`, default `~/.onesource`) so they survive restarts without touching the client config.
`1s_batch_config` covers these settings:
| Setting | Values | Default | What it controls |
|---|---|---|---|
| `mode` | `x402-exact` / `x402-batch` / `mpp-charge` / `mpp-session` | rail default | The default rail + scheme the session starts in. Also switched live when that rail is active. |
| `deposit_multiplier` | number ≥ 3 | `10` | x402 channel deposit = call price × this. |
| `mpp_max_deposit` | token amount | `1` | `mpp-session` channel deposit, in tokens (flat, not scaled by call price). |
| `prompt` | `ask` / `auto` / `off` | `ask` | How proactively the agent offers to switch to a channel mode. |
| `threshold` | integer > 0 | `5` | Anticipated call count at or above which a channel mode is worth considering. |
Call it with no arguments to see the current values and the config file path, with a patch (for example `{ "mode": "x402-batch", "deposit_multiplier": 20 }`) to change them, or pass `{ "reset": true }` to restore defaults (which deletes the file and falls back to env vars, then built-in defaults). A saved value takes priority over the matching env var. To switch the live payment scheme for the current session without persisting a default, use `1s_payment_mode` instead.
In x402 mode the agent receives batch guidance in its system prompt at startup, scaled by `prompt` and `threshold`: when it anticipates a burst of calls it can offer (or, on `auto`, perform) the switch to batch mode and remind you to `1s_refund` when finished. `1s_setup_check` reports your current mode, whether batch is available, and both settings.
---
## MPP settlement (Tempo)
With `MPP_PRIVATE_KEY` set, the server pays per call over [MPP on Tempo](../x402-and-mpp/mpp-tempo) instead of x402 on Base. MPP settles in USDC.e or pathUSD, and Tempo gas is materially cheaper than Base. It has two schemes, switchable mid-session with `1s_payment_mode`:
- **`mpp-charge`** (default): one signed Tempo payment per call. Simplest; needs no RPC.
- **`mpp-session`**: a voucher channel. The first call opens an on-chain channel for a flat deposit, `MPP_MAX_DEPOSIT` (default `1` token, not scaled by call price), then subsequent calls are signed off-chain as cumulative vouchers and settled together. Materially cheaper for a burst of calls.
Start in a given mode, or flip it mid-session with `1s_payment_mode`:
```bash
MPP_PRIVATE_KEY=0x… MPP_PAYMENT_MODE=session npx -y @one-source/mcp@latest
```
- The unspent `mpp-session` deposit is settled and reclaimed automatically when the server shuts down cleanly (`SIGINT` / `SIGTERM`), and you can also reclaim it on demand any time with the `1s_refund` tool. `1s_refund` is rail-neutral: it returns the remaining escrow of an open x402 `batch` channel on Base or an `mpp-session` channel on Tempo. A hard kill before either settles leaves the deposit locked on-chain until reclaimed later, so `MPP_MAX_DEPOSIT` bounds the worst case.
- Like x402 batch mode, `mpp-session` needs the long-lived stdio transport to hold channel state; under stateless `--http` it falls back to per-call (`mpp-charge`) behavior.
- x402 and MPP are separate rails with separate wallets (USDC on Base vs USDC.e / pathUSD on Tempo). If both keys are set, x402 is the initial rail, and `1s_payment_mode` switches between all four schemes.
---
## Networks
The OneSource REST API runs against Ethereum mainnet and the Sepolia testnet. Every chain tool takes an optional `network` argument; it defaults to `ethereum`. Pass `network: "sepolia"` on any call to target Sepolia instead.
| Network | `network` value | EIP-155 chain id |
|---|---|---|
| Ethereum mainnet | `ethereum` (default) | `1` (`0x1`) |
| Sepolia testnet | `sepolia` | `11155111` (`0xaa36a7`) |
`1s_list_networks` returns the current list, and [`/api/chain/network-info`](/api-reference/chain-network-info) reports it live. Additional networks land here as OneSource expands its coverage.
---
## Auth precedence
The server picks an auth mode at startup using this order:
1. `ONESOURCE_API_KEY` → Bearer mode (subscription API key).
2. `X402_PRIVATE_KEY` → wallet-paid mode, [x402 on Base](../x402-and-mpp/x402-base) (USDC).
3. `MPP_PRIVATE_KEY` → wallet-paid mode, [MPP on Tempo](../x402-and-mpp/mpp-tempo) (USDC.e / pathUSD).
4. None set → server starts but every paid tool call returns `Payment required (402)`. `1s_setup_check` reports `Status: Not configured`.
The API key always takes precedence over a wallet key. If both `X402_PRIVATE_KEY` and `MPP_PRIVATE_KEY` are set, x402 is the initial rail; switch to MPP in-session with `1s_payment_mode`.
:::note Wallet-paid modes are for the server you run yourself
Both wallet rails sign payments from a single private key the server holds, so they apply to the local (stdio) or self-hosted server you run. A shared, multi-tenant deployment holds no wallet and authenticates each request with a Bearer API key instead.
:::
---
## Configure Claude Code
[Claude Code](https://claude.com/claude-code) is Anthropic's terminal-based agent. It supports MCP servers via the `claude mcp` subcommand.
## Add the server
```bash
claude mcp add onesource -e ONESOURCE_API_KEY=sk_… -- npx -y @one-source/mcp@latest
```
This registers a stdio MCP server named `onesource`. Claude Code stores the config in your user-level `~/.claude.json` (or per-project `.claude/settings.json` if run from a project directory).
## Verify
In a Claude Code session:
```
> Call 1s_setup_check.
```
Expected: a tool-use block whose "Current configuration" reports the **active auth method** as `API key` (with the first 6 characters of your key), the server version, and that the service is reachable.
## Common scopes
- **User-scoped** (default with `claude mcp add`): the server is available in every Claude Code session you start.
- **Project-scoped**: prefix with `--scope project` to write to `.claude/settings.json` in the current directory. Useful if different projects need different API keys.
```bash
claude mcp add onesource --scope project -e ONESOURCE_API_KEY=sk_… -- npx -y @one-source/mcp@latest
```
## Remove or re-add
```bash
claude mcp remove onesource
claude mcp list # see what's registered
```
## Windows note
If `npx` fails to launch on your Windows configuration, wrap the command with `cmd /c`:
```bash
claude mcp add onesource -e ONESOURCE_API_KEY=sk_… -- cmd /c npx -y @one-source/mcp@latest
```
## Troubleshooting
- **"MCP server onesource already exists"**: run `claude mcp remove onesource` first, then re-add.
- **403 / wrong key**: a stale `ONESOURCE_API_KEY` in your shell rc file (`.zshrc`, `.bashrc`) can shadow the value Claude Code passed. Run `echo $ONESOURCE_API_KEY` outside Claude Code to check, then unset it in your shell or rotate the key in the MCP config.
- **`npx` slow to start**: first call downloads the package. Subsequent calls hit npm's local cache and start fast.
- **`npx` / `command not found`**: confirm Node.js is on PATH (`node --version`). On Windows, re-run the Node.js installer and make sure "Add to PATH" is checked, or restart your terminal after install.
## Next
- [Tool reference](./tool-reference)
- Same setup for [Claude Desktop](./configure-claude-desktop), [Cursor](./configure-cursor), [Windsurf](./configure-windsurf)
---
## Configure Claude Desktop
[Claude Desktop](https://claude.ai/download) reads MCP servers from a per-user JSON config file.
## Locate the config file
| OS | Path |
|---|---|
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
| Linux | `~/.config/Claude/claude_desktop_config.json` |
If the file doesn't exist, create it.
## Add the server
```json
{
"mcpServers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "ONESOURCE_API_KEY": "sk_…" }
}
}
}
```
If `mcpServers` already exists, add `"onesource"` as a new key inside it.
**Windows note:** if `npx` doesn't launch directly from JSON on your machine, swap the command form:
```json
"command": "cmd",
"args": ["/c", "npx", "-y", "@one-source/mcp@latest"],
```
## Restart Claude Desktop
Fully quit the app (Cmd-Q / right-click tray → Quit) and reopen. MCP servers are loaded on launch.
**What these fields mean:**
- `command`: the program to run (`npx` fetches and runs npm packages).
- `args`: arguments to that program (`-y` auto-confirms the install).
- `env`: environment variables passed through to the server process.
## Verify
Start a new chat and ask: *"Call `1s_setup_check`."* You should see a tool-use card whose "Current configuration" reports the **active auth method** as `API key` (with the first 6 characters of your key).
If you don't see the tool offered:
- Confirm `claude_desktop_config.json` is valid JSON (use a linter: a stray comma will silently break **all** servers, not just `onesource`).
- Check **Settings → Developer → MCP Logs** for boot errors.
- Fully quit Claude Desktop and reopen; closing the window isn't enough. macOS: Cmd+Q. Windows: right-click the tray icon → Quit.
## Troubleshooting
### Server not showing up
- **Bad JSON.** Validate the file at [jsonlint.com](https://jsonlint.com/).
- **Didn't fully restart.** Use Cmd+Q (macOS) or Quit-from-tray (Windows).
- **Node.js missing.** Run `node --version` in a terminal. If missing, install from [nodejs.org](https://nodejs.org/) (LTS).
### "Command not found" / "npx not found"
Node.js isn't on the system PATH. Restart your computer after installing Node.js. On Windows, re-run the installer with "Add to PATH" checked.
### Tool calls fail
- **Network.** The server has to reach `api.onesource.io`.
- **Smoke-test directly.** In a terminal: `ONESOURCE_API_KEY=sk_… npx -y @one-source/mcp@latest`. If it boots without errors, the package itself is healthy; the issue is in the Claude config. Ctrl+C to stop.
## Next
- [Tool reference](./tool-reference)
- Other clients: [Claude Code](./configure-claude-code), [Cursor](./configure-cursor), [Windsurf](./configure-windsurf)
---
## Configure Cursor
[Cursor](https://cursor.com) supports MCP servers through its native settings UI as of Cursor 0.45+.
## Add the server (UI)
1. **Cursor → Settings → MCP**.
2. Click **+ Add new MCP server**.
3. Fill in:
- **Name**: `onesource`
- **Type**: `command`
- **Command**: `npx -y @one-source/mcp@latest`
- **Environment variables**: `ONESOURCE_API_KEY=sk_…`
4. Click **Save**.
## Add the server (config file)
Alternatively, edit `~/.cursor/mcp.json` for a global config that applies to every project, or `.cursor/mcp.json` in a project root for a per-project config (create if absent):
```json
{
"mcpServers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "ONESOURCE_API_KEY": "sk_…" }
}
}
}
```
## Verify
Open Composer or Chat and ask: *"Call `1s_setup_check`."* The tool card's "Current configuration" should report the **active auth method** as `API key` (with the first 6 characters of your key).
## Troubleshooting
- **Cursor doesn't see the server**: hit the refresh button on the MCP settings page or restart the app. New env vars require a restart.
- **403 / wrong key**: see [Claude Code](./configure-claude-code#troubleshooting); same shadowing pattern applies.
- **Some `onesource` tools missing in Composer**: Cursor caps active tools across all MCP servers at ~40. If you have multiple MCPs configured, disable the ones you aren't using on the MCP settings page.
- **Invalid JSON**: a stray comma or bracket in `mcp.json` silently prevents the server from loading. Validate with a linter.
## Next
- [Tool reference](./tool-reference)
- Other clients: [Claude Code](./configure-claude-code), [Claude Desktop](./configure-claude-desktop), [Windsurf](./configure-windsurf)
---
## Configure VS Code
VS Code reaches MCP servers through GitHub Copilot: they appear as callable tools in Copilot Chat when you're in **Agent** mode.
## Prerequisites
- VS Code with the **GitHub Copilot** extension installed.
- Node.js 18+.
## Add the server
Create or edit `.vscode/mcp.json` in your workspace root:
```json
{
"servers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "ONESOURCE_API_KEY": "sk_…" }
}
}
}
```
:::caution
VS Code uses `"servers"` as the top-level key. Claude Desktop, Cursor, and Windsurf all use `"mcpServers"`; copying a config across clients without changing this key will silently fail.
:::
You can also reach this file from the Command Palette: **Ctrl+Shift+P** (Cmd+Shift+P on macOS) → **MCP: Add Server**.
## Workspace vs user scope
- **Workspace** (the example above): `.vscode/mcp.json` in your workspace root. The server is only available in that workspace.
- **User**: Command Palette → **MCP: Open User Configuration**. The server is available across every workspace.
## Verify
Open Copilot Chat, switch to **Agent** mode, and prompt: *"Call `1s_setup_check`."* You should see a tool card whose "Current configuration" reports the **active auth method** as `API key` (with the first 6 characters of your key).
## With x402 (wallet-paid)
Swap `ONESOURCE_API_KEY` for an Ethereum private key:
```json
{
"servers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "X402_PRIVATE_KEY": "0x…" }
}
}
}
```
For MPP on Tempo instead, set `MPP_PRIVATE_KEY` in place of `X402_PRIVATE_KEY`. See [x402 on Base](../x402-and-mpp/x402-base) and [MPP on Tempo](../x402-and-mpp/mpp-tempo) for funding details.
## Next
- [Tool reference](./tool-reference)
- Other clients: [Claude Code](./configure-claude-code), [Claude Desktop](./configure-claude-desktop), [Cursor](./configure-cursor), [Windsurf](./configure-windsurf)
---
## Configure Windsurf
[Windsurf](https://windsurf.com) reads MCP servers from a per-user JSON config file.
## Locate the config file
| OS | Path |
|---|---|
| macOS / Linux | `~/.codeium/windsurf/mcp_config.json` |
| Windows | `%USERPROFILE%\.codeium\windsurf\mcp_config.json` |
You can also reach the file from inside Windsurf via the MCPs icon in the Cascade panel → **Configure**.
## Add the server
Create or edit the config file:
```json
{
"mcpServers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "ONESOURCE_API_KEY": "sk_…" }
}
}
}
```
## Restart Cascade
In Windsurf, open the Cascade panel and click the refresh icon next to the MCP servers list. New servers and changed env vars require this refresh (or a full Windsurf restart).
## Verify
Open Cascade and prompt: *"Call `1s_setup_check`."* Expected: a tool card whose "Current configuration" reports the **active auth method** as `API key` (with the first 6 characters of your key).
## Per-tool toggling
Windsurf lets you enable or disable individual tools within an MCP server from the Cascade MCP panel: useful if you want, say, only the blockchain data tools and not `1s_report_bug`.
## Next
- [Tool reference](./tool-reference)
- Other clients: [Claude Code](./configure-claude-code), [Claude Desktop](./configure-claude-desktop), [Cursor](./configure-cursor)
---
## Docs MCP (companion)
The runtime [`@one-source/mcp`](./install) lets your assistant **call** OneSource: make real Ethereum reads, run paid endpoints, fetch live data.
A separate package, **`onesource-docs-mcp`**, lets the assistant **read about** OneSource: search the docs, list endpoints, look up parameters and pricing, find endpoints by use case. No auth, no payment, no live data.
Most people want both. They complement each other: the docs MCP answers "how do I use the OneSource REST API?", the runtime MCP answers "what's the current ETH balance of vitalik.eth?"
| MCP | Package | What it does | Auth | Tools |
|---|---|---|---|---|
| **Runtime** | [`@one-source/mcp`](./install) | Calls live OneSource endpoints | API key, x402, or MPP | 30 |
| **Docs** | [`onesource-docs-mcp`](https://www.npmjs.com/package/onesource-docs-mcp) | Searches OneSource docs offline | None | 8 |
## When you only want the docs MCP
If you only want documentation help (no live data, no payments), install just the docs MCP.
### Hosted endpoint (zero setup)
The docs MCP is also hosted as a stateless HTTP endpoint. Point any MCP client that supports HTTP transport at:
```
https://docs.onesource.io/api/mcp
```
No install, no auth. Best path for clients that support remote MCP servers.
### Local install
**Claude Code:**
```bash
claude mcp add onesource-docs -- npx -y onesource-docs-mcp
```
**Claude Desktop / Cursor / Windsurf:**
```json
{
"mcpServers": {
"onesource-docs": {
"command": "npx",
"args": ["-y", "onesource-docs-mcp"]
}
}
}
```
**VS Code:**
```json
{
"servers": {
"onesource-docs": {
"command": "npx",
"args": ["-y", "onesource-docs-mcp"]
}
}
}
```
**Any MCP client (stdio):** `npx onesource-docs-mcp`.
## Tools
| Tool | Purpose |
|---|---|
| `1s_search_docs` | Keyword search across all OneSource docs |
| `1s_get_api_overview` | Operation counts, tags, networks, sample endpoints |
| `1s_list_endpoints` | List endpoints (optional tag filter): method, path, price, summary |
| `1s_get_endpoint_reference` | Full reference for one endpoint: parameters, request body, example response, curl |
| `1s_search_use_cases` | Find endpoints by natural-language description |
| `1s_list_networks` | Supported networks |
| `1s_get_payment_info` | x402 / MPP protocols, prices, pay-to addresses |
| `1s_get_authentication_guide` | Bearer / x402 / MPP auth: code examples |
The data is bundled at publish time from the live OpenAPI spec and the Markdown sources of this site: no network calls at runtime.
## Running both
You can register both MCPs side-by-side under different names; they don't conflict. The runtime MCP's tools are the blockchain tools (`1s_*_live`, `1s_network_info`, `1s_tx_receipt`, …); the docs MCP's are prefixed `1s_search_*` / `1s_get_*` / `1s_list_*`, so your assistant can easily pick the right one.
```json
{
"mcpServers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "ONESOURCE_API_KEY": "sk_…" }
},
"onesource-docs": {
"command": "npx",
"args": ["-y", "onesource-docs-mcp"]
}
}
}
```
## Also see
- [AI editor setup guide](/guides/ai-editor-setup): uses a generic `mcpdoc`-based docs server reading `llms.txt`. Lighter-weight; trades the structured 8-tool surface above for plain keyword search.
---
## Install the MCP server
The OneSource REST API ships with an official MCP (Model Context Protocol) server. Any MCP-compatible AI assistant (Claude Code, Claude Desktop, Cursor, Windsurf) can call OneSource endpoints with natural language. The server runs locally via `npx`, authenticates with your API key, and exposes 30 tools for balances, NFTs, transactions, events, contracts, and chain utilities.
- **Package:** [`@one-source/mcp`](https://www.npmjs.com/package/@one-source/mcp)
- **Source:** https://github.com/blockparty-global/1s-mcp
## Prerequisites
- Node.js 18+
- A credential: either a OneSource API key from your [subscription](../api-key/retrieve-api-key) (keys start with `sk_`), or a wallet private key for pay-per-call access via [x402 on Base](../x402-and-mpp/x402-base) or [MPP on Tempo](../x402-and-mpp/mpp-tempo).
## Client-specific setup
- [Claude Code](./configure-claude-code)
- [Claude Desktop](./configure-claude-desktop)
- [Cursor](./configure-cursor)
- [Windsurf](./configure-windsurf)
- [VS Code (GitHub Copilot)](./configure-vscode)
## Any MCP client (generic stdio)
For any MCP-compatible client not listed above, the server runs over stdio by default:
```bash
ONESOURCE_API_KEY=sk_… npx -y @one-source/mcp@latest
```
Or, as a JSON configuration block:
```json
{
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "ONESOURCE_API_KEY": "sk_…" }
}
```
The server communicates over stdin/stdout. Diagnostics go to stderr; most clients capture them separately.
## Self-hosted HTTP transport
If you want to run the MCP server as a long-lived HTTP service (multiple agents share a single instance, or you want to expose it over your network):
```bash
ONESOURCE_API_KEY=sk_… npx -y @one-source/mcp@latest --http --port=3000
```
Point MCP clients at `http://localhost:3000/mcp`.
**Endpoints:**
| Endpoint | Method | Description |
|---|---|---|
| `/mcp` | POST | MCP protocol handler |
| `/health` | GET | Returns server status, version, and tool count |
| `*` | OPTIONS | CORS preflight (allows cross-origin requests) |
**Health check:**
```bash
curl http://localhost:3000/health
```
**Port priority:** `PORT` env var (used by hosting platforms like Railway and Fly.io) → `--port` flag → default `3000`.
## Verify
After your assistant restarts, ask it: *"Call `1s_setup_check`."* On a healthy install its "Current configuration" reports your **active auth method** as `API key` (with the first 6 characters of your key) and confirms the server version and that the service is reachable. If it shows the auth method as *none* (blockchain tools locked), double-check the env var and reload the client.
## Next
- [Configure-by-client guides](./configure-claude-code)
- [Configuration reference](./configuration): full env-var and transport-mode reference
- [Tool reference](./tool-reference): what each of the 30 tools does
---
## MCP tool reference
`@one-source/mcp` exposes 30 tools, all prefixed with `1s_` so they sort together in your client. Live-chain tools end in `_live`; chain utilities don't. Every tool maps 1:1 to a OneSource REST API endpoint and inherits its pricing; see the [API Reference](/api-reference/) for full parameter and response schemas.
## Live chain data (12)
| Tool | Endpoint | What it returns |
|---|---|---|
| `1s_multi_balance_live` | `/api/chain/live-balance` | ETH + multiple ERC20 balances in one batched call |
| `1s_erc20_balance_live` | `/api/chain/erc20-balance` | Single ERC20 balance with name/symbol/decimals |
| `1s_erc1155_balance_live` | `/api/chain/erc1155-balance` | ERC1155 token balance for an account |
| `1s_nft_owner_live` | `/api/chain/nft-owner` | NFT owner via `ownerOf` |
| `1s_nft_metadata_live` | `/api/chain/nft-metadata` | NFT metadata with IPFS / Arweave / `data:` URI resolution |
| `1s_erc721_tokens_live` | `/api/chain/erc721-tokens` | Token metadata batch for an ERC721 contract |
| `1s_erc20_transfers_live` | `/api/chain/erc20-transfers` | ERC20 Transfer logs |
| `1s_events_live` | `/api/chain/events` | Event logs via `eth_getLogs` |
| `1s_total_supply_live` | `/api/chain/total-supply` | ERC20 total supply |
| `1s_allowance_live` | `/api/chain/allowance` | ERC20 allowance check |
| `1s_tx_details_live` | `/api/chain/tx/{hash}` | Transaction + receipt |
| `1s_contract_info_live` | `/api/chain/contract/{address}` | Name/symbol/decimals + ERC-standard detection (ERC-165) |
## Chain utilities (13)
| Tool | Endpoint | What it returns |
|---|---|---|
| `1s_network_info` | `/api/chain/network-info` | Chain ID, latest block, gas price (batched) |
| `1s_chain_id` | `/api/chain/chain-id` | EIP-155 chain ID |
| `1s_block_number` | `/api/chain/block-number` | Latest block number |
| `1s_block_by_number` | `/api/chain/block/{number}` | Block header + transaction hashes |
| `1s_pending_block` | `/api/chain/pending` | Pending block from the mempool |
| `1s_contract_code` | `/api/chain/code/{address}` | Contract bytecode |
| `1s_nonce` | `/api/chain/nonce/{address}` | Transaction count for an address |
| `1s_storage_read` | `/api/chain/storage` | Read an arbitrary storage slot |
| `1s_tx_receipt` | `/api/chain/receipt/{hash}` | Transaction receipt |
| `1s_simulate_call` | `/api/chain/call` | Simulate `eth_call` (read-only contract call) |
| `1s_estimate_gas` | `/api/chain/estimate-gas` | Gas estimate for a transaction |
| `1s_ens_resolve` | `/api/chain/ens/{input}` | Forward ENS resolution |
| `1s_proxy_detect` | `/api/chain/proxy/{address}` | Upgradeable-proxy implementation detection (EIP-1967, UUPS, Transparent) |
## Payments (2)
These two tools only do something in wallet-paid mode (`X402_PRIVATE_KEY` or `MPP_PRIVATE_KEY` set). With an API key, calls are covered by your plan, so they report that there is nothing to switch or refund.
| Tool | Purpose |
|---|---|
| `1s_payment_mode` | View or switch the rail + scheme the session pays with: `x402-exact` / `x402-batch` ([x402 on Base](../x402-and-mpp/x402-base), USDC) or `mpp-charge` / `mpp-session` ([MPP on Tempo](../x402-and-mpp/mpp-tempo), USDC.e / pathUSD). `*-exact` / `*-charge` settle per call; `*-batch` / `*-session` open a channel where one deposit funds many calls. Call with no arguments to see the current mode. |
| `1s_refund` | Reclaim the unused channel deposit back to your wallet, on demand. Rail-neutral: it settles and returns the full remaining escrow of an open x402 `batch` channel (on Base) or an `mpp-session` voucher channel (on Tempo). For x402 this beats waiting for the gateway's automatic idle refund; for MPP it's the manual reclaim path alongside the automatic settle on clean shutdown. |
## Setup and ops (3)
These tools need no authentication, so they work before you've configured a key.
| Tool | Purpose |
|---|---|
| `1s_setup_check` | Interactive setup and health check. **Call this first** in any session: it asks whether you want to just review your current config or set up / change it, then for a change it walks you through choosing an auth method (API key) or payment rail (x402 on Base / MPP on Tempo) and every related option one decision at a time. It applies what it can live (via `1s_payment_mode` / `1s_batch_config`) and hands you a single ready-to-run command for anything needing a restart (such as a wallet key), never asking you to paste a secret into the chat or hand-edit config files. It also confirms the credential is being read, the service is reachable, the server version (with an update hint if a newer one is published), and, in a wallet-paid mode, the wallet address and current x402 batch or MPP session state. |
| `1s_batch_config` | View or change the payment preferences (autonomy, "burst" threshold, x402 deposit multiplier, MPP session deposit cap, default rail + mode) from your assistant and persist them across restarts. No config-file editing or env vars required. See [Configuration → Configure batch behavior from your assistant](./configuration#configure-batch-behavior-from-your-assistant). |
| `1s_report_bug` | File a bug report from inside your assistant. Captures recent tool calls and the request context. |
## Example prompts
The assistant picks the right tool automatically:
- *"What's the USDC balance of vitalik.eth?"* → `1s_ens_resolve` + `1s_erc20_balance_live`
- *"Show the last 10 ERC20 transfers out of 0xabc…"* → `1s_erc20_transfers_live`
- *"Resolve the metadata for Bored Ape #4521."* → `1s_nft_metadata_live`
- *"Decode the receipt for transaction 0xdef… and tell me which events fired."* → `1s_tx_receipt`
- *"Is 0x123… a proxy contract? If so, what's the implementation address?"* → `1s_proxy_detect`
## Network
Ethereum mainnet (default) and the Sepolia testnet. Every chain tool takes an optional `network` parameter; it defaults to `ethereum`. Pass `network: "sepolia"` to target Sepolia. See [Configuration → Networks](./configuration#networks) for details.
## Pricing
Per-call cost is published in the response `meta.cost_usdc` field and on each operation's [API Reference](/api-reference/) page under `x-payment-info`. Bearer-key subscribers pay nothing per call (your monthly quota covers it); wallet-paid users (x402 or MPP) pay $0.001 – $0.010 per call.
---
## MPP on Tempo
**MPP** (Multi-Party Payments) is an IETF-aligned HTTP payment protocol that uses standard `WWW-Authenticate` / `Authorization` headers ([RFC 7235](https://datatracker.ietf.org/doc/html/rfc7235)) instead of x402's custom `Payment-Required` flow. The headers survive proxies, work with `curl`, and don't conflict with regular HTTP auth schemes.
OneSource supports MPP on the [Tempo](https://tempo.xyz) network, and accepts **both USDC.e and pathUSD** as settlement currencies. It offers two payment modes:
- **Single payment**: one charge settled per call. The default; simplest to integrate.
- **Sessions**: open a channel once, pay many calls under it, close to settle in a single transaction. Materially cheaper for high call volumes.
**Which currency you can actually pay in depends on your client**: see [Currencies and client support](#currencies-and-client-support).
## Single payment (one charge per call)
Each call settles on its own:
1. Your client calls a OneSource endpoint without any `Authorization` header.
2. OneSource returns `HTTP 402` with one or more `WWW-Authenticate: Payment` headers; each carries the price, `payTo`, network, and currency.
3. Your client picks a challenge and constructs an MPP credential: a signed payload covering the price + recipient + nonce.
4. Your client retries the call with `Authorization: Payment `.
5. OneSource verifies the credential, settles on Tempo, and returns the response plus a `Payment-Receipt` header.
Same wallet model as x402; different network and protocol header format.
## Currencies and client support
OneSource accepts two currencies on mainnet, and emits a challenge for **both** on every single-payment 402 (USDC.e first, then pathUSD):
| Currency | Contract | Notes |
|---|---|---|
| **USDC.e** | `0x20c000000000000000000000b9537d11c60e8b50` | The default everywhere. Standard ERC20 stablecoin on Tempo. |
| **pathUSD** | `0x20c0000000000000000000000000000000000000` | Native Tempo stablecoin. Reachable today only via the mpp-go SDK (see below). |
The catch: **whether you can use pathUSD depends on the client you pay with.** OneSource offers both, but most clients only speak USDC.e today.
| Client | USDC.e | pathUSD |
|---|:---:|:---:|
| **AgentCash** | ✓ | - |
| **Tempo CLI** | ✓ | - |
| **Custom client (`mpp-go`)** | ✓ | ✓ |
Sessions are **USDC.e only** regardless of client: see [Sessions](#sessions).
## Using MPP-aware clients
MPP is a direct-HTTP protocol: you reach it by pointing an MPP-capable client at a OneSource REST endpoint, and it negotiates the 402 for you. Any of the clients below works.
:::note The OneSource MCP Server now speaks MPP too
The [`@one-source/mcp`](../mcp/install) runtime server pays with MPP on Tempo when you give it an `MPP_PRIVATE_KEY` (USDC.e / pathUSD), alongside x402 on Base via `X402_PRIVATE_KEY`. It offers `mpp-charge` (per-call) and `mpp-session` (voucher channel) modes, switchable with `1s_payment_mode`; see [MCP → MPP settlement](../mcp/configuration#mpp-settlement-tempo). The direct-HTTP clients below remain the way to pay with MPP from anything other than the MCP server; `mpp-go` is still the only client that reaches **pathUSD**.
:::
### AgentCash
[AgentCash](https://agentcash.dev) speaks MPP alongside x402: point it at a OneSource endpoint and it picks the right protocol from the 402 challenge. It pays in **USDC.e**: it takes the first challenge offered without iterating (which is exactly why OneSource lists USDC.e first), and rejects pathUSD with `unsupported_token`. Nothing to configure.
### Tempo CLI
The official [Tempo wallet CLI](https://github.com/tempoxyz/wallet) makes a paid request with `tempo request`:
```bash
tempo request https://api.onesource.io/api/chain/network-info
```
It handles the 402 → credential → retry cycle automatically and prints the response; there's no currency flag to set. **The Tempo CLI pays in USDC.e only** at this stage: its default wallet's access keys are scoped to USDC.e spending, so a pathUSD challenge fails. If you need to pay in pathUSD, use the mpp-go SDK below.
Sessions use the same tool: `tempo wallet sessions list` / `tempo wallet sessions close ` (a session opens automatically on the first request to a host).
### Custom client (`mpp-go`)
The [`github.com/tempoxyz/mpp-go`](https://github.com/tempoxyz/mpp-go) library exposes the credential primitives directly and is the **only client that can pay in pathUSD as well as USDC.e** against OneSource. It supports any of Tempo's stablecoins, but only when signing from an **ECDSA-generated wallet keypair**. See the mpp-go README: the `charge-basic` and `session-client` examples are the quickest way in.
## Sessions
The second payment mode: **open a session once, pay many calls under one signed envelope, then close to settle on-chain.** For workloads above ~50 calls/min this is materially cheaper than settling every call.
How it works:
- The 402 advertises a **session** challenge alongside the per-call charge challenges. Your client opens a channel, then satisfies subsequent calls with vouchers against it instead of paying each one individually.
- Closing the session settles the net total in one on-chain transaction. OneSource runs Phase 2 session settlement in production: a KMS-signed `close` handler, a durable Redis voucher store, and a 5-minute on-chain settler backstop so an un-closed session still settles.
**Sessions are USDC.e only.** Unlike single payments, OneSource emits just one session challenge, for USDC.e. The Tempo CLI's `tempo wallet sessions close` selects a challenge differently from its per-call path, so offering a second (pathUSD) session challenge risks the close building a settle transaction in the wrong currency; and the default Tempo wallet access keys are scoped to USDC.e spending anyway. Single-currency emission removes the ambiguity.
## When MPP beats x402
- **Lower per-call cost.** Tempo gas is materially cheaper than Base; settlement fees can be an order of magnitude lower.
- **Proxy / CDN friendly.** RFC 7235 headers pass through Cloudflare, AWS API Gateway, and other middleboxes that strip nonstandard headers.
- **Session model.** If your agent makes hundreds of calls/minute, per-call settlement burns fees; sessions amortize.
## When to stick with x402
- You already have a Base USDC balance and prefer not to bridge.
- Your existing tooling speaks x402 only.
- You want the on-chain settlement to land on a high-traffic L2.
Both are first-class options at the gateway; pick whichever fits your wallet and integrations.
## Next
- [x402 on Base](./x402-base)
- [Wallet setup](./wallet-setup): if you haven't funded your wallet yet
- [Tempo docs](https://docs.tempo.xyz): MPP protocol reference, CLI, and wallet setup
---
## Wallet setup
Pay-per-call access uses one of two protocols:
- **[x402 on Base mainnet](./x402-base)**: pay each call in USDC on Base. The standard agent-payment protocol.
- **[MPP on Tempo](./mpp-tempo)**: pay each call in USDC.e (or pathUSD) on the Tempo network. Lower per-call gas costs.
Both require a wallet you control with a funded balance in the relevant currency.
## Pick a wallet flavor
| Protocol | Network | Currency | Wallet needs |
|---|---|---|---|
| x402 | Base mainnet (chain ID 8453) | USDC (`0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`) | Ethereum-style key (secp256k1) with a USDC balance on Base |
| MPP | Tempo mainnet | USDC.e (`0x20c000000000000000000000b9537d11c60e8b50`) or pathUSD (`0x20c0000000000000000000000000000000000000`) | Tempo-style key with USDC.e or pathUSD balance |
If you already use an agent wallet (e.g., [AgentCash](https://agentcash.dev)), it likely supports both protocols out of the box; skip to the protocol pages.
## Fund a fresh wallet
### x402 / Base
**1. Get a private key.** You need a 64-char hex string starting with `0x`, 66 chars total.
- **Export from MetaMask**: Account menu (three dots) → **Account details** → **Show private key**.
- **Export from Coinbase Wallet**: Settings → Developer settings → **Export private key**.
- **Generate a fresh one**: `echo "0x$(openssl rand -hex 32)"`. Fund this address before using it.
:::warning Use a dedicated wallet
Don't use the private key from a wallet that holds significant funds. Create or designate a wallet specifically for OneSource payments and keep only a small USDC balance on it (a few dollars).
:::
**2. Send USDC on Base to its address.** Sources:
- Bridge from Ethereum mainnet via [Base Bridge](https://bridge.base.org).
- Withdraw USDC directly to Base from an exchange that supports Base (Coinbase, Kraken, etc.).
- Swap directly on Base via Uniswap, Aerodrome, or your DEX of choice.
**3. Budget.** A few dollars of USDC handles thousands of API calls (per-call prices range from $0.001 to $0.010).
:::warning Base network only
USDC must be on **Base** (chain ID 8453), not Ethereum mainnet. Sending USDC on the wrong network to the same address won't work; the funds will sit on the wrong chain. If this happens, use [Base Bridge](https://bridge.base.org) to move them across.
:::
### MPP / Tempo
1. Generate a Tempo-compatible key.
2. Obtain USDC.e or pathUSD on Tempo. See [Tempo's docs](https://tempo.xyz) for current bridge and on-ramp options.
3. Mainnet RPC: `https://rpc.tempo.xyz`.
## What the OneSource REST API does
When you call an endpoint without a Bearer key, the service returns `HTTP 402 Payment Required` with two headers offered in parallel:
- `Payment-Required`: x402 v2 challenge (price, payTo, chain, asset).
- `WWW-Authenticate: Payment`: RFC 7235-style MPP challenge (price, payTo, network, asset).
Your client picks whichever protocol it supports, signs the proof, and resends the request with the `Payment-Signature` (x402) or `Authorization: Payment` (MPP) header. The service verifies, settles on-chain, and returns the response, along with a receipt header for your records.
## Security best practices
- **Dedicated wallet**: not a wallet that holds real funds.
- **Never commit keys**: use env vars, `.env` in `.gitignore`, or a secrets manager (AWS Secrets Manager, HashiCorp Vault, 1Password CLI).
- **Small balance**: a few dollars at a time. Top up when it drains.
- **Rotate**: if a key was ever shared, logged, or pasted somewhere unexpected, generate a new one and move the balance.
- **Production**: secrets manager, not plain env vars.
## Troubleshooting
| Problem | Fix |
|---|---|
| `Payment required (402)` keeps coming back | x402 isn't configured; your client isn't signing. Set `X402_PRIVATE_KEY` (or supply an account to `@x402/fetch`). |
| Payment fails / times out | Wallet has no USDC on Base. Check the balance on [BaseScan](https://basescan.org). |
| `x402 setup failed` / "invalid key" in logs | Key must start with `0x` and be 66 characters (`0x` + 64 hex). |
| USDC went to the wrong network | Bridge it to Base via [bridge.base.org](https://bridge.base.org). |
| Tools work but call returns no payment receipt | Confirm `Payment-Response` header is present; settlement is async and may lag the response slightly. |
## Next
- [x402 on Base](./x402-base)
- [MPP on Tempo](./mpp-tempo)
---
## x402 on Base
[x402](https://www.x402.org) is the Coinbase-originated standard for HTTP-native micropayments. OneSource supports x402 on Base mainnet: every paid endpoint accepts a USDC-on-Base payment signature in place of an API key.
## How a call works
1. Your client calls a OneSource endpoint without any `Authorization` header.
2. OneSource returns `HTTP 402` with a `Payment-Required` header containing the price ($0.001 – $0.010 USDC), the `payTo` address, and the asset (USDC on Base chain ID 8453).
3. Your client signs an EIP-712 `TransferWithAuthorization` payload over the price + nonce + deadline.
4. Your client retries the call with the signed payload in a `Payment-Signature` header.
5. OneSource verifies the signature with the x402 facilitator, settles the transfer on Base, and returns the API response plus a `Payment-Response` header (transaction hash + settled amount).
Total round-trip is typically under one second; the on-chain settlement is async to the response.
## Using `x402-fetch`
[`x402-fetch`](https://www.npmjs.com/package/x402-fetch) wraps `fetch` to handle the 402 → sign → retry loop automatically:
```ts
const account = privateKeyToAccount(process.env.X402_PRIVATE_KEY as `0x${string}`);
const wallet = createWalletClient({ account, chain: base, transport: http() });
const paidFetch = wrapFetchWithPayment(fetch, wallet);
const res = await paidFetch('https://api.onesource.io/api/chain/network-info');
const { data, error } = await res.json();
```
The wrapper handles every paid endpoint transparently: you call it like normal `fetch`.
## Using AgentCash
[AgentCash](https://agentcash.dev) is an agent wallet and MCP server that speaks both x402 and MPP. Set it up with:
```bash
npx agentcash@latest install
```
It generates a local wallet on first run; fund it with USDC on Base. Once AgentCash is wired into your assistant (see [its setup docs](https://agentcash.dev)), ask the assistant to `fetch https://api.onesource.io/api/chain/network-info` and it signs the x402 payment automatically.
## Using the OneSource MCP Server with x402
Set `X402_PRIVATE_KEY` instead of `ONESOURCE_API_KEY`:
```bash
X402_PRIVATE_KEY=0x… npx -y @one-source/mcp@latest
```
Or in your MCP client config:
```json
{
"mcpServers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "X402_PRIVATE_KEY": "0x…" }
}
}
}
```
The OneSource MCP Server picks up the key and signs payments automatically on every tool call.
To batch many calls through one channel instead of paying per call, add `X402_PAYMENT_MODE=batch` (and a persistent `X402_CHANNEL_DIR`): see [Batch settlement](#batch-settlement) below.
## Pricing
Per-call USDC prices are published in two places:
- The `x-payment-info.price.amount` extension on every [API Reference](/api-reference/) page.
- The response `meta.cost_usdc` field on every successful call.
Examples:
| Endpoint | Price (USDC) |
|---|---|
| `/api/chain/block-number` | 0.001 |
| `/api/chain/network-info` | 0.001 |
| `/api/chain/live-balance` | 0.003 |
| `/api/chain/call` | 0.005 |
| `/api/chain/nft-metadata` | 0.008 |
| `/api/chain/pending` | 0.010 |
## Verify the payment
The `Payment-Response` header on a successful call carries the on-chain transaction hash. You can verify settlement on https://basescan.org with that hash.
## Receipts
OneSource also returns settlement metadata on the body's `meta` object: `payment_chain`, `payment_token`, `cost_usdc`, `request_id`. Log these for audit / billing reconciliation.
## Batch settlement
x402 has a session-like mode that mirrors [MPP sessions](./mpp-tempo#sessions), but stays on Base/USDC, so you don't bridge to Tempo. Instead of settling every call on-chain, you open a payment channel with a single deposit, satisfy many calls with off-chain signed claims against it, and OneSource settles the cumulative total in batched on-chain claims. For high call volumes this amortizes settlement gas the same way MPP sessions do.
OneSource advertises a `batch-settlement` scheme alongside the standard `exact` scheme in the `402` for every `/api/chain/*` route. OneSource is the channel authorizer (self-managed), claims accrued usage on a short interval, and refunds any unused deposit after a withdraw delay (~1 day on mainnet).
### Using it
Batch needs a client that holds channel state across calls: the deposit, the signed vouchers, and the running cumulative total. Two supported paths:
- **The OneSource MCP Server (no code).** Set `X402_PAYMENT_MODE=batch` (and a persistent `X402_CHANNEL_DIR`) when you run [`@one-source/mcp`](../mcp/install) in x402 mode, or flip it in-session with the `1s_payment_mode` tool. The `1s_refund` tool reclaims any unused deposit on demand when you're done. See [MCP configuration → Batch settlement](../mcp/configuration#batch-settlement-payment-channels). Works in the default stdio (long-lived) transport, not the stateless `--http` mode.
- **A custom client.** Build directly on the `@x402/evm` SDK. [Build an x402 batch-settlement client](/guides/x402-batch-settlement-client) is a complete, runnable example you can copy.
:::note Third-party wallets are exact-only
The common third-party x402 clients (`x402-fetch`, AgentCash, the Coinbase agentic wallet (`awal`)) implement only the **exact** scheme. They ignore the batch challenge and keep paying per call, unchanged, so nothing breaks. Batch today means either the OneSource MCP Server's batch mode or a custom batch-settlement client. Protocol details: the [x402 batch-settlement spec](https://docs.x402.org/schemes/batch-settlement).
:::
## Next
- [MPP on Tempo](./mpp-tempo): alternative protocol with lower gas costs
- [Wallet setup](./wallet-setup): if you haven't funded your wallet yet
---
## Guides
Practical guides for common OneSource REST API workflows.
## Available
- **[Batching and cost optimization](./batching-and-cost-optimization)**: patterns that cut API spend 5-20x: batched balance calls, endpoint-tier awareness, client-side caching, and amortized settlement (MPP sessions or x402 batch settlement) for high-volume workloads.
- **[Error codes](./error-codes)**: the two error shapes (envelope from OneSource, flat from the gateway), what each HTTP status means, and a recovery pattern that handles both. Includes a normalizer and a retry/backoff sketch.
- **[Migrating from x402 / MPP to an API key](./migrating-to-api-key)**: moving production traffic from wallet-paid x402 or MPP calls to a Bearer API key from app.onesource.io (or running both in parallel). Generalized across our discovery channels: AgenticMarket / CDP Bazaar, AgentCash, x402scan, mppScan, MCP Registry, Glama, npm.
- **[AI editor setup](./ai-editor-setup)**: wire the OneSource docs into Claude Code, Claude Desktop, Cursor, VS Code, or Windsurf as an MCP server so your assistant can answer questions about the API. Distinct from [`@one-source/mcp`](/getting-started/mcp/install), which lets the assistant *call* the API.
## Reference
The [API Reference](/api-reference/) lists every endpoint with parameters, example responses, and per-call pricing.
---
## AI editor setup
Give your AI coding assistant knowledge of the OneSource **documentation** so it can answer questions and generate accurate calls without you copy-pasting from these pages.
:::note Reading about vs. calling the API
This wires in the **docs** (the assistant can *read about* OneSource). To let the assistant *call* the API at runtime, install [`@one-source/mcp`](/getting-started/mcp/install): the two complement each other, so install both for the best experience.
:::
There are three ways to do it, in rough order of preference.
## 1. Recommended: the OneSource docs MCP
[`onesource-docs-mcp`](/getting-started/mcp/docs-mcp) gives your assistant 8 structured tools, search docs, list endpoints, look up parameters, pricing, and auth, which retrieve far more precisely than plain text search. Two ways to run it:
- **Hosted, zero install**: point any HTTP-capable MCP client at `https://docs.onesource.io/api/mcp`.
- **Local**: `npx -y onesource-docs-mcp` (Node).
Per-editor configuration (Claude Code, Claude Desktop, Cursor, Windsurf, VS Code) lives on the **[Docs MCP page](/getting-started/mcp/docs-mcp)**; start there.
## 2. Already using `mcpdoc`?
If you've standardized on LangChain's [`mcpdoc`](https://github.com/langchain-ai/mcpdoc) across your documentation sources, add OneSource as one more entry rather than running a second kind of server. It serves keyword search over `llms.txt` (no structured tools) and needs Python's [`uv`](https://docs.astral.sh/uv/): `curl -LsSf https://astral.sh/uv/install.sh | sh`.
**Claude Code:**
```bash
claude mcp add-json onesource-docs '{"type":"stdio","command":"uvx","args":["--from","mcpdoc","mcpdoc","--urls","OneSourceDocs:https://docs.onesource.io/llms.txt","--follow-redirects"]}'
```
**Cursor / Windsurf / Claude Desktop**: add to your editor's MCP config file (VS Code uses a `servers` key instead of `mcpServers`):
```json
{
"mcpServers": {
"onesource-docs": {
"command": "uvx",
"args": ["--from", "mcpdoc", "mcpdoc", "--urls", "OneSourceDocs:https://docs.onesource.io/llms.txt", "--follow-redirects"]
}
}
}
```
## 3. No MCP support: use the raw files
If your tool can't speak MCP, point it at (or paste in) the doc bundles directly:
- **[llms.txt](https://docs.onesource.io/llms.txt)**: index of every page (lightweight, for discovery).
- **[llms-full.txt](https://docs.onesource.io/llms-full.txt)**: the full docs concatenated into one file (for context-window ingestion).
---
## Batching and cost optimization
Per-call pricing rewards efficient request patterns. A few small habits cut spend dramatically, sometimes 5-20x for the same answer.
## 1. Use `live-balance` for batched ERC20 lookups
`/api/chain/live-balance` runs ETH balance + multiple ERC20 balances in a **single** call (server-side multicall under the hood). One $0.003 request beats N $0.003 requests:
```bash
# Cheap: one call for ETH + 3 tokens = $0.003
curl "https://api.onesource.io/api/chain/live-balance?address=0xd8da...&tokens=0xA0b8...,0xdAC1...,0x6B17..." \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
# Expensive: four calls = $0.012, same data
curl "https://api.onesource.io/api/chain/erc20-balance?account=...&token=0xA0b8..." # $0.003
curl "https://api.onesource.io/api/chain/erc20-balance?account=...&token=0xdAC1..." # $0.003
curl "https://api.onesource.io/api/chain/erc20-balance?account=...&token=0x6B17..." # $0.003
# + a separate ETH balance call
```
The `tokens` parameter accepts a comma-separated list, typically up to 20 tokens per call before you should split.
**Heads-up on response shape:** the batched call returns each balance as a raw hex string and includes `symbol` and `decimals` but omits the `name` field, while `erc20-balance` returns a decimal string and includes `name`. If you're switching code from N single calls to one batched call, parse the hex (`parseInt(balance, 16)` or `BigInt(balance)`) and look up the human-readable token name elsewhere if you need it.
## 2. Use `network-info` instead of separate calls
`/api/chain/network-info` returns chain ID + latest block + current gas price in one $0.001 call. Don't call `chain-id` and `block-number` separately when you need the bundle, and don't reach for `estimate-gas` to get the chain-tip gas price (that endpoint estimates gas for a *specific* transaction body, not the tip).
| What you want | One call | Two calls |
|---|---|---|
| Chain ID + block + gas price | `network-info` = **$0.001** | `chain-id` + `block-number` = **$0.002** (and still no gas price) |
## 3. Pick the cheapest endpoint that answers your question
Endpoints are priced by complexity. Always check the [API Reference](/api-reference/) for cost before wiring:
| Tier | Endpoints | Use when |
|---|---|---|
| **$0.001** | `block-number`, `chain-id`, `network-info` | Tip / chain ID lookups |
| **$0.003** | `live-balance`, `block`, `erc20-balance`, `erc1155-balance`, `nft-owner`, `code`, `nonce`, `allowance`, `total-supply` | Single-state reads |
| **$0.004** | `estimate-gas` | Gas estimate for a specific transaction body |
| **$0.005** | `call`, `events`, `erc20-transfers`, `contract`, `receipt`, `storage`, `ens`, `proxy` | Decoded reads, log queries |
| **$0.008** | `tx`, `nft-metadata`, `erc721-tokens` | Heavy reads, metadata resolution |
| **$0.010** | `pending` | Mempool reads |
A workload that calls `chain/tx` ($0.008) when `chain/receipt` ($0.005) would have answered the question is paying 60% more for the same outcome.
## 4. Cache deterministic results client-side
Some endpoint results are immutable. Cache them aggressively:
- **Block headers** (`/api/chain/block/{number}`) for any block ≥ 64 blocks below tip: immutable after finality.
- **Transactions and receipts** (`/api/chain/tx/{hash}`, `/api/chain/receipt/{hash}`): immutable after confirmation.
- **Contract bytecode** (`/api/chain/code/{address}`): immutable for non-upgradeable contracts; safe to cache for proxies between upgrade events.
- **NFT metadata** (`/api/chain/nft-metadata`): off-chain metadata via IPFS / Arweave is content-addressed; cache by `tokenURI` rather than re-fetching every call.
- **ENS resolution** (`/api/chain/ens/{name}`): cache for an hour or two; ENS records change rarely.
Even a 1-minute TTL on `block` lookups cuts cost dramatically for analytics workloads.
## 5. Don't poll the tip if you don't need to
If your app needs "is this transaction confirmed?", poll the **receipt** by hash, not the block tip. One $0.005 call replaces dozens of $0.001 tip checks plus per-block scans.
**Watch the response shape, not the HTTP status.** `chain/receipt` returns `HTTP 200` whether the tx is confirmed or not; for an unknown hash you get `{"data": {"result": null}, ...}`. Polling on status `404` will loop forever. The correct check:
```ts
const { data } = await res.json();
if (data.result === null) return 'pending'; // not yet mined or unknown hash
return { status: 'confirmed', receipt: data.result };
```
(Same pattern with `chain/tx`, which returns `{transaction, receipt}` and uses `data.transaction === null` for the not-yet-mined case, but at $0.008 you're paying 60% more for the same answer if all you needed was "is it confirmed?".)
## 6. Batch event-log queries by topic + block range
`/api/chain/events` paginates `eth_getLogs` results. A single call with a wide `to_block`-`from_block` range and the right topic filters covers thousands of events for $0.005, much cheaper than N narrow queries. A 500-block window on a busy contract can easily return 10,000+ logs for a single $0.005 call.
The upstream node caps any single query at **20,000 log results**. Overshoot and you get a `502` envelope error whose `message` includes the safe sub-range the node would have served: use it to drive adaptive pagination:
```json
{
"data": null,
"error": {
"code": 502,
"message": "getLogs failed: RPC error -32602: query exceeds max results 20000, retry with the range 21675072-21675860"
},
"meta": { "endpoint": "/api/chain/events", "request_id": "..." }
}
```
Parse the suggested range out of the message and retry with that window, then continue from the next block. Add address or topic filters to keep result counts under the cap when you can.
## 7. For wallet-paid workloads, watch settlement fees too
x402 and MPP per-call USDC charges *include* on-chain settlement costs. At very low per-call prices ($0.001) the gas can be a non-trivial fraction of the spend. If you're running thousands of calls/minute, amortize the settlement so one deposit/envelope covers N calls:
- **On Tempo (MPP):** [sessions](/getting-started/x402-and-mpp/mpp-tempo#sessions): open a channel once, pay many calls under it.
- **On Base (x402):** [batch settlement](/getting-started/x402-and-mpp/x402-base#batch-settlement): the same model without leaving Base. Use the [OneSource MCP Server's batch mode](/getting-started/mcp/configuration#batch-settlement-payment-channels) (no code) or [build a batch-settlement client](/guides/x402-batch-settlement-client).
## Quick checklist
- [ ] Replaced N ERC20 lookups with a single `live-balance` call.
- [ ] Used `network-info` for chain-state checks instead of three separate calls.
- [ ] Cached immutable results (finalized blocks, confirmed receipts, NFT metadata) client-side.
- [ ] Polling receipts by hash, not the tip.
- [ ] Picked the cheapest endpoint that answers each question (check `meta.cost_usdc` to confirm).
- [ ] For wallet-paid high-volume workloads: amortized settlement, MPP sessions on Tempo or x402 batch settlement on Base, instead of paying per call.
---
## Error codes
OneSource can emit three error shapes depending on where the failure originates. Your client needs to handle all three:
- **Envelope errors**: from the OneSource service itself (parameter validation, on-chain lookups, upstream RPC issues). Carries `{error: {code, message}, meta}`.
- **Gateway errors**: from the API gateway in front of OneSource (auth, payment, routing). Flat `{error, message}`.
- **402 + MPP RFC 7807 errors**: payment-protocol-specific shapes the gateway forwards verbatim. Different again.
The HTTP status is the only thing all three share. Branch on it when in doubt; use the body to read details.
## Envelope errors (from the OneSource service)
```json
{
"error": {
"code": 400,
"message": "address param required"
},
"meta": { "endpoint": "/api/chain/live-balance", "request_id": "abcdef0123456789" }
}
```
- `error.code` is the **HTTP status as an integer** (`400`, `402`, `500`, `502`), mirroring the status line. It is *not* a stable string identifier.
- `error.message` is human-readable English produced by the handler. Wording can change between releases; don't parse it.
- `meta.request_id` mirrors the server-side trace. Always log it.
- `data` is **omitted** on error (not present as `null`).
Branch on the HTTP status (or equivalently `error.code`), and use `error.message` for diagnostics.
### What you'll see by status
| HTTP | When | Example `error.message` |
|---|---|---|
| `400` | Parameter missing, malformed, or wrong type. | `"address param required"`, `"invalid number \"notanumber\": expected decimal or 0x-prefixed hex"`, `"to and data fields required"`, `"invalid JSON body"` |
| `404` | Path doesn't match any route on OneSource (the gateway forwarded an unknown path). | `"404 page not found"` |
| `500` | Unhandled internal failure. | Captured handler message. |
| `502` | Upstream RPC node returned an error; the message passes the upstream verbatim. | `"ownerOf failed: RPC error 3: execution reverted: ERC721: owner query for nonexistent token"`, `"getLogs failed: RPC error -32602: query exceeds max results 20000, retry with the range ..."` |
The `400` bucket is by far the most common; pre-validate parameters client-side against the [API Reference](/api-reference/) before sending.
### "Resource not found" doesn't mean `404`
OneSource does **not** return `404` for missing on-chain resources. The semantics vary by endpoint, and you generally need to look at the response body:
| Endpoint | Unknown / missing resource returns |
|---|---|
| `/api/chain/block/{n}` (past tip or future) | `200` + `{"data": {"result": null}, ...}` |
| `/api/chain/tx/{hash}` (unknown hash) | `200` + `{"data": {"transaction": null, "receipt": null}, ...}` |
| `/api/chain/receipt/{hash}` (unknown / unmined) | `200` + `{"data": {"result": null}, ...}` |
| `/api/chain/nft-owner` (nonexistent `token_id`) | `502` envelope error; the RPC revert (`"execution reverted: ERC721: owner query for nonexistent token"`) passes through |
| `/api/chain/ens/{name}` (no resolver) | `200` + `{"data": {"name": "...", "error": "no resolver set for this name"}, ...}`: inline `data.error` string, NOT the envelope `error` field |
Polling on HTTP `404` to detect "tx pending" or "NFT doesn't exist" will fail. The reliable patterns:
```ts
// "Is this tx confirmed?": receipt by hash
const { data } = await res.json();
if (data?.result === null) return 'pending';
return { status: 'confirmed', receipt: data.result };
// "Does this NFT exist?": catch the 502 revert
if (!res.ok && body?.error?.code === 502 &&
body.error.message.includes('owner query for nonexistent token')) {
return 'does-not-exist';
}
```
## Gateway errors (auth, payment, routing)
Returned by the gateway *before* the request reaches OneSource, so there's no `meta` block. Flat shape:
```json
{ "error": "invalid_token", "message": "Invalid or expired API key" }
```
`error` is a lowercase snake_case identifier. Branch on it the same way you would on an HTTP status.
| `error` | HTTP | Cause | Recovery |
|---|---|---|---|
| `invalid_token` | 401 | `Authorization: Bearer sk_…` was present but the key isn't recognized or has been revoked. | Re-copy the key from [app.onesource.io](https://app.onesource.io) → API Keys. Rotate if compromised. |
| `api_key_required` | 401 | The endpoint requires a Bearer key but x402/MPP isn't accepted, *or* both x402 and MPP are disabled. In current production, an anonymous call to a paid endpoint returns `402` with a payment challenge instead (see the next section). | Add `Authorization: Bearer sk_…`. |
| `plan_required` | 403 | Your key is on the sandbox tier but the endpoint requires a paid plan. | Upgrade your subscription at [app.onesource.io](https://app.onesource.io) or call the endpoint with x402 / MPP instead. |
| `auth_unavailable` | 503 | The gateway couldn't reach the validation service. Transient. | Retry with backoff. |
| `invalid_payment` | 400 | x402 / MPP payment header was present but malformed. | Re-sign with the current challenge. |
| `verification_failed` | 502 | Payment header was well-formed but the facilitator couldn't verify it. | Re-sign and retry; if persistent, the facilitator is degraded. |
| `upstream_error` | 502 | Gateway reached OneSource but OneSource returned an unrecoverable error. | Retry with backoff. |
| `not_found` | 404 | No upstream configured for this host. (404s for unknown *paths* on `api.onesource.io` return the envelope shape above, not this one.) | Check the hostname. |
## Payment-required (402) and MPP RFC 7807
### 402 from an anonymous call
An unauthenticated call to a paid endpoint comes back with `HTTP 402` and a third hybrid shape: `error` is a string (like gateway flat shape) but a `meta` block is present too:
```json
{
"error": "Payment required",
"meta": {
"cost_usdc": "0.001",
"endpoint": "/api/chain/network-info",
"network": "eip155:8453"
}
}
```
The response also carries the protocol-specific challenge headers:
- `Payment-Required: `: x402 challenge (price in USDC on Base, recipient address, expiry).
- `WWW-Authenticate: Payment ...` (one or more): MPP challenges (Tempo network, both one-shot `charge` and `session` flavors).
A single 402 advertises every supported protocol; clients pick the one they speak and ignore the rest. Sign and retry; payment-aware HTTP clients ([`x402-fetch`](https://www.npmjs.com/package/x402-fetch) for x402, MPP-aware fetchers for Tempo) handle this loop for you. See [x402 on Base](/getting-started/x402-and-mpp/x402-base) or [MPP on Tempo](/getting-started/x402-and-mpp/mpp-tempo).
### MPP credential errors (RFC 7807)
A malformed MPP `Authorization: Payment ...` credential comes back from the MPP SDK in **RFC 7807 problem+json** form, neither envelope nor gateway-flat:
```json
{
"type": "https://mpp.dev/errors/malformed-credential",
"title": "Malformed Credential",
"status": 400,
"detail": "mpp: invalid credential encoding: json decode: invalid character ..."
}
```
Branch on `type` (a stable URL) when handling MPP-specific failures.
The gateway also exposes [rate-limit headers](/getting-started/api-key/rate-limits-and-quotas#rate-limit-headers) (`X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`) on every authenticated response.
## Anatomy of recovery logic
A robust client branches on HTTP status. Normalize the three body shapes up front so the rest of the logic stays simple:
```ts
function parseError(status: number, body: any): { code: number | string; message: string } | null {
// RFC 7807 (MPP credential errors): { type, title, detail, status }
if (body?.type && body?.title) {
return { code: body.type, message: body.detail ?? body.title };
}
if (!body?.error) return null;
// Envelope shape: { error: { code: , message }, meta }
if (typeof body.error === 'object') return body.error;
// Gateway flat shape: { error: "", message }
// Also handles the 402 hybrid: { error: "Payment required", meta }
return { code: body.error, message: body.message ?? '' };
}
async function callSkills(url: string, init: RequestInit, attempt = 0): Promise {
const res = await fetch(url, init);
const body = await res.json();
const err = parseError(res.status, body);
if (!err) return body.data;
// Retryable: transient gateway/upstream failures + 5xx from OneSource.
const retryable =
err.code === 'auth_unavailable' ||
err.code === 'upstream_error' ||
err.code === 'verification_failed' ||
(typeof err.code === 'number' && err.code >= 500);
if (retryable && attempt < 3) {
await new Promise(r => setTimeout(r, 2 ** attempt * 500 + Math.random() * 250));
return callSkills(url, init, attempt + 1);
}
throw new APIError(err, res.status);
}
```
Note: don't branch on `err.code === 404` to detect "resource not found": OneSource returns `200` + `data.*: null` for missing on-chain resources. See [the "Resource not found" section](#resource-not-found-doesnt-mean-404) above.
A few notes on what this sketch deliberately doesn't do:
- **No `RATE_LIMITED` / `Retry-After` handling.** OneSource currently emits rate-limit *headers* (`X-RateLimit-*`) but does not return `429` responses today. If you exceed the cap, the gateway logs it and the headers will trend toward zero, but calls are not actively blocked at the application layer. Future enforcement will add `429` + `Retry-After`; at that point a `429` branch with `Retry-After` becomes the right pattern.
- **No 402 branch.** Payment-aware HTTP clients (`@x402/fetch`, MPP fetchers) handle the `402 → sign → retry` loop for you. If you're writing one of those clients yourself, branch on `402` and read the `Payment-Required` / `WWW-Authenticate` headers.
## Always log `request_id`
`meta.request_id` (envelope shape only; gateway errors don't carry one) is the single most useful field for support. It mirrors the server-side trace; including it in your error log lets the team find the corresponding entry in one query.
## When in doubt
Open a bug report including:
1. The endpoint and parameters.
2. The full response body and HTTP status.
3. `meta.request_id` if the failure came from OneSource (envelope shape).
4. The approximate time of the failure.
Filed via the `1s_report_bug` MCP tool (if you're using the [OneSource MCP Server](/getting-started/mcp/install)) or as a GitHub issue.
---
## Migrating from x402 / MPP to an API key
The OneSource REST API is discoverable through a number of channels (AgenticMarket / CDP Bazaar, AgentCash, x402scan, mppScan, the MCP Registry, Glama, npm) and any agent that found you there can call the API today by signing per-request payments in USDC. That's a great fit for low-volume or experimental traffic.
For production-scale workloads a direct Bearer API key is usually cheaper and simpler: predictable monthly billing through Stripe, configurable rate limits, no per-call wallet settlement. This guide covers what changes (and what doesn't) when you migrate.
## What stays the same
- **Base URL.** Still `https://api.onesource.io`. No DNS change.
- **Endpoints.** Identical paths, parameters, response shapes. All endpoints in the [API Reference](/api-reference/) work as before.
- **Response envelope.** Same `{ data, error, meta }` shape on success and failure.
The wire format is *exactly* the same. Only the auth header changes.
## What changes
### Auth
| Before (x402 or MPP) | After (Bearer API key) |
|---|---|
| Sign each call (USDC on Base for x402, or USDC.e / pathUSD on Tempo for MPP); client handles `402 → sign → retry` | `Authorization: Bearer sk_…` once, every call |
| Wallet private key in your agent's env | API key from the OneSource dashboard |
| Per-call USDC settlement on Base or Tempo | Monthly billing through Stripe |
### Pricing model
Per-call charges range $0.001 – $0.010 USDC depending on endpoint. The Bearer-key subscription bundles that into a per-month fee with a request quota and rate cap; exact numbers depend on the plan you pick at signup. For sustained traffic the monthly plan is usually materially cheaper than per-call. For low or bursty volume, x402 or MPP may still be more economical; see the [pricing comparison](#pricing-comparison) below.
### Rate limits
| | x402 / MPP | Bearer API key |
|---|---|---|
| Rate cap | None, only your wallet balance limits you | Set by your plan, account-wide (shared across all your keys) |
| Monthly quota | None | Set by your plan, account-wide |
If your traffic has sustained spikes above your plan's rate cap, plan for backoff. The Developer plan is currently the only subscription tier, so for more capacity, route the overflow to x402 / MPP (which has no quota) or run a second subscription under a different email.
### Discovery
The OneSource REST API stays listed on its existing discovery channels (AgenticMarket / CDP Bazaar, AgentCash, x402scan, mppScan, the MCP Registry, Glama, npm) regardless of which auth path your own traffic uses. The service still emits x402 challenges in parallel with MPP and accepts Bearer keys, so third-party agents that find OneSource through any of those channels keep working without code changes.
## Migration path
### 1. Sign up for an API key
Follow [Sign up for an API key](/getting-started/api-key/subscribe) at [app.onesource.io/signup](https://app.onesource.io/signup): create your account, confirm your email, and subscribe to the Developer plan. Then create a key under **API Keys** and copy its `sk_…` value from the creation dialog right away, it's shown only once.
### 2. Read the key into your app
[Retrieve your API key](/getting-started/api-key/retrieve-api-key) covers dashboard, env-var, and `.env` patterns. Standard practice: store as `ONESOURCE_API_KEY` in your hosting platform's secret store; read once at startup; cache in memory.
### 3. Swap auth in your client
If you were using `@x402/fetch`:
```diff
- import { wrapFetchWithPayment } from '@x402/fetch';
- import { privateKeyToAccount } from 'viem/accounts';
-
- const account = privateKeyToAccount(process.env.X402_PRIVATE_KEY);
- const skillsFetch = wrapFetchWithPayment(fetch, { account, chain: 'base' });
+ const skillsFetch = (url: string, init: RequestInit = {}) =>
+ fetch(url, {
+ ...init,
+ headers: { ...init.headers, Authorization: `Bearer ${process.env.ONESOURCE_API_KEY}` },
+ });
const res = await skillsFetch('https://api.onesource.io/api/chain/network-info');
```
If you were using AgentCash MCP or an MPP-aware fetcher:
```diff
{
"mcpServers": {
"agentcash": { "command": "npx", "args": ["-y", "agentcash-mcp"] }
+ ,
+ "onesource": {
+ "command": "npx",
+ "args": ["-y", "@one-source/mcp@latest"],
+ "env": { "ONESOURCE_API_KEY": "sk_…" }
+ }
}
}
```
The OneSource MCP Server gives the agent direct, key-authenticated access; AgentCash and other wallet-paid clients can stay for non-OneSource x402 endpoints.
### 4. Update error handling
With Bearer-key auth the failure surface shifts from x402 / MPP payment errors to gateway auth errors plus envelope errors from the service. The gateway returns a flat `{error, message}` shape for auth/routing problems (`invalid_token`, `api_key_required`, `plan_required`) while the OneSource service still returns the canonical `{data, error, meta}` envelope for parameter validation and on-chain failures. See [Error codes](/guides/error-codes) for the full catalog and a normalizer that handles both shapes.
If your retry logic only handles the `402 → sign → retry` loop, replace it with a 5xx backoff path. Rate-limit headers (`X-RateLimit-*`) are advisory today, so back off proactively when `Remaining` trends low rather than waiting for a `429`.
### 5. Smoke test
Run the unified verify call from [First request](/getting-started/api-key/first-request) once with the new key. Compare to your existing x402 / MPP logs: `data` should be byte-equal for the same input.
## Pricing comparison
A worked example for a workload making 30 calls/minute (~43,200 calls/day, ~1.3M calls/month), assuming an even mix of paid endpoints:
| Path | Cost basis | Approximate monthly cost |
|---|---|---|
| x402 (avg $0.004/call) | 1.3M × $0.004 + Base settlement gas | **~$5,200 + gas** |
| MPP (avg $0.004/call, lower gas) | 1.3M × $0.004 + Tempo settlement | **~$5,200 + minimal gas** |
| Bearer API key | Fixed monthly plan covering the request volume | **plan price** |
For sustained > ~$1K/month equivalents, the monthly plan typically wins. Below that, x402 or MPP can be cheaper *and* gives you no rate cap.
## Running both in parallel
Nothing forces you to pick one. The OneSource service accepts Bearer keys, x402 signatures, and MPP credentials in parallel; they coexist on the same endpoints. Common dual-mode patterns:
- **API key as primary, wallet-paid as overflow.** Your default client uses the Bearer key; when `X-RateLimit-Remaining` drops near zero (or, once enforcement lands, when you see a `429`), fall back to an x402- or MPP-signed call for that request. Plan for two-or-three orders-of-magnitude burst capacity without paying for the headroom every month.
- **API key for production paths, wallet-paid for experiments.** Your prod service uses the Bearer key; ad-hoc scripts or one-off agents pay per call.
Both paths are first-class and tested.
## Keeping your discovery footprint visible
If you actively rely on discovery (agents finding OneSource through AgenticMarket / CDP Bazaar, AgentCash, x402scan, mppScan, the MCP Registry, Glama, or npm) those listings remain live and unaffected by which auth path your own traffic uses. Even if you switch your *own* calls to Bearer-key auth, third-party agents discovering you through any wallet-paid channel will continue to pay-per-call via x402 or MPP without disruption.
---
## Build an x402 batch-settlement client
OneSource advertises a `batch-settlement` scheme on every `/api/chain/*` route (see
[x402 on Base → Batch settlement](/getting-started/x402-and-mpp/x402-base#batch-settlement)
for how it works and when it pays off). The common x402 clients, `x402-fetch`,
AgentCash, the Coinbase agentic wallet (`awal`), only implement the **exact** scheme,
so they ignore the batch challenge and keep paying per call. To actually batch, you need a
client that implements the scheme: open a channel with one deposit, sign an off-chain
voucher per call, and let OneSource settle the cumulative total in a single on-chain claim.
This guide is that client, end to end. It's the reference implementation we use to
validate the feature against mainnet: roughly 60 lines of TypeScript on top of the
official `@x402` SDK. Copy it, point it at your wallet, and run.
:::note Prefer not to write code?
If you just want batch from an AI assistant, the
[OneSource MCP Server's batch mode](/getting-started/mcp/configuration#batch-settlement-payment-channels)
does it with one env var (`X402_PAYMENT_MODE=batch`): no client code. This guide is for
building a **custom** client or wiring batch into your own app.
Either way, batch only earns its keep for a **burst of calls from one long-lived
process**: the channel state (deposit, cumulative voucher) has to persist across calls.
For a handful of one-off requests, plain per-call [x402](/getting-started/x402-and-mpp/x402-base)
is simpler and cheaper. See [Batching and cost optimization](/guides/batching-and-cost-optimization)
to decide.
:::
## Prerequisites
- **Node.js ≥ 24**.
- **A wallet funded with USDC on Base** (mainnet asset `0x833589…`). The first call
deposits `price × depositMultiplier` into the channel escrow, so fund a little above
one call's price. Per-call prices are on each
[API Reference](/api-reference/) page.
- The wallet's **private key**. Treat it like any secret: load it from the environment,
never commit it.
## Project setup
Create a directory with three files.
**`package.json`**
```json
{
"name": "onesource-x402-batch-client",
"private": true,
"type": "module",
"scripts": {
"start": "tsx index.ts"
},
"dependencies": {
"@x402/evm": "2.14.0",
"@x402/fetch": "2.14.0",
"dotenv": "^16.4.7",
"viem": "^2.48.11"
},
"devDependencies": {
"tsx": "^4.21.0",
"typescript": "^5.7.3"
}
}
```
The batch scheme lives in `@x402/evm` (the scoped SDK family), not the legacy
`x402-fetch` package; that one is exact-only.
**`tsconfig.json`**
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "bundler",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"types": ["node"]
},
"include": ["index.ts"]
}
```
**`.env`**
```bash
EVM_PRIVATE_KEY=0x… # funded Base wallet (the payer)
RESOURCE_SERVER_URL=https://api.onesource.io
ENDPOINT_PATH=/api/chain/block-number # any priced /api/chain/* route
NUMBER_OF_REQUESTS=3
DEPOSIT_MULTIPLIER=5 # deposit = price × this on channel open
STORAGE_DIR=./channel-storage # persist channel state across runs
# CHANNEL_SALT=0x… # bump for a fresh channel (see below)
# REFUND_AFTER_REQUESTS=true # reclaim unused deposit at the end
```
Then `npm install`.
## The client
**`index.ts`**
```ts
config();
const privateKey = process.env.EVM_PRIVATE_KEY?.trim() as `0x${string}`;
if (!privateKey) throw new Error('EVM_PRIVATE_KEY is required');
const baseURL = process.env.RESOURCE_SERVER_URL ?? 'https://api.onesource.io';
const endpointPath = process.env.ENDPOINT_PATH ?? '/api/chain/block-number';
const url = `${baseURL}${endpointPath}`;
const storageDir = process.env.STORAGE_DIR;
const channelSalt = (process.env.CHANNEL_SALT ?? `0x${'0'.repeat(64)}`) as `0x${string}`;
const numberOfRequests = Number(process.env.NUMBER_OF_REQUESTS ?? '3');
const depositMultiplier = Number(process.env.DEPOSIT_MULTIPLIER ?? '5');
const refundAtEnd = process.env.REFUND_AFTER_REQUESTS === 'true';
async function main(): Promise {
const account = privateKeyToAccount(privateKey);
// The public client lets the scheme read chain state and submit the deposit.
// Pass a URL to http() to use your own RPC instead of the public default.
const publicClient = createPublicClient({ chain: base, transport: http() });
const signer = toClientEvmSigner(account, publicClient);
const scheme = new BatchSettlementEvmScheme(signer, {
depositPolicy: { depositMultiplier },
salt: channelSalt,
// Persist channel + voucher state to disk so a later run reuses the same
// channel instead of opening (and funding) a new one.
...(storageDir ? { storage: new FileClientChannelStorage({ directory: storageDir }) } : {}),
});
const client = new x402Client();
client.register('eip155:*', scheme);
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
const httpClient = new x402HTTPClient(client);
console.log(`payer ${signer.address} → ${url}\n`);
for (let i = 0; i < numberOfRequests; i++) {
const res = await fetchWithPayment(url, { method: 'GET' });
const result = await httpClient.processResponse(res);
if (result.kind === 'success') {
console.log(`Request ${i + 1}:`, result.body);
// settleResponse.transaction is the on-chain claim hash on the call that
// triggers a settlement; off-chain voucher-only calls have it empty.
console.log(' settle:', JSON.stringify(result.settleResponse));
} else {
console.log(`Request ${i + 1} - ${result.kind}:`, JSON.stringify(result));
}
}
if (refundAtEnd) {
console.log('\nRequesting refund of the unused channel balance…');
console.log(JSON.stringify(await scheme.refund(url), null, 2));
}
}
main().catch((err) => {
console.error(err?.response?.data?.error ?? err);
process.exit(1);
});
```
Run it:
```bash
npm start
```
## What happens on each run
1. **First call: channel open.** The scheme reads the `402`, sees the
`batch-settlement` challenge, and deposits `price × DEPOSIT_MULTIPLIER` USDC into the
escrow contract on Base (one on-chain transaction). It signs a voucher for this call's
amount and retries; OneSource serves the response.
2. **Subsequent calls: off-chain vouchers.** Each call signs a new voucher for the
*cumulative* amount and sends it in the payment header. No on-chain transaction:
`settleResponse.transaction` is empty for these.
3. **Settlement: one claim.** OneSource (the channel authorizer) redeems accrued vouchers
in a single on-chain claim on a short interval, transferring the cumulative total from
escrow to the receiver.
So N calls cost **one deposit + one claim** in gas instead of N settlements. With the
example's 3 calls you'll see the deposit tx on call 1, empty settle on calls 2–3, and the
cumulative total claimed in one transaction. Verify any hash on
[basescan.org](https://basescan.org).
## Channel lifecycle
- **Persistent storage.** `STORAGE_DIR` keeps the channel record on disk. A later run
with the **same wallet + same `CHANNEL_SALT`** reuses the open channel: no second
deposit. This is why batch only makes sense in a long-lived process or across runs that
share that directory; a fresh process with no stored state opens a new channel every
time (worst of both worlds).
- **A fresh channel.** Change `CHANNEL_SALT` to any new 32-byte hex value to open a
separate channel under the same wallet (useful when the previous one is drained or
pending withdrawal).
- **The unused deposit.** Because you deposit `price × multiplier`, a residual stays
locked in escrow after your calls. Set `REFUND_AFTER_REQUESTS=true` to call
`scheme.refund(url)` and reclaim it, but note the on-chain **withdraw delay** (~1 day
on mainnet) before the funds are releasable. Lower `DEPOSIT_MULTIPLIER` to shrink the
residual, at the cost of re-depositing sooner if the burst runs long.
:::warning Switching away mid-channel
Any unclaimed-but-deposited balance is locked until the withdraw delay elapses. Don't
open a large channel for a workload you might abandon; size the deposit to the burst.
:::
## Caveats
- The two batch-capable paths today are this custom client and the
[OneSource MCP Server's batch mode](/getting-started/mcp/configuration#batch-settlement-payment-channels).
Standard third-party agent wallets (`x402-fetch`, AgentCash, Coinbase `awal`) are
exact-only and will silently pay per call against the same endpoints; that's fine,
just not batched.
- The scheme is published in TypeScript and Go. This guide is the TypeScript path; the Go
SDK exposes the equivalent batch-settlement client.
- For the protocol-level contract (authorizer model, voucher format, withdraw mechanics),
see the [x402 batch-settlement spec](https://docs.x402.org/schemes/batch-settlement).
## Next
- [x402 on Base](/getting-started/x402-and-mpp/x402-base): the per-call scheme and the conceptual batch overview
- [Batching and cost optimization](/guides/batching-and-cost-optimization): when batching beats other savings
- [MPP on Tempo](/getting-started/x402-and-mpp/mpp-tempo): the same amortization model on Tempo
---
## Overview
# OneSource REST API & MCP Server
A REST and MCP interface to live Ethereum data: balances, NFTs, transactions, events, contracts, ENS, and chain utilities. Serves Ethereum mainnet by default and the Sepolia testnet via the `network` parameter. Designed for AI agents and apps that need fresh, on-chain answers without running their own node.
- **Base URL:** `https://api.onesource.io`
- **MCP server:** [`@one-source/mcp`](https://www.npmjs.com/package/@one-source/mcp) on npm
- **OpenAPI:** [`/openapi.json`](https://api.onesource.io/openapi.json)
- **AI-optimized docs:** [`/llms-full.txt`](pathname:///llms-full.txt)
## Three ways to access
| Access path | How you authenticate | Best for |
|---|---|---|
| **API key** | `Authorization: Bearer sk_…` | Production apps, predictable monthly billing via Stripe |
| **MCP server** | `ONESOURCE_API_KEY=sk_…` (or an x402 / MPP wallet) | AI assistants: Claude Code, Claude Desktop, Cursor, Windsurf |
| **x402 / MPP** | USDC on Base (x402) or USDC.e on Tempo (MPP) | Pay-per-call from agents, no signup required |
See [Getting Started](/getting-started/) to pick a path.
## Response envelope
Every `/api/*` response is wrapped:
```json
{ "data": { ... }, "error": null, "meta": { "endpoint": "/api/chain/network-info", "request_id": "…" } }
```
On failure, `data` is `null` and `error` is `{ code, message }`. Standard HTTP status codes apply (`200`, `400`, `401`, `403`, `404`, `429`).
## What's covered
- **Chain RPC:** `eth_call`, gas estimate, code, nonce, storage, receipts, blocks, pending block, network info
- **Balances:** ETH + ERC20 (batched), ERC1155, ERC20 allowances and total supply
- **NFTs:** ownership, metadata with IPFS/Arweave resolution, ERC721 token enumeration
- **Logs:** `eth_getLogs`, ERC20 transfer history
- **ENS:** forward resolution
- **Identity:** proxy detection, contract introspection
- **Free utilities:** pricing, health, OpenAPI, sitemap, llms.txt
Full endpoint catalog: [API Reference](/api-reference/).