Handle Rate Limits

What to do when you hit rate limits, how to read the rate limit headers, and how to avoid hitting them.

The Jobven API enforces two kinds of limits. Both return a 429 status when exceeded, but you handle them differently:

  • Burst limit — a per-second cap on how fast you can send requests. Retry after a short pause.
  • Monthly quota — the number of jobs your plan delivers each billing period. Every job in a response counts one; empty responses are free. Retrying won't help until the period resets.

The exact numbers for each plan are listed under Authentication → Rate Limiting.

Response Headers

Every response includes headers describing your monthly job quota, in jobs. Remaining already accounts for the jobs in the response it arrives with:

X-RateLimit-Limit: 50000
X-RateLimit-Remaining: 48750
X-RateLimit-Reset: 1735257660
HeaderDescription
X-RateLimit-LimitCalls included this billing period. This is the limit actually enforced on your account, which is your plan's allowance unless a different one was agreed with us.
X-RateLimit-RemainingCalls left this billing period
X-RateLimit-ResetUnix timestamp (seconds) when your quota resets
X-Request-Time-MsHow long the request took to process
A 429 also carries a Retry-After header, in seconds. On the burst limit it matches the body's retryAfter. On the monthly quota it is a suggested wait of one minute to one hour before checking again; resetAt in the body and X-RateLimit-Reset give the exact reset time.

Read the header, not your plan's advertised allowance

X-RateLimit-Limit is the ceiling actually enforced on your account. If we agreed a different limit with you, your plan's published number is not the one you are held to, and the header is where that difference shows up. Every API response carries it, so there is no separate call to make.

If you track usage programmatically, X-RateLimit-Limit and X-RateLimit-Remaining are the only two numbers you need. Credits are no longer part of how a request is accepted or rejected.

Telling the Two Limits Apart

Both limits return 429, so check the error field in the response body to decide how to react.

Burst limit (slow down and retry):

{
  "statusCode": 429,
  "message": "Rate limit exceeded",
  "error": "Too Many Requests",
  "retryAfter": 1
}

Monthly quota (stop and upgrade or wait for reset):

{
  "statusCode": 429,
  "message": "Monthly job limit exceeded",
  "error": "Quota Exceeded",
  "resetAt": 1735257660,
  "upgradeUrl": "https://jobven.com/dashboard/billing"
}

upgradeUrl is the billing page, where you can move to a plan with a larger allowance. An account on agreed terms has no upgradeUrl; contact us to change its limit.

Handling 429 Responses

Retry on the burst limit, but stop on a quota error — retrying won't return data until your billing period resets.

JavaScript

async function fetchWithRetry(url, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, {
      headers: { 'X-API-Key': process.env.JOBVEN_API_KEY }
    });

    if (response.status === 429) {
      const body = await response.json();

      // Monthly quota - retrying won't help until reset
      if (body.error === 'Quota Exceeded') {
        throw new Error('Monthly quota exhausted. Upgrade your plan or wait for reset.');
      }

      // Burst limit - wait and retry
      const wait = body.retryAfter ?? 1;
      console.log(`Rate limited. Waiting ${wait}s...`);
      await new Promise(r => setTimeout(r, wait * 1000));
      continue;
    }

    if (!response.ok) {
      throw new Error(`API error: ${response.status}`);
    }

    return response;
  }

  throw new Error('Max retries exceeded');
}

Python

import os
import time
import requests

def fetch_with_retry(url, params=None, max_retries=3):
    headers = {'X-API-Key': os.environ['JOBVEN_API_KEY']}

    for attempt in range(max_retries):
        response = requests.get(url, headers=headers, params=params)

        if response.status_code == 429:
            body = response.json()

            # Monthly quota - retrying won't help until reset
            if body.get('error') == 'Quota Exceeded':
                raise Exception('Monthly quota exhausted. Upgrade your plan or wait for reset.')

            # Burst limit - wait and retry
            wait = body.get('retryAfter', 1)
            print(f"Rate limited. Waiting {wait}s...")
            time.sleep(wait)
            continue

        response.raise_for_status()
        return response

    raise Exception('Max retries exceeded')

Avoiding Rate Limits

  • Use the maximum limit - Omitting limit returns your plan's maximum per request: 25 on Free, 50 on Starter, 100 on Growth and above. You pay per job delivered, not per request, so a larger page costs the same as the same jobs fetched in smaller pages; it just takes fewer round trips against the per-second limit.
  • Use incremental sync - Fetch only the jobs listed since your last run with listedAfter. A poll that returns nothing costs nothing, so you can check as often as the per-second limit allows. See Incremental Sync.
  • Cache responses - Don't fetch the same data repeatedly. Cache results for as long as makes sense for your use case.
  • Watch your remaining quota - Monitor X-RateLimit-Remaining and slow down before it reaches zero.

Next Steps

Fetch All Jobs

Paginate through large result sets.

Incremental Sync

Spend your allowance only on jobs you have not seen.