API Integration Troubleshooting

This document provides troubleshooting procedures for common issues when integrating with Noderr APIs. This guide covers issues relevant to external developers using REST, GraphQL, and WebSocket APIs.


Table of Contents


Authentication Issues

Problem: 401 Unauthorized

Symptoms:

  • API returns 401 status code
  • "Unauthorized" or "Invalid token" error message
  • Requests fail even with a valid API key

Root Causes:

  • API key is missing or invalid
  • API key has expired
  • API key doesn't have required permissions
  • Authorization header format is incorrect
  • Token is revoked or disabled

Diagnostic Steps:

  1. Verify API Key is Present:

    # Check if Authorization header is set
    curl -v https://api.noderr.xyz/v1/vaults 2>&1 | grep -i "authorization"# Should show: Authorization: Bearer YOUR_API_KEY
  2. Verify Token Format:

    # Check Authorization header format
    curl -H "Authorization: Bearer YOUR_API_KEY" \
    https://api.noderr.xyz/v1/vaults \
    -w "\nStatus: %{http_code}\n"# Should return 200, not 401
  3. Check Token Validity:

    # Test with a known valid endpoint
    curl -H "Authorization: Bearer YOUR_API_KEY" \
    https://api.noderr.xyz/v1/status \
    -w "\nStatus: %{http_code}\n"

Resolution Procedures:

  1. Generate New API Key:

    • Log in to the Noderr dashboard
    • Navigate to the API Keys section
    • Click "Generate New Key"
    • Copy the key immediately (it won't be shown again)
    • Update your application with the new key
  2. Verify Header Format:

    # Correct format
    curl -H "Authorization: Bearer YOUR_API_KEY" https://api.noderr.xyz/v1/vaults
    # Incorrect formats (will fail)
    curl -H "Authorization: YOUR_API_KEY" https://api.noderr.xyz/v1/vaults
    curl -H "X-API-KEY: YOUR_API_KEY" https://api.noderr.xyz/v1/vaults
    
  3. Check Permissions:

    # Get token info to verify permissions
    curl -H "Authorization: Bearer YOUR_API_KEY" \
    https://api.noderr.xyz/v1/auth/token-info | jq '.permissions'# Should include required scopes like "vaults:read", "vaults:write"
  4. Implement Token Refresh:

    asyncfunctionmakeAuthenticatedRequest(url) {
    try {
    const response = await fetch(url, {
    headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json'
    }
    });
    if (response.status === 401) {
    // Token expired, refresh itawait refreshToken();
    return makeAuthenticatedRequest(url); // Retry
    }
    return response.json();
    } catch (error) {
    console.error('Request failed:', error);
    }
    }
    

Rate Limiting

Problem: 429 Too Many Requests

Symptoms:

  • API returns 429 status code
  • "Rate limit exceeded" error message
  • Requests fail after a burst of calls
  • Retry-After header present in response

Root Causes:

  • Exceeded API rate limit
  • Not implementing exponential backoff
  • Sending requests too quickly
  • Not respecting Retry-After header
  • Concurrent requests from multiple clients

Diagnostic Steps:

  1. Check Rate Limit Headers:

    # Make a request and check headers
    curl -i https://api.noderr.xyz/v1/vaults \
    -H "Authorization: Bearer YOUR_API_KEY" | grep -i "rate-limit\|retry-after"# Should show:# X-RateLimit-Limit: 1000# X-RateLimit-Remaining: 999# X-RateLimit-Reset: 1705334400
  2. Monitor Request Rate:

    # Count requests per secondfor i in {1..10}; do
    curl -s https://api.noderr.xyz/v1/vaults \
    -H "Authorization: Bearer YOUR_API_KEY" > /dev/null
    echo"Request $i completed"done
  3. Check Current Rate Limit Status:

    # Get rate limit info
    curl -H "Authorization: Bearer YOUR_API_KEY" \
    https://api.noderr.xyz/v1/rate-limit-status | jq '.'

