Troubleshooting Guide
Comprehensive troubleshooting guide for FortiBlox Nexus API - common errors, solutions, and debugging tools
Troubleshooting Guide
This guide covers common issues you might encounter when using FortiBlox Nexus API and how to resolve them.
Common API Errors
401 Unauthorized
What it means: Your API key is either missing, invalid, or has been revoked.
Error response:
{
"error": {
"code": 401,
"message": "Unauthorized: Invalid API key"
}
}How to fix it:
-
Verify your API key format
- Production keys start with
fbx_ - Test keys start with
fbx_test_ - Keys should be 64-70 characters long
- Production keys start with
-
Check for extra spaces or newlines
# Bad - has extra space curl -H "X-API-Key: fbx_key123 " # Good curl -H "X-API-Key: fbx_key123" -
Ensure key is active
- Log into Nexus Dashboard
- Navigate to API Keys
- Verify status shows "Active"
-
Check key hasn't expired
- Keys can have expiration dates
- Generate a new key if expired
-
Verify the header name
# Correct -H "X-API-Key: YOUR_KEY" # Also correct -H "Authorization: Bearer YOUR_KEY" # Wrong -H "API-Key: YOUR_KEY"
Quick test: Use curl to isolate the issue:
curl -X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "getHealth"}'403 Forbidden
What it means: Your API key is valid, but you don't have permission for this specific operation.
Error response:
{
"error": {
"code": 403,
"message": "Forbidden: API key not enabled for gRPC streaming"
}
}Common causes:
1. Service not enabled
{
"error": {
"code": 403,
"message": "API key not enabled for geyser:query service"
}
}Solution: Update your API key permissions:
- Go to API Keys in dashboard
- Click "Edit" on your key
- Enable the required service scope
- Save changes
2. Tier restriction
{
"error": {
"code": 403,
"message": "gRPC streaming requires Business tier or higher"
}
}Solution: Upgrade your subscription:
- Free/Developer tiers: Limited gRPC access
- Business tier: 50 gRPC streams
- Professional tier: 200 gRPC streams + mainnet
3. Network restriction
{
"error": {
"code": 403,
"message": "Mainnet access requires Professional tier"
}
}Solution:
- Free/Developer/Business: Devnet only
- Professional/Enterprise: Full mainnet access
4. IP restriction
{
"error": {
"code": 403,
"message": "Request from IP 123.45.67.89 not allowed"
}
}Solution: Update IP restrictions in dashboard:
- Navigate to API Keys
- Edit your key
- Add your current IP to whitelist
- Or remove IP restrictions for development
429 Too Many Requests
What it means: You've exceeded your rate limit or credit quota.
Error response:
{
"error": {
"code": 429,
"message": "Rate limit exceeded: 10 requests/second",
"retryAfter": 1000
}
}Rate limits by tier:
| Tier | RPC/sec | Geyser/sec | Enhanced/sec | WebSocket Connections |
|---|---|---|---|---|
| Free | 10 | 5 | 2 | 5 |
| Developer | 50 | 25 | 10 | 5 |
| Business | 200 | 100 | 50 | 250 |
| Professional | 500 | 200 | 100 | 250 |
How to fix it:
1. Implement exponential backoff
async function makeRequestWithRetry(payload, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await axios.post(endpoint, payload, {
headers: { 'X-API-Key': apiKey }
});
return response.data;
} catch (error) {
if (error.response?.status === 429) {
const retryAfter = error.response.data.error.retryAfter || 1000;
const backoff = retryAfter * Math.pow(2, i);
console.log(`Rate limited. Retrying in ${backoff}ms...`);
await new Promise(resolve => setTimeout(resolve, backoff));
} else {
throw error;
}
}
}
throw new Error('Max retries exceeded');
}2. Check rate limit headers
const response = await axios.post(endpoint, payload, {
headers: { 'X-API-Key': apiKey }
});
console.log('Rate limit:', response.headers['x-ratelimit-limit']);
console.log('Remaining:', response.headers['x-ratelimit-remaining']);
console.log('Reset:', response.headers['x-ratelimit-reset']);3. Implement request batching
// Instead of 100 separate requests
for (let addr of addresses) {
await getBalance(addr); // Rate limited!
}
// Batch into groups
const chunks = chunkArray(addresses, 10);
for (let chunk of chunks) {
await Promise.all(chunk.map(addr => getBalance(addr)));
await sleep(100); // Pace requests
}4. Use WebSocket for real-time data
// Instead of polling every second (60 req/min)
setInterval(async () => {
const slot = await getSlot(); // Rate limited!
}, 1000);
// Use WebSocket subscription (free!)
const ws = new WebSocket('wss://nexus.fortiblox.com/geyser/ws?api-key=KEY');
ws.send(JSON.stringify({
method: 'subscribe',
params: ['slotUpdates']
}));5. Upgrade your tier
If you consistently hit rate limits, consider upgrading:
- Developer tier: $49/mo → 50 req/sec
- Business tier: $499/mo → 200 req/sec
- Professional tier: $999/mo → 500 req/sec
500 Internal Server Error
What it means: Something went wrong on FortiBlox's servers.
Error response:
{
"error": {
"code": 500,
"message": "Internal server error",
"requestId": "req_abc123xyz"
}
}How to fix it:
-
Retry the request
- Server errors are usually temporary
- Wait 1-2 seconds and retry
- Use exponential backoff
-
Check the status page
- Visit status.fortiblox.com
- See if there's an ongoing incident
- Check maintenance schedules
-
Contact support
- Include the
requestIdfrom error response - Email: [email protected]
- Discord: discord.gg/fortiblox
- Include the
Example retry logic:
async function robustRequest(payload) {
const maxRetries = 3;
for (let i = 0; i < maxRetries; i++) {
try {
return await axios.post(endpoint, payload, {
headers: { 'X-API-Key': apiKey }
});
} catch (error) {
if (error.response?.status === 500 && i < maxRetries - 1) {
console.log(`Server error. Retrying... (${i + 1}/${maxRetries})`);
await sleep(Math.pow(2, i) * 1000);
} else {
throw error;
}
}
}
}503 Service Unavailable
What it means: The API is temporarily unavailable, usually due to maintenance or high load.
Error response:
{
"error": {
"code": 503,
"message": "Service temporarily unavailable",
"retryAfter": 30
}
}How to fix it:
-
Respect Retry-After header
if (error.response?.status === 503) { const retryAfter = error.response.headers['retry-after'] || 30; console.log(`Service unavailable. Retrying in ${retryAfter}s`); await sleep(retryAfter * 1000); } -
Check for scheduled maintenance
- Visit status.fortiblox.com
- Subscribe to status updates
- Maintenance is usually announced 24-48 hours in advance
-
Implement fallback logic
try { return await primaryRequest(); } catch (error) { if (error.response?.status === 503) { console.log('Primary unavailable, using fallback...'); return await fallbackRequest(); } throw error; }
Professional & Enterprise tiers have 99.9% uptime SLA with automatic failover to backup regions.
Authentication Issues
Invalid API Key Format
Symptoms:
- "Invalid API key format" error
- 401 Unauthorized immediately
Common mistakes:
// Wrong - missing prefix
const apiKey = "e2372852b2fbfcfa89a9f2d77d71c80d";
// Wrong - truncated
const apiKey = "fbx_e2372852b2fbfcfa89a9f2d77d71";
// Wrong - has newline
const apiKey = "fbx_e2372852b2fbfcfa89a9f2d77d71c80d39f46298\n";
// Correct
const apiKey = "fbx_e2372852b2fbfcfa89a9f2d77d71c80d39f46298af8f0fdd24a75c17c7b508ee";Solution:
// Trim whitespace
const apiKey = process.env.FORTIBLOX_API_KEY.trim();
// Validate format
if (!apiKey.startsWith('fbx_')) {
throw new Error('Invalid API key format');
}
if (apiKey.length < 64) {
throw new Error('API key too short');
}Missing Required Headers
Symptoms:
- 401 Unauthorized
- "Missing authentication header" error
Wrong:
// Missing X-API-Key header
const response = await axios.post('https://nexus.fortiblox.com/rpc', {
jsonrpc: '2.0',
method: 'getHealth'
});Correct:
// Include X-API-Key header
const response = await axios.post('https://nexus.fortiblox.com/rpc', {
jsonrpc: '2.0',
method: 'getHealth'
}, {
headers: {
'X-API-Key': process.env.FORTIBLOX_API_KEY,
'Content-Type': 'application/json'
}
});Also correct:
// Authorization header
const response = await axios.post('https://nexus.fortiblox.com/rpc', {
jsonrpc: '2.0',
method: 'getHealth'
}, {
headers: {
'Authorization': `Bearer ${process.env.FORTIBLOX_API_KEY}`,
'Content-Type': 'application/json'
}
});Key Not Enabled for Service
Symptoms:
- 403 Forbidden
- "Service not enabled" error
Check your key's scopes:
- Log into Nexus Dashboard
- Navigate to API Keys
- Click on your key
- View "Enabled Services" section
Required scopes:
| Service | Required Scope |
|---|---|
| RPC Nodes | rpc:read |
| RPC Write | rpc:write |
| Geyser API | geyser:query |
| WebSocket | geyser:stream |
| gRPC Streaming | grpc:stream |
| Enhanced API | enhanced:read |
Solution:
- Edit your API key
- Enable the required service
- Save changes
- Wait 1-2 minutes for changes to propagate
Tier Restrictions
Symptoms:
- 403 Forbidden
- "Requires [tier] or higher" error
Feature availability by tier:
| Feature | Free | Developer | Business | Professional |
|---|---|---|---|---|
| RPC Access | ✅ | ✅ | ✅ | ✅ |
| Geyser API | ✅ | ✅ | ✅ | ✅ |
| WebSocket | ✅ (5) | ✅ (5) | ✅ (250) | ✅ (250) |
| gRPC Devnet | ❌ | ✅ | ✅ | ✅ |
| gRPC Mainnet | ❌ | ❌ | ❌ | ✅ |
| Webhooks | ❌ | ❌ | ✅ | ✅ |
| SQL Analytics | ❌ | ❌ | ✅ | ✅ |
Solution: Upgrade your tier →
Rate Limiting
How to Check Rate Limits
Response headers:
curl -v -X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "getHealth"}'Headers to check:
X-RateLimit-Limit: 50 # Max requests per second
X-RateLimit-Remaining: 45 # Requests left in current window
X-RateLimit-Reset: 1678901234 # Unix timestamp when limit resets
X-Credits-Used: 1 # Credits consumed by this request
X-Credits-Remaining: 9999999 # Credits left this monthMonitor in JavaScript:
const response = await axios.post(endpoint, payload, {
headers: { 'X-API-Key': apiKey }
});
const rateLimit = {
limit: response.headers['x-ratelimit-limit'],
remaining: response.headers['x-ratelimit-remaining'],
reset: new Date(response.headers['x-ratelimit-reset'] * 1000),
creditsUsed: response.headers['x-credits-used'],
creditsRemaining: response.headers['x-credits-remaining']
};
console.log('Rate limit status:', rateLimit);
// Warn if running low
if (rateLimit.remaining < 5) {
console.warn('Approaching rate limit! Slowing down...');
}What to Do When Rate Limited
1. Implement smart throttling:
class RateLimiter {
constructor(maxPerSecond) {
this.maxPerSecond = maxPerSecond;
this.requests = [];
}
async throttle() {
const now = Date.now();
// Remove requests older than 1 second
this.requests = this.requests.filter(time => now - time < 1000);
// Wait if at limit
if (this.requests.length >= this.maxPerSecond) {
const oldestRequest = Math.min(...this.requests);
const waitTime = 1000 - (now - oldestRequest);
await new Promise(resolve => setTimeout(resolve, waitTime));
}
this.requests.push(Date.now());
}
}
const limiter = new RateLimiter(10); // 10 req/sec for Free tier
async function makeRequest(payload) {
await limiter.throttle();
return axios.post(endpoint, payload, {
headers: { 'X-API-Key': apiKey }
});
}2. Use request queuing:
const PQueue = require('p-queue');
const queue = new PQueue({
intervalCap: 10, // Max 10 requests
interval: 1000 // Per 1 second
});
async function queuedRequest(payload) {
return queue.add(() =>
axios.post(endpoint, payload, {
headers: { 'X-API-Key': apiKey }
})
);
}3. Cache responses:
const cache = new Map();
async function cachedGetBalance(address) {
const cacheKey = `balance:${address}`;
// Check cache
if (cache.has(cacheKey)) {
const { data, timestamp } = cache.get(cacheKey);
if (Date.now() - timestamp < 5000) { // 5 second TTL
return data;
}
}
// Fetch fresh data
const response = await axios.post(endpoint, {
jsonrpc: '2.0',
id: 1,
method: 'getBalance',
params: [address]
}, {
headers: { 'X-API-Key': apiKey }
});
// Update cache
cache.set(cacheKey, {
data: response.data,
timestamp: Date.now()
});
return response.data;
}Credit System Explained
Credit costs:
| Operation | Credits | Example |
|---|---|---|
| Simple RPC | 1 | getHealth, getSlot, getBalance |
| Heavy RPC | 3-5 | getBlock, getProgramAccounts, getTransaction |
| Geyser query | 1 | List transactions, blocks |
| Complex Geyser | 5-10 | Filtered queries, joins |
| Enhanced API | 5-10 | Parse transaction, NFT metadata |
| WebSocket msg | 0 | Real-time updates (free!) |
Example calculation:
// Scenario: DEX monitoring app
// - Poll 10 token prices every 5 seconds
// - Check transaction status 100 times/hour
// - Stream real-time trades via WebSocket
// Price polling
// 10 tokens × 12 polls/minute × 60 minutes × 24 hours = 172,800 requests/day
// 1 credit each = 172,800 credits/day
// Transaction checks
// 100 checks/hour × 24 hours = 2,400 requests/day
// 3 credits each = 7,200 credits/day
// WebSocket streaming
// Unlimited messages = 0 credits
// Total: 180,000 credits/day × 30 days = 5.4 million credits/month
// Recommended: Developer tier (10M credits/month)View your usage:
- Go to Dashboard
- Navigate to Usage tab
- See:
- Credits used today/this month
- Most expensive endpoints
- Projected monthly usage
- Upgrade recommendations
Upgrade Paths
When to upgrade:
Free → Developer ($49/mo)
- Hitting 1M credit limit
- Need > 10 req/sec
- Want gRPC streaming
- Need email support
Developer → Business ($499/mo)
- Hitting 10M credit limit
- Need > 50 req/sec
- Need 250 WebSocket connections
- Want webhooks
- Need priority support
Business → Professional ($999/mo)
- Hitting 100M credit limit
- Need > 200 req/sec
- Need mainnet gRPC access
- Need 99.99% SLA
- Need SQL analytics
Professional → Enterprise (Custom)
- Need > 200M credits/month
- Need custom SLA
- Need dedicated infrastructure
- Need white-label solution
RPC Method Errors
Account Not Found
Error response:
{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "Invalid params: Account not found"
},
"id": 1
}Common causes:
1. Wrong address format:
// Wrong - includes extra characters
const address = "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK\n";
// Correct - clean base58 string
const address = "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK";2. Account doesn't exist yet:
// Check if account exists first
const response = await axios.post(endpoint, {
jsonrpc: '2.0',
id: 1,
method: 'getAccountInfo',
params: [address]
}, {
headers: { 'X-API-Key': apiKey }
});
if (response.data.result.value === null) {
console.log('Account does not exist');
} else {
console.log('Account data:', response.data.result.value);
}3. Wrong network:
// Make sure you're on the right network
const endpoint = 'https://nexus.fortiblox.com/rpc'; // Mainnet
// vs
const endpoint = 'https://nexus.fortiblox.com/devnet/rpc'; // DevnetInvalid Parameters
Error response:
{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "Invalid params: expected array of length 2, got 1"
},
"id": 1
}Common mistakes:
1. Wrong parameter count:
// Wrong - missing commitment parameter
{
method: 'getBalance',
params: ['DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK']
}
// Correct - include configuration object
{
method: 'getBalance',
params: [
'DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK',
{ commitment: 'confirmed' }
]
}2. Wrong parameter type:
// Wrong - slot as string
{
method: 'getBlock',
params: ['12345']
}
// Correct - slot as number
{
method: 'getBlock',
params: [12345]
}3. Invalid configuration:
// Wrong - invalid commitment
{
method: 'getBalance',
params: [address, { commitment: 'immediate' }] // 'immediate' removed
}
// Correct - use valid commitment
{
method: 'getBalance',
params: [address, { commitment: 'confirmed' }] // 'confirmed' or 'finalized'
}Valid commitment levels:
processed- Fastest, least secureconfirmed- Balanced (recommended)finalized- Slowest, most secure
Transaction Not Found
Error response:
{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "Transaction not found"
},
"id": 1
}Common causes:
1. Transaction not yet confirmed:
// Send transaction
const signature = await sendTransaction(transaction);
// Wrong - query immediately
const tx = await getTransaction(signature); // May not exist yet!
// Correct - wait for confirmation
await confirmTransaction(signature);
const tx = await getTransaction(signature);2. Transaction too old:
// Free/Developer tiers may not have full history
// Professional tier: Unlimited history
// Check transaction age
const signature = '5w8Z...';
try {
const tx = await getTransaction(signature);
} catch (error) {
if (error.message.includes('not found')) {
console.log('Transaction may be too old or pruned');
}
}3. Wrong signature format:
// Wrong - truncated signature
const sig = "5w8ZHp";
// Correct - full base58 signature (87-88 chars)
const sig = "5w8ZHpn7VNpN8FwGcPqKq4KZv6b2CqKjW2BvmKw3fFqm8e9VnN6FwGcPqKq4KZv6b2CqKjW2BvmKw";Commitment Level Issues
Symptoms:
- Inconsistent data
- Missing transactions
- Stale balances
Understanding commitment levels:
// Processed - Node has processed the transaction
// - Fastest (< 1 second)
// - Can be rolled back
// - Use for: Real-time UI updates
// Confirmed - Supermajority of cluster confirmed
// - Balanced (2-3 seconds)
// - Unlikely to roll back
// - Use for: Most applications (recommended)
// Finalized - Block is finalized
// - Slowest (10-15 seconds)
// - Cannot be rolled back
// - Use for: Critical transactions, complianceExample:
// Trading app - use confirmed
const balance = await getBalance(address, { commitment: 'confirmed' });
// Financial audit - use finalized
const balance = await getBalance(address, { commitment: 'finalized' });
// Real-time dashboard - use processed
const balance = await getBalance(address, { commitment: 'processed' });Geyser API Issues
Pagination Problems
Symptoms:
- Missing results
- Duplicate results
- Incorrect total count
Common mistakes:
1. Not handling pagination:
// Wrong - only gets first page
const response = await axios.get(
'https://nexus.fortiblox.com/geyser/transactions?limit=100',
{ headers: { 'X-API-Key': apiKey } }
);
// Only returns 100 transactions!
// Correct - paginate through all results
async function getAllTransactions(filter) {
let allTxs = [];
let cursor = null;
do {
const url = cursor
? `https://nexus.fortiblox.com/geyser/transactions?limit=100&cursor=${cursor}`
: `https://nexus.fortiblox.com/geyser/transactions?limit=100`;
const response = await axios.get(url, {
headers: { 'X-API-Key': apiKey }
});
allTxs = allTxs.concat(response.data.data);
cursor = response.data.pagination?.nextCursor;
} while (cursor);
return allTxs;
}2. Incorrect cursor usage:
// Wrong - using page numbers
const page2 = await fetch(`/geyser/transactions?page=2`);
// Correct - using cursor from previous response
const page1 = await fetch(`/geyser/transactions?limit=100`);
const nextCursor = page1.pagination.nextCursor;
const page2 = await fetch(`/geyser/transactions?limit=100&cursor=${nextCursor}`);3. Rate limiting during pagination:
// Wrong - too fast
async function getAllPages() {
let cursor = null;
do {
const response = await fetch(url + cursor);
cursor = response.pagination.nextCursor;
} while (cursor); // Rate limited!
}
// Correct - add delays
async function getAllPages() {
let cursor = null;
do {
const response = await fetch(url + cursor);
cursor = response.pagination.nextCursor;
await sleep(100); // Respect rate limits
} while (cursor);
}Missing Data
Symptoms:
- Empty results
- Null fields
- Incomplete objects
Common causes:
1. Data not available yet:
// Recent transactions may not be indexed
const response = await axios.get(
'https://nexus.fortiblox.com/geyser/transactions',
{ headers: { 'X-API-Key': apiKey } }
);
// Data typically available within 1-2 seconds
// If missing, wait and retry2. Filters too restrictive:
// Wrong - no results because filters too narrow
const txs = await getTransactions({
before: '2024-01-01',
after: '2024-01-01', // Same date
type: 'transfer',
minAmount: 1000000
});
// Correct - loosen filters
const txs = await getTransactions({
after: '2024-01-01',
type: 'transfer'
});3. Wrong field names:
// Check API documentation for correct field names
// Fields are case-sensitive!
// Wrong
filter: { transaction_type: 'transfer' }
// Correct
filter: { type: 'transfer' }Slow Queries
Symptoms:
- Requests timeout
- Response times > 5 seconds
- Inconsistent performance
Optimization tips:
1. Use specific filters:
// Slow - queries entire database
const txs = await getTransactions();
// Fast - narrows search space
const txs = await getTransactions({
account: 'DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK',
after: '2024-01-01',
limit: 100
});2. Limit result size:
// Slow - returns 10,000 rows
const txs = await getTransactions({ limit: 10000 });
// Fast - returns 100 rows
const txs = await getTransactions({ limit: 100 });3. Use indexed fields:
// Fast - indexed fields
const txs = await getTransactions({
account: 'ADDRESS', // Indexed
slot: 12345, // Indexed
signature: 'SIGNATURE' // Indexed
});
// Slow - unindexed fields
const txs = await getTransactions({
memo: 'hello', // Not indexed
customField: 'value' // Not indexed
});4. Cache results:
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 60 }); // 60 second TTL
async function getCachedTransactions(account) {
const cacheKey = `txs:${account}`;
// Check cache
let txs = cache.get(cacheKey);
if (txs) return txs;
// Fetch from API
txs = await getTransactions({ account });
// Store in cache
cache.set(cacheKey, txs);
return txs;
}Filter Syntax
Common mistakes:
1. Wrong comparison operators:
// Wrong - SQL syntax doesn't work
filter: { amount: '>= 1000' }
// Correct - use JSON operators
filter: { amount: { $gte: 1000 } }2. Invalid date formats:
// Wrong - invalid format
filter: { timestamp: '01/15/2024' }
// Correct - ISO 8601
filter: { timestamp: '2024-01-15T00:00:00Z' }
// Also correct - Unix timestamp
filter: { timestamp: 1705276800 }3. Complex filters:
// AND operation
filter: {
type: 'transfer',
amount: { $gte: 1000 },
status: 'confirmed'
}
// OR operation
filter: {
$or: [
{ type: 'transfer' },
{ type: 'swap' }
]
}
// NOT operation
filter: {
type: { $ne: 'unknown' }
}Filter operators:
$eq- Equal$ne- Not equal$gt- Greater than$gte- Greater than or equal$lt- Less than$lte- Less than or equal$in- In array$nin- Not in array
WebSocket Issues
Connection Failures
Symptoms:
- Connection immediately closes
- "Failed to connect" error
- Timeout during handshake
Common causes:
1. Wrong WebSocket URL:
// Wrong - HTTP instead of WebSocket
const ws = new WebSocket('https://nexus.fortiblox.com/geyser/ws');
// Correct - Use wss:// protocol
const ws = new WebSocket('wss://nexus.fortiblox.com/geyser/ws');2. Firewall blocking WebSocket:
# Test WebSocket connectivity
wscat -c wss://nexus.fortiblox.com/geyser/ws?api-key=YOUR_KEY
# If this fails, check:
# - Firewall allows port 443
# - Proxy supports WebSocket
# - Corporate network not blocking3. Connection limits exceeded:
// Check how many connections you have open
// Free/Developer: Max 5 connections
// Business/Professional: Max 250 connections
// Close unused connections
ws.close();Solution - implement reconnection:
class ReconnectingWebSocket {
constructor(url, options = {}) {
this.url = url;
this.reconnectDelay = 1000;
this.maxReconnectDelay = 30000;
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.on('open', () => {
console.log('Connected');
this.reconnectDelay = 1000; // Reset delay
});
this.ws.on('close', () => {
console.log(`Reconnecting in ${this.reconnectDelay}ms...`);
setTimeout(() => this.connect(), this.reconnectDelay);
// Exponential backoff
this.reconnectDelay = Math.min(
this.reconnectDelay * 2,
this.maxReconnectDelay
);
});
this.ws.on('error', (error) => {
console.error('WebSocket error:', error);
});
}
}Authentication Errors
Symptoms:
- 401 Unauthorized on connect
- Connection closes immediately
- "Invalid API key" message
Solutions:
1. Query parameter authentication (browser):
// Correct for browsers
const ws = new WebSocket(
`wss://nexus.fortiblox.com/geyser/ws?api-key=${apiKey}`
);2. Header authentication (Node.js):
// Correct for Node.js
const WebSocket = require('ws');
const ws = new WebSocket('wss://nexus.fortiblox.com/geyser/ws', {
headers: {
'X-API-Key': apiKey
}
});3. Check key scopes:
// Key must have 'geyser:stream' scope
// Verify in dashboard:
// https://nexus.fortiblox.com/api-keysLearn more about WebSocket authentication →
Subscription Problems
Symptoms:
- Not receiving messages
- "Invalid subscription" error
- Unexpected disconnections
Common mistakes:
1. Wrong subscription format:
// Wrong - invalid JSON
ws.send('subscribe to slotUpdates');
// Correct - valid JSON-RPC
ws.send(JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'subscribe',
params: ['slotUpdates']
}));2. Subscribing before connection opens:
// Wrong - send before ready
const ws = new WebSocket(url);
ws.send(subscribeMsg); // Error! Not connected yet
// Correct - wait for open event
const ws = new WebSocket(url);
ws.on('open', () => {
ws.send(JSON.stringify({
method: 'subscribe',
params: ['slotUpdates']
}));
});3. Not handling subscription confirmation:
ws.on('message', (data) => {
const msg = JSON.parse(data);
// Handle subscription confirmation
if (msg.id === 1 && msg.result) {
console.log('Subscribed with ID:', msg.result);
subscriptionId = msg.result;
}
// Handle subscription updates
if (msg.method === 'notification') {
console.log('Update:', msg.params);
}
});4. Too many subscriptions:
// Each subscription counts toward connection limits
// Free: 5 subscriptions per connection
// Business: 250 subscriptions per connection
// Use filters to reduce subscription count
ws.send(JSON.stringify({
method: 'subscribe',
params: [{
channel: 'accountUpdates',
filters: {
accounts: ['ADDRESS1', 'ADDRESS2', 'ADDRESS3'] // One sub for multiple accounts
}
}]
}));Message Parsing
Symptoms:
- "Unexpected token" error
- Cannot read property of undefined
- Malformed messages
Common issues:
1. Not parsing JSON:
// Wrong - treating message as string
ws.on('message', (data) => {
console.log(data); // [Object object]
console.log(data.method); // undefined
});
// Correct - parse JSON
ws.on('message', (data) => {
const message = JSON.parse(data);
console.log(message.method); // 'notification'
});2. Not handling different message types:
ws.on('message', (data) => {
const message = JSON.parse(data);
// Subscription confirmation
if (message.id && message.result) {
console.log('Subscription confirmed:', message.result);
}
// Subscription update
else if (message.method === 'notification') {
console.log('Update:', message.params);
}
// Error
else if (message.error) {
console.error('Error:', message.error);
}
});3. Not validating message structure:
function handleMessage(data) {
try {
const message = JSON.parse(data);
// Validate structure
if (!message.jsonrpc || message.jsonrpc !== '2.0') {
console.warn('Invalid JSON-RPC message');
return;
}
// Handle different types
if (message.method === 'notification') {
handleNotification(message.params);
} else if (message.error) {
handleError(message.error);
} else if (message.result !== undefined) {
handleResult(message.result);
}
} catch (error) {
console.error('Failed to parse message:', error);
}
}Debugging Tools
Using curl to Test
Basic health check:
curl -X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getHealth"
}'Test with verbose output:
curl -v -X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getSlot"
}'Save response to file:
curl -X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlock",
"params": [123456]
}' \
-o response.jsonMeasure response time:
curl -w "\nTime: %{time_total}s\n" \
-X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getHealth"
}'Test Geyser API:
curl -H "X-API-Key: YOUR_KEY" \
"https://nexus.fortiblox.com/geyser/transactions?limit=10"Test WebSocket with wscat:
# Install wscat
npm install -g wscat
# Connect to WebSocket
wscat -c "wss://nexus.fortiblox.com/geyser/ws?api-key=YOUR_KEY"
# Send subscription
> {"jsonrpc":"2.0","id":1,"method":"subscribe","params":["slotUpdates"]}
# Receive messages
< {"jsonrpc":"2.0","result":123,"id":1}
< {"jsonrpc":"2.0","method":"notification","params":{"subscription":123,"result":{"slot":123456}}}Checking Request/Response Headers
In JavaScript:
const axios = require('axios');
async function debugRequest() {
try {
const response = await axios.post(
'https://nexus.fortiblox.com/rpc',
{
jsonrpc: '2.0',
id: 1,
method: 'getHealth'
},
{
headers: {
'X-API-Key': process.env.FORTIBLOX_API_KEY,
'Content-Type': 'application/json'
},
validateStatus: () => true // Don't throw on error status
}
);
console.log('Status:', response.status);
console.log('Headers:', response.headers);
console.log('Data:', response.data);
// Check rate limit
console.log('\nRate Limit Info:');
console.log(' Limit:', response.headers['x-ratelimit-limit']);
console.log(' Remaining:', response.headers['x-ratelimit-remaining']);
console.log(' Reset:', new Date(response.headers['x-ratelimit-reset'] * 1000));
// Check credits
console.log('\nCredit Info:');
console.log(' Used:', response.headers['x-credits-used']);
console.log(' Remaining:', response.headers['x-credits-remaining']);
// Check request ID
console.log('\nRequest ID:', response.headers['x-request-id']);
} catch (error) {
console.error('Request failed:', error.message);
}
}In Python:
import requests
import os
def debug_request():
response = requests.post(
'https://nexus.fortiblox.com/rpc',
json={
'jsonrpc': '2.0',
'id': 1,
'method': 'getHealth'
},
headers={
'X-API-Key': os.getenv('FORTIBLOX_API_KEY'),
'Content-Type': 'application/json'
}
)
print(f"Status: {response.status_code}")
print(f"Headers: {dict(response.headers)}")
print(f"Data: {response.json()}")
# Check rate limit
print("\nRate Limit Info:")
print(f" Limit: {response.headers.get('x-ratelimit-limit')}")
print(f" Remaining: {response.headers.get('x-ratelimit-remaining')}")
print(f" Reset: {response.headers.get('x-ratelimit-reset')}")
# Check credits
print("\nCredit Info:")
print(f" Used: {response.headers.get('x-credits-used')}")
print(f" Remaining: {response.headers.get('x-credits-remaining')}")
# Check request ID
print(f"\nRequest ID: {response.headers.get('x-request-id')}")Reading Request IDs
Every API request returns a unique X-Request-ID header for debugging.
Capture request ID:
try {
const response = await axios.post(endpoint, payload, {
headers: { 'X-API-Key': apiKey }
});
// Success - log request ID
const requestId = response.headers['x-request-id'];
console.log('Request ID:', requestId);
} catch (error) {
// Error - capture request ID from error response
const requestId = error.response?.headers['x-request-id'];
console.error('Request failed. Request ID:', requestId);
console.error('Error:', error.response?.data);
}Use request ID for support:
function logError(error, context) {
const requestId = error.response?.headers['x-request-id'];
const errorInfo = {
timestamp: new Date().toISOString(),
requestId: requestId,
endpoint: error.config?.url,
method: error.config?.method,
status: error.response?.status,
error: error.response?.data,
context: context
};
console.error('Error details:', JSON.stringify(errorInfo, null, 2));
// Send to error tracking service
// Sentry, Datadog, etc.
}Contacting Support
When to contact support:
- Persistent 500/503 errors
- Unexplained rate limiting
- Data inconsistencies
- Performance issues
- Billing questions
What to include:
- Request ID (from X-Request-ID header)
- API key prefix (first 10 chars:
fbx_e23728...) - Timestamp of the issue
- Request/response examples
- Your tier (Free, Developer, Business, Professional)
- Expected vs actual behavior
Example support email:
Subject: [Request ID: req_abc123] 500 Error on getTransaction
Hi FortiBlox Support,
I'm experiencing 500 errors when calling getTransaction:
Request ID: req_abc123xyz
Timestamp: 2024-01-15 14:30:00 UTC
API Key: fbx_e23728... (Developer tier)
Endpoint: POST https://nexus.fortiblox.com/rpc
Request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "getTransaction",
"params": ["5w8ZHp..."]
}
Response:
{
"error": {
"code": 500,
"message": "Internal server error"
}
}
This happens consistently for this transaction signature.
Other signatures work fine.
Can you help investigate?
Thanks,
[Your name]Support channels:
| Channel | Response Time | Best For |
|---|---|---|
| Discord | < 1 hour | Quick questions, community help |
| 24-48 hours (Free/Developer) 4 hours (Business) 1 hour (Professional) | Technical issues, bug reports | |
| Status Page | Real-time | Service outages, maintenance |
| Documentation | Instant | How-to guides, examples |
- Discord: discord.gg/fortiblox
- Email: [email protected]
- Status: status.fortiblox.com
- Twitter: @FortiBlox
Performance Issues
Slow Response Times
Symptoms:
- Requests taking > 2 seconds
- Inconsistent latency
- Timeout errors
Diagnosis:
1. Measure response time:
async function measureLatency(method, params) {
const start = Date.now();
try {
const response = await axios.post(endpoint, {
jsonrpc: '2.0',
id: 1,
method: method,
params: params
}, {
headers: { 'X-API-Key': apiKey }
});
const latency = Date.now() - start;
console.log(`${method}: ${latency}ms`);
return response.data;
} catch (error) {
const latency = Date.now() - start;
console.error(`${method} failed after ${latency}ms:`, error.message);
throw error;
}
}
// Test different endpoints
await measureLatency('getHealth', []);
await measureLatency('getSlot', []);
await measureLatency('getBalance', ['ADDRESS']);2. Check network latency:
# Ping FortiBlox servers
ping nexus.fortiblox.com
# Measure HTTP latency
curl -w "\nTime: %{time_total}s\n" https://nexus.fortiblox.com/rpc3. Compare with baseline:
// getHealth should be < 100ms
// getSlot should be < 200ms
// getBalance should be < 300ms
// getBlock should be < 500ms
const benchmarks = {
getHealth: 100,
getSlot: 200,
getBalance: 300,
getBlock: 500
};
async function benchmark(method, params) {
const latency = await measureLatency(method, params);
const baseline = benchmarks[method];
if (latency > baseline * 2) {
console.warn(`${method} is ${latency}ms (expected < ${baseline}ms)`);
}
}Solutions:
1. Use connection pooling:
const axios = require('axios');
const http = require('http');
const https = require('https');
const agent = new https.Agent({
keepAlive: true,
maxSockets: 10,
maxFreeSockets: 5,
timeout: 60000
});
const client = axios.create({
httpsAgent: agent,
headers: { 'X-API-Key': apiKey }
});
// Reuse connections
await client.post(endpoint, payload);
await client.post(endpoint, payload); // Reuses connection2. Enable compression:
const response = await axios.post(endpoint, payload, {
headers: {
'X-API-Key': apiKey,
'Accept-Encoding': 'gzip, deflate'
}
});3. Request only needed data:
// Slow - returns full transaction
const tx = await getTransaction(signature);
// Fast - returns only signature status
const status = await getSignatureStatus(signature);4. Use batch requests:
// Slow - 100 separate requests
for (let addr of addresses) {
const balance = await getBalance(addr);
}
// Fast - 1 batch request
const balances = await getMultipleAccounts(addresses);Timeout Errors
Symptoms:
- "ETIMEDOUT" error
- "Socket hang up" error
- Requests never complete
Common causes:
1. Default timeout too short:
// Wrong - 5 second timeout
const response = await axios.post(endpoint, payload, {
timeout: 5000
});
// Correct - 30 second timeout
const response = await axios.post(endpoint, payload, {
timeout: 30000
});2. Heavy RPC calls:
// These calls can take 10+ seconds:
// - getBlock (full block)
// - getProgramAccounts (large program)
// - getTokenLargestAccounts
// Use longer timeout
const response = await axios.post(endpoint, {
jsonrpc: '2.0',
id: 1,
method: 'getBlock',
params: [slot]
}, {
timeout: 60000, // 60 seconds
headers: { 'X-API-Key': apiKey }
});3. Network issues:
// Test connectivity
const dns = require('dns').promises;
async function checkConnectivity() {
try {
// Resolve DNS
const addresses = await dns.resolve('nexus.fortiblox.com');
console.log('DNS resolved:', addresses);
// Test connection
const response = await axios.get('https://nexus.fortiblox.com', {
timeout: 5000
});
console.log('Connection OK');
} catch (error) {
console.error('Connectivity issue:', error.message);
}
}Solutions:
1. Implement timeouts with retry:
async function requestWithTimeout(payload, timeout = 30000) {
const maxRetries = 3;
for (let i = 0; i < maxRetries; i++) {
try {
const response = await axios.post(endpoint, payload, {
timeout: timeout,
headers: { 'X-API-Key': apiKey }
});
return response.data;
} catch (error) {
if (error.code === 'ETIMEDOUT' && i < maxRetries - 1) {
console.log(`Timeout. Retrying... (${i + 1}/${maxRetries})`);
await sleep(1000 * (i + 1));
} else {
throw error;
}
}
}
}2. Use Promise.race for manual timeout:
function timeoutPromise(ms, promise) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
)
]);
}
// Use it
try {
const result = await timeoutPromise(5000, makeRequest());
} catch (error) {
console.error('Request timed out after 5 seconds');
}Optimization Tips
1. Use WebSocket instead of polling:
// Bad - polling every second (60 req/min)
setInterval(async () => {
const slot = await getSlot();
updateUI(slot);
}, 1000);
// Good - WebSocket subscription (0 credits!)
const ws = new WebSocket('wss://nexus.fortiblox.com/geyser/ws?api-key=KEY');
ws.send(JSON.stringify({
method: 'subscribe',
params: ['slotUpdates']
}));
ws.on('message', (data) => {
const msg = JSON.parse(data);
if (msg.method === 'notification') {
updateUI(msg.params.result.slot);
}
});2. Cache frequently accessed data:
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 10 }); // 10 second cache
async function getCachedSlot() {
let slot = cache.get('current-slot');
if (!slot) {
slot = await getSlot();
cache.set('current-slot', slot);
}
return slot;
}3. Batch related requests:
// Bad - 3 separate requests
const balance = await getBalance(addr);
const tokenBalance = await getTokenAccountBalance(tokenAddr);
const nfts = await getNFTsByOwner(addr);
// Good - parallel requests
const [balance, tokenBalance, nfts] = await Promise.all([
getBalance(addr),
getTokenAccountBalance(tokenAddr),
getNFTsByOwner(addr)
]);4. Use commitment levels wisely:
// Processed - fastest, least secure
const slot = await getSlot({ commitment: 'processed' });
// Confirmed - balanced (recommended)
const balance = await getBalance(addr, { commitment: 'confirmed' });
// Finalized - slowest, most secure
const tx = await getTransaction(sig, { commitment: 'finalized' });5. Monitor and optimize:
// Track slow endpoints
const metrics = {
totalRequests: 0,
totalLatency: 0,
slowRequests: []
};
async function monitoredRequest(method, params) {
const start = Date.now();
metrics.totalRequests++;
try {
const result = await makeRequest(method, params);
const latency = Date.now() - start;
metrics.totalLatency += latency;
// Log slow requests
if (latency > 1000) {
metrics.slowRequests.push({ method, latency, timestamp: new Date() });
}
return result;
} catch (error) {
throw error;
}
}
// View metrics
console.log('Average latency:', metrics.totalLatency / metrics.totalRequests);
console.log('Slow requests:', metrics.slowRequests);Next Steps
Security Best Practices
Learn how to secure your API keys and protect your application
Rate Limits & Billing
Understand rate limits, credits, and upgrade options
WebSocket Best Practices
Optimize your WebSocket implementation
Contact Support
Still having issues? Our team is here to help
Pro Tip: Join our Discord community for real-time help from the FortiBlox team and other developers!