NodukanNodukan/ Developers
Introduction

Rate Limits

Understand Nodukan API rate limits — request windows, plan tiers, response headers, 429 handling, and exponential backoff strategies.

Rate Limits

The Nodukan API enforces rate limits on a per-key, 60-second sliding window to ensure fair usage and platform stability. When you exceed the limit, you receive a 429 Too Many Requests response with headers telling you exactly when to retry.

Limits by plan#

PlanRequests / 60 sBurst (10 s window)
Free6015
Pro30060
Scale1 000200
CustomNegotiatedNegotiated
Note:

Limits are per API key, not per account. If you generate multiple keys, each key has its own independent limit bucket.

Rate limit headers#

Every API response includes these headers so you can track your current usage:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp (seconds) when the window resets
Retry-AfterSeconds to wait before retrying (only on 429 responses)

Example response headers on a normal (non-throttled) request:

HTTP/1.1 200 OK
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 247
X-RateLimit-Reset: 1715000460
Content-Type: application/json

429 response#

When you're throttled, the full response looks like this:

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1715000460
Retry-After: 18
Content-Type: application/json
{
  "error": {
    "type": "rate_limit_error",
    "code": "too_many_requests",
    "message": "You have exceeded your rate limit of 300 requests per 60 seconds. Retry after 18 seconds.",
    "retryAfter": 18,
    "resetAt": "2026-05-06T10:01:00Z"
  },
  "requestId": "req_01j9x..."
}

Retry strategy#

The recommended approach is exponential backoff with jitter. Read the Retry-After header and wait at least that many seconds. If Retry-After is absent, fall back to an exponential schedule.

JavaScript / Node.js#

async function fetchWithRetry(url, options, maxRetries = 4) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const res = await fetch(url, options);

    if (res.status !== 429) return res;

    if (attempt === maxRetries) {
      throw new Error(`Rate limit exceeded after ${maxRetries} retries`);
    }

    const retryAfter = Number(res.headers.get('Retry-After') ?? 0);
    const jitter = Math.random() * 1000; // 0–1000 ms
    const delay = retryAfter * 1000 || Math.min(1000 * 2 ** attempt, 30_000);

    console.warn(`Rate limited. Waiting ${Math.round((delay + jitter) / 1000)}s…`);
    await new Promise((resolve) => setTimeout(resolve, delay + jitter));
  }
}

// Usage
const res = await fetchWithRetry('https://api.nodukan.com/v1/lead-magnets', {
  headers: { Authorization: `Bearer ${process.env.NODUKAN_API_KEY}` },
});

Python#

import os, time, random, requests

def fetch_with_retry(url, headers, max_retries=4):
    for attempt in range(max_retries + 1):
        resp = requests.get(url, headers=headers)

        if resp.status_code != 429:
            resp.raise_for_status()
            return resp.json()

        if attempt == max_retries:
            raise RuntimeError(f"Rate limit exceeded after {max_retries} retries")

        retry_after = float(resp.headers.get("Retry-After", 0))
        delay = retry_after or min(2 ** attempt, 30)
        jitter = random.uniform(0, 1)
        print(f"Rate limited. Waiting {delay + jitter:.1f}s…")
        time.sleep(delay + jitter)

data = fetch_with_retry(
    "https://api.nodukan.com/v1/lead-magnets",
    headers={"Authorization": f"Bearer {os.environ['NODUKAN_API_KEY']}"},
)

PHP#

<?php

function fetchWithRetry(string $url, string $apiKey, int $maxRetries = 4): array {
    for ($attempt = 0; $attempt <= $maxRetries; $attempt++) {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => ["Authorization: Bearer $apiKey"],
        ]);
        $body = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($status !== 429) {
            return json_decode($body, true);
        }

        if ($attempt === $maxRetries) {
            throw new RuntimeException("Rate limit exceeded after $maxRetries retries");
        }

        // Parse Retry-After from headers (simplified — use CURLOPT_HEADERFUNCTION in prod)
        $delay = min(pow(2, $attempt), 30) + (rand(0, 1000) / 1000.0);
        echo "Rate limited. Waiting {$delay}s…\n";
        usleep((int)($delay * 1_000_000));
    }
}

Best practices#

  • Pre-emptively throttle — if you're iterating over a large result set, add a small delay between requests rather than burning through your window.
  • Batch where possible — use the limit parameter (up to 100) to reduce the number of requests needed to paginate through data.
  • Cache responses — lead magnet and account data rarely changes. Cache aggressively and refresh on a schedule rather than re-fetching on every operation.
  • Use webhooks — instead of polling for new leads or chat events, subscribe to webhooks to receive push notifications only when something changes.
Note:

If you consistently hit rate limits on the Pro plan, contact us about Scale or Custom plans with higher limits.

Was this helpful?
Edit on GitHub

Last updated