Resolution Procedures:

  1. Implement Exponential Backoff:

    asyncfunctionmakeRequestWithBackoff(url, maxRetries = 3) {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
    const response = await fetch(url, {
    headers: { 'Authorization': `Bearer ${apiKey}` }
    });
    if (response.status === 429) {
    // Get retry-after headerconst retryAfter = response.headers.get('Retry-After') || Math.pow(2, attempt);
    console.log(`Rate limited. Waiting ${retryAfter} seconds...`);
    await sleep(retryAfter * 1000);
    continue;
    }
    return response.json();
    } catch (error) {
    if (attempt === maxRetries - 1) throw error;
    }
    }
    }
    functionsleep(ms) {
    returnnewPromise(resolve =>setTimeout(resolve, ms));
    }
    
  2. Respect Rate Limit Headers:

    asyncfunctionmakeRateLimitedRequest(url) {
    const response = await fetch(url, {
    headers: { 'Authorization': `Bearer ${apiKey}` }
    });
    // Get rate limit info from headersconst remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
    const reset = parseInt(response.headers.get('X-RateLimit-Reset'));
    if (remaining < 10) {
    // Getting close to the limit, wait before the next requestconst waitTime = (reset - Math.floor(Date.now() / 1000)) * 1000;
    console.log(` ${remaining} requests remaining. Waiting ${waitTime}ms...`);
    await sleep(waitTime);
    }
    return response.json();
    }
    
  3. Batch Requests:

    // Instead of multiple individual requestsfor (const vaultId of vaultIds) {
    await fetch(`/vaults/${vaultId}`); // Bad: hits rate limit
    }
    // Use batch endpointconst response = await fetch('/vaults/batch', {
    method: 'POST',
    body: JSON.stringify({ vaultIds })
    });
    
  4. Upgrade API Plan:

    • Check current rate limits: https://api.noderr.xyz/v1/rate-limit-status
    • View available plans: https://noderr.xyz/pricing
    • Upgrade to a higher tier for increased limits

Connection Issues

Problem: Connection Timeout

Symptoms:

  • Request hangs for 30+ seconds
  • "Connection timeout" error
  • No response from the server
  • Network appears to be working

Root Causes:

  • Server is overloaded
  • Network connectivity issues
  • Firewall blocking the connection
  • DNS resolution failing
  • Incorrect endpoint URL

Diagnostic Steps:

  1. Test DNS Resolution:

    # Verify DNS is working
    nslookup api.noderr.xyz
    dig api.noderr.xyz
    # Should return a valid IP address
  2. Test Basic Connectivity:

    # Test connection to the API server
    curl -v --connect-timeout 5 https://api.noderr.xyz/v1/status
    # Should connect within 5 seconds
  3. Check Endpoint Availability:

    # Test multiple endpointsfor endpoint in status vaults users; doecho"Testing /v1/$endpoint..."
    curl -s --connect-timeout 5 \
    https://api.noderr.xyz/v1/$endpoint \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -w "Status: %{http_code}\n"done
  4. Monitor Network Latency:

    # Measure latency to the API
    ping api.noderr.xyz
    # Measure HTTP latency
    curl -w "Time: %{time_total}s\n" -o /dev/null -s https://api.noderr.xyz/v1/status
    

Resolution Procedures:

  1. Increase Timeout:

    // JavaScript - increase timeoutconst controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 15000); // 15 secondstry {
    const response = await fetch(url, {
    signal: controller.signal,
    headers: { 'Authorization': `Bearer ${apiKey}` }
    });
    return response.json();
    } finally {
    clearTimeout(timeoutId);
    }
    
  2. Implement Retry Logic:

    asyncfunctionmakeRequestWithRetry(url, maxRetries = 3) {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 10000);
    const response = await fetch(url, {
    signal: controller.signal,
    headers: { 'Authorization': `Bearer ${apiKey}` }
    });
    clearTimeout(timeout);
    return response.json();
    } catch (error) {
    if (error.name === 'AbortError') {
    console.log(`Attempt ${attempt + 1} timed out. Retrying...`);
    if (attempt < maxRetries - 1) {
    await sleep(Math.pow(2, attempt) * 1000);
    continue;
    }
    }
    throw error;
    }
    }
    }
    
  3. Check Network Configuration:

    # Verify firewall allows HTTPS
    sudo iptables -L | grep 443
    # Test with different DNS
    curl -H "Authorization: Bearer YOUR_API_KEY" \
    --dns-servers 8.8.8.8 \
    https://api.noderr.xyz/v1/status
    
  4. Use Connection Pooling:

    // Reuse connections instead of creating new onesconst https = require('https');
    const agent = new https.Agent({ keepAlive: true });
    const response = await fetch(url, {
    agent: agent,
    headers: { 'Authorization': `Bearer ${apiKey}` }
    });
    

