FortiBlox LogoFortiBlox Docs

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:

  1. Verify your API key format

    • Production keys start with fbx_
    • Test keys start with fbx_test_
    • Keys should be 64-70 characters long
  2. 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"
  3. Ensure key is active

    • Log into Nexus Dashboard
    • Navigate to API Keys
    • Verify status shows "Active"
  4. Check key hasn't expired

    • Keys can have expiration dates
    • Generate a new key if expired
  5. 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

View pricing tiers →

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:

TierRPC/secGeyser/secEnhanced/secWebSocket Connections
Free10525
Developer5025105
Business20010050250
Professional500200100250

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:

  1. Retry the request

    • Server errors are usually temporary
    • Wait 1-2 seconds and retry
    • Use exponential backoff
  2. Check the status page

  3. Contact support

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:

  1. 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);
    }
  2. Check for scheduled maintenance

    • Visit status.fortiblox.com
    • Subscribe to status updates
    • Maintenance is usually announced 24-48 hours in advance
  3. 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:

  1. Log into Nexus Dashboard
  2. Navigate to API Keys
  3. Click on your key
  4. View "Enabled Services" section

Required scopes:

ServiceRequired Scope
RPC Nodesrpc:read
RPC Writerpc:write
Geyser APIgeyser:query
WebSocketgeyser:stream
gRPC Streaminggrpc:stream
Enhanced APIenhanced:read

Solution:

  1. Edit your API key
  2. Enable the required service
  3. Save changes
  4. Wait 1-2 minutes for changes to propagate

Tier Restrictions

Symptoms:

  • 403 Forbidden
  • "Requires [tier] or higher" error

Feature availability by tier:

FeatureFreeDeveloperBusinessProfessional
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 month

Monitor 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:

OperationCreditsExample
Simple RPC1getHealth, getSlot, getBalance
Heavy RPC3-5getBlock, getProgramAccounts, getTransaction
Geyser query1List transactions, blocks
Complex Geyser5-10Filtered queries, joins
Enhanced API5-10Parse transaction, NFT metadata
WebSocket msg0Real-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:

  1. Go to Dashboard
  2. Navigate to Usage tab
  3. 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

View detailed pricing →


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';  // Devnet

Invalid 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 secure
  • confirmed - 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 (&lt; 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, compliance

Example:

// 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 retry

2. 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 blocking

3. 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-keys

Learn 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.json

Measure 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:

  1. Request ID (from X-Request-ID header)
  2. API key prefix (first 10 chars: fbx_e23728...)
  3. Timestamp of the issue
  4. Request/response examples
  5. Your tier (Free, Developer, Business, Professional)
  6. 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:

ChannelResponse TimeBest For
Discord< 1 hourQuick questions, community help
Email24-48 hours (Free/Developer)
4 hours (Business)
1 hour (Professional)
Technical issues, bug reports
Status PageReal-timeService outages, maintenance
DocumentationInstantHow-to guides, examples

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/rpc

3. Compare with baseline:

// getHealth should be &lt; 100ms
// getSlot should be &lt; 200ms
// getBalance should be &lt; 300ms
// getBlock should be &lt; 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 connection

2. 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


Pro Tip: Join our Discord community for real-time help from the FortiBlox team and other developers!