Authentication
Every request to the Bootnode API must be authenticated. This guide covers how to get your API key, use it in requests, handle rate limits, and manage errors.
Get Your API Key
Sign in to your Bootnode dashboard at dashboard.bootnode.dev and navigate to Settings → API Keys.
Key types
| Prefix | Environment | Use for |
|---|---|---|
| bn_live_ | Production | Live mainnet requests |
| bn_test_ | Testnet | Development and testing (testnets only) |
Security
Never expose your API key in client-side code, public repositories, or browser network requests. Always use server-side code or environment variables.
# .env.local (Next.js, Vite, etc.)
BOOTNODE_API_KEY=bn_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
# Never commit this file to version control.
# Add .env.local to your .gitignore.Use the X-API-Key Header
The recommended authentication method. Pass your API key in the X-API-Key header.
# curl
curl https://api.bootnode.dev/v1/gas/ethereum/prices \
-H "X-API-Key: bn_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
# TypeScript / fetch
const res = await fetch("https://api.bootnode.dev/v1/gas/ethereum/prices", {
headers: {
"X-API-Key": process.env.BOOTNODE_API_KEY!,
},
});
# Python / requests
import requests
res = requests.get(
"https://api.bootnode.dev/v1/gas/ethereum/prices",
headers={"X-API-Key": "bn_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"},
)
# Go / net/http
req, _ := http.NewRequest("GET", "https://api.bootnode.dev/v1/gas/ethereum/prices", nil)
req.Header.Set("X-API-Key", "bn_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6")Use the Authorization Bearer Header
Alternatively, use the standard Bearer token format. Both methods are equivalent.
# curl
curl https://api.bootnode.dev/v1/gas/ethereum/prices \
-H "Authorization: Bearer bn_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
# TypeScript / fetch
const res = await fetch("https://api.bootnode.dev/v1/gas/ethereum/prices", {
headers: {
Authorization: `Bearer ${process.env.BOOTNODE_API_KEY}`,
},
});
# Python / requests
import requests
res = requests.get(
"https://api.bootnode.dev/v1/gas/ethereum/prices",
headers={"Authorization": "Bearer bn_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"},
)Rate Limiting
All API responses include rate limit headers. When you exceed your limit, the API returns HTTP 429.
| Plan | Requests / sec | Requests / day | Compute Units / sec |
|---|---|---|---|
| Free | 10 | 100,000 | 200 |
| Growth | 100 | 5,000,000 | 2,000 |
| Enterprise | Custom | Custom | Custom |
Response headers:
X-RateLimit-Limit: 100 # Max requests per second
X-RateLimit-Remaining: 97 # Remaining in current window
X-RateLimit-Reset: 1706400000 # Unix timestamp when window resets
Retry-After: 1 # Seconds to wait (only on 429)Different methods consume different compute units (CU). Simple reads like eth_blockNumber cost 1 CU, while archive calls like eth_getStorageAt with a historical block cost 20 CU.
Error Handling
Bootnode returns structured error responses. Always check the HTTP status code and handle errors gracefully with retries for transient failures.
// Robust API client with retry logic
async function bootnodeRequest(
path: string,
options: RequestInit = {}
): Promise<any> {
const maxRetries = 3;
const baseDelay = 1000; // 1 second
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await fetch(`https://api.bootnode.dev/v1${path}`, {
...options,
headers: {
"X-API-Key": process.env.BOOTNODE_API_KEY!,
"Content-Type": "application/json",
...options.headers,
},
});
// Success
if (res.ok) {
return res.json();
}
// Parse error
const error = await res.json().catch(() => ({
error: { code: "unknown", message: res.statusText, status: res.status },
}));
// Don't retry client errors (except rate limits)
if (res.status !== 429 && res.status < 500) {
throw new Error(
`Bootnode API error [${error.error.code}]: ${error.error.message}`
);
}
// Retry with exponential backoff
if (attempt < maxRetries) {
const retryAfter = res.headers.get("Retry-After");
const delay = retryAfter
? parseInt(retryAfter, 10) * 1000
: baseDelay * Math.pow(2, attempt);
console.warn(
`Bootnode request failed (attempt ${attempt + 1}/${maxRetries + 1}), ` +
`retrying in ${delay}ms...`
);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
throw new Error("Bootnode API: max retries exceeded");
}
// Usage:
// const prices = await bootnodeRequest("/gas/ethereum/prices");
// const wallet = await bootnodeRequest("/wallets/create", {
// method: "POST",
// body: JSON.stringify({ owner: "0x...", chain: "base" }),
// });Error Response Format
// 401 Unauthorized
{
"error": {
"code": "unauthorized",
"message": "Invalid API key. Check your X-API-Key header.",
"status": 401
}
}
// 429 Rate Limited
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded. Retry after 1 second.",
"status": 429
}
}
// 400 Bad Request
{
"error": {
"code": "invalid_request",
"message": "Missing required parameter: chain",
"status": 400
}
}API Key Scopes
When creating an API key, you can restrict it to specific scopes for least-privilege access:
| Scope | Access |
|---|---|
| rpc:read | JSON-RPC read methods (eth_call, eth_getBalance, etc.) |
| rpc:write | JSON-RPC write methods (eth_sendRawTransaction) |
| data:read | Token, NFT, and transfer APIs |
| wallets:manage | Create and manage smart wallets |
| webhooks:manage | Create, update, and delete webhooks |
| gas:manage | Gas policies and sponsorship |
| bundler:send | Submit UserOperations to the bundler |