Request/Response Issues

Problem: 400 Bad Request

Symptoms:

  • API returns 400 status code
  • "Bad Request" or "Invalid Request" error
  • Request format appears correct
  • Same request works in Postman

Root Causes:

  • Invalid JSON format
  • Missing required fields
  • Invalid field values
  • Incorrect content type header
  • Request body encoding issues

Diagnostic Steps:

  1. Validate JSON Format:

    # Check if JSON is validecho'{"amount": 1000, "receiver": "0x..."}' | jq '.'# If invalid, jq will show an error
  2. Test Request with curl:

    # Make a request with verbose output
    curl -v -X POST https://api.noderr.xyz/v1/vaults/vault-1/deposit \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"amount": 1000, "receiver": "0x1234567890123456789012345678901234567890"}'# Check the response body for error details
  3. Validate Request Format:

    # Check Content-Type header
    curl -i -X POST https://api.noderr.xyz/v1/vaults/vault-1/deposit \
    -H "Content-Type: application/json" | grep -i "content-type"# Should match: application/json

Resolution Procedures:

  1. Validate Request Format:

    // Good: Valid requestconst request = {
    amount: 1000,
    receiver: '0x1234567890123456789012345678901234567890'
    };
    const response = await fetch('https://api.noderr.xyz/v1/vaults/vault-1/deposit', {
    method: 'POST',
    headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json'
    },
    body: JSON.stringify(request)
    });
    
  2. Check Required Fields:

    # Get endpoint documentation
    curl https://api.noderr.xyz/v1/docs/endpoints/deposit | jq '.required_fields'# Ensure all required fields are present
  3. Validate Field Values:

    // Validate before sendingfunctionvalidateDepositRequest(request) {
    if (!request.amount || request.amount <= 0) {
    thrownewError('Amount must be greater than 0');
    }
    if (!request.receiver || !request.receiver.match(/^0x[0-9a-fA-F]{40}$/)) {
    thrownewError('Invalid Ethereum address');
    }
    returntrue;
    }
    
  4. Add Error Handling:

    asyncfunctionmakeRequest(url, options) {
    const response = await fetch(url, options);
    if (!response.ok) {
    const error = await response.json();
    console.error('API Error:', error);
    console.error('Request was:', options.body);
    thrownewError(error.message);
    }
    return response.json();
    }
    

WebSocket Issues

Problem: WebSocket Connection Drops

Symptoms:

  • WebSocket connection closes unexpectedly
  • Subscription stops receiving updates
  • "Connection reset" errors
  • No reconnection attempt

Root Causes:

  • Network interruption
  • Server restart or deployment
  • Idle timeout (no heartbeat)
  • Client-side error
  • Firewall closing idle connections

Diagnostic Steps:

  1. Monitor Connection Status:

    ws.onopen = () =>console.log('Connected');
    ws.onclose = (event) => {
    console.log('Closed:', event.code, event.reason);
    };
    ws.onerror = (error) =>console.error('Error:', error);
    
  2. Check Server Status:

    # Test WebSocket endpoint
    wscat -c wss://api.noderr.xyz/ws
    # Should connect successfully
  3. Monitor Connection Activity:

    let lastMessageTime = Date.now();
    ws.onmessage = (event) => {
    lastMessageTime = Date.now();
    console.log('Message received:', event.data);
    };
    // Check if the connection is stalesetInterval(() => {
    const timeSinceLastMessage = Date.now() - lastMessageTime;
    if (timeSinceLastMessage > 60000) {
    console.warn('No message for 60 seconds');
    }
    }, 10000);
    

Resolution Procedures:

  1. Implement Automatic Reconnection:

    classReconnectingWebSocket{
    constructor(url, apiKey) {
    this.url = url;
    this.apiKey = apiKey;
    this.reconnectInterval = 1000;
    this.maxReconnectInterval = 30000;
    this.connect();
    }
    connect() {
    this.ws = new WebSocket(this.url);
    this.ws.onopen = () => {
    console.log('Connected');
    this.reconnectInterval = 1000;
    this.authenticate();
    };
    this.ws.onclose = () => {
    console.log('Disconnected, reconnecting...');
    setTimeout(() =>this.connect(), this.reconnectInterval);
    this.reconnectInterval = Math.min(
    this.reconnectInterval * 2,
    this.maxReconnectInterval
    );
    };
    this.ws.onerror = (error) => {
    console.error('WebSocket error:', error);
    };
    }
    authenticate() {
    this.ws.send(JSON.stringify({ type: 'auth', token: this.apiKey }));
    }
    subscribe(channel, params) {
    this.ws.send(JSON.stringify({ type: 'subscribe', channel, ...params }));
    }
    }
    
  2. Add Heartbeat:

    // Send heartbeat every 30 secondssetInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({ type: 'ping' }));
    }
    }, 30000);
    ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    if (data.type === 'pong') {
    console.log('Heartbeat received');
    }
    };
    
  3. Handle Reconnection State:

    classWebSocketManager{
    constructor(url, apiKey) {
    this.url = url;
    this.apiKey = apiKey;
    this.subscriptions = [];
    this.connect();
    }
    connect() {
    this.ws = new WebSocket(this.url);
    this.ws.onopen = () => {
    this.authenticate();
    // Resubscribe to channelsthis.subscriptions.forEach(sub => {
    this.subscribe(sub.channel, sub.params);
    });
    };
    this.ws.onclose = () => {
    setTimeout(() =>this.connect(), 5000);
    };
    }
    subscribe(channel, params) {
    // Store subscription for reconnectionthis.subscriptions.push({ channel, params });
    if (this.ws.readyState === WebSocket.OPEN) {
    this.ws.send(JSON.stringify({ type: 'subscribe', channel, ...params }));
    }
    }
    }
    

GraphQL Issues

Problem: Malformed Query

Symptoms:

  • API returns 400 status code
  • "Malformed query" or "Syntax error" in response
  • Query works in GraphQL playground but not in code

Root Causes:

  • Syntax error in the GraphQL query string
  • Incorrectly formatted variables
  • Missing or extra brackets/parentheses

Diagnostic Steps:

  1. Validate Query in Playground:

    • Copy the exact query string from your code into the GraphQL playground (https://api.noderr.xyz/v1/graphql)
    • If it fails, correct the syntax in the playground first
  2. Check Query String in Code:

    // Log the query string before sendingconst query = `
    query GetVault($id: ID!) {
    vault(id: $id) {
    id
    balance
    }
    }
    `;
    console.log('Query:', query);
    

Resolution Procedures:

  1. Use a GraphQL Client Library:

    • Libraries like Apollo Client or graphql-request handle query formatting for you
    import { GraphQLClient, gql } from'graphql-request';
    const client = new GraphQLClient('https://api.noderr.xyz/v1/graphql', {
    headers: { Authorization: `Bearer ${apiKey}` }
    });
    const query = gql`
    query GetVault($id: ID!) {
    vault(id: $id) {
    id
    balance
    }
    }
    `;
    const variables = { id: 'vault-1' };
    const data = await client.request(query, variables);
    
  2. Properly Format Variables:

    // Variables should be a separate object, not embedded in the query stringconst response = await fetch('https://api.noderr.xyz/v1/graphql', {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${apiKey}`
    },
    body: JSON.stringify({
    query: query,
    variables: variables
    })
    });
    

results matching ""

    No results matching ""