> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nouvel.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate Limits

> Understanding API rate limiting and best practices

## Overview

The Nouvel API enforces rate limits to ensure fair usage and system stability. All API keys are subject to a **60 requests per minute** limit, regardless of your plan tier.

<Note>
  Rate limits are enforced at the infrastructure level, ensuring consistent and reliable limiting across all API endpoints.
</Note>

## Rate Limit Details

<ParamField path="Limit" type="number" default="60">
  Maximum number of requests allowed per minute per API key
</ParamField>

<ParamField path="Window" type="string" default="1 minute">
  Time window for rate limit calculations (sliding window)
</ParamField>

<ParamField path="Scope" type="string" default="per-key">
  Rate limits are applied per API key, not per user or organization
</ParamField>

## Rate Limit Headers

Every API response includes standard rate limit headers to help you track your usage:

| Header                  | Description                                        | Example      |
| ----------------------- | -------------------------------------------------- | ------------ |
| `X-RateLimit-Limit`     | Maximum requests allowed in the current window     | `60`         |
| `X-RateLimit-Remaining` | Number of requests remaining in the current window | `42`         |
| `X-RateLimit-Reset`     | Unix timestamp when the rate limit window resets   | `1678901234` |

<CodeGroup>
  ```bash Example Response Headers theme={null}
  HTTP/1.1 200 OK
  X-RateLimit-Limit: 60
  X-RateLimit-Remaining: 42
  X-RateLimit-Reset: 1678901234
  Content-Type: application/json
  ```
</CodeGroup>

## When You Exceed the Limit

When you exceed the rate limit, the API returns a **429 Too Many Requests** status code:

<CodeGroup>
  ```json 429 Response theme={null}
  {
    "error": "Rate limit exceeded",
    "reason": "You have made too many requests. Please wait before trying again.",
    "retryAfter": 23
  }
  ```
</CodeGroup>

<Warning>
  When you receive a 429 response, your request is **not processed**. You must retry the request after the rate limit window resets.
</Warning>

The response includes:

* `error`: Error message indicating rate limit exceeded
* `reason`: Human-readable explanation
* `retryAfter`: Seconds until you can retry (optional)

Additionally, a `Retry-After` header indicates how many seconds to wait before retrying.

## Endpoint Resource Costs

While all endpoints share the same 60 requests/minute limit, they consume different amounts of system resources:

<Card title="Video Generation (POST /api/v1/generate)" icon="video">
  **High resource cost** - Triggers AI models, video rendering, and long-running background jobs. Each generation uses significant compute resources.
</Card>

<Card title="Status Polling (GET /api/v1/jobs/:id)" icon="clock">
  **Low resource cost** - Simple database lookup. You can poll frequently within your rate limit.
</Card>

<Card title="Project Listing (GET /api/v1/projects)" icon="list">
  **Low resource cost** - Database query with pagination. Efficient for monitoring multiple jobs.
</Card>

<Tip>
  Even though video generation is resource-intensive, the rate limit is the same for all endpoints. Plan your API usage accordingly to balance generation requests with status checks.
</Tip>

## Best Practices

### 1. Implement Exponential Backoff

When you receive a 429 error, implement exponential backoff to avoid repeatedly hitting the rate limit:

<CodeGroup>
  ```typescript TypeScript theme={null}
  async function generateWithBackoff(url: string, maxRetries = 3) {
    let attempt = 0;

    while (attempt < maxRetries) {
      try {
        const response = await fetch('https://app.nouvel.ai/api/v1/generate', {
          method: 'POST',
          headers: {
            'Authorization': 'Bearer nvl_...',
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({ urls: [url] })
        });

        if (response.status === 429) {
          const backoffMs = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s...
          console.log(`Rate limited. Retrying in ${backoffMs}ms...`);
          await new Promise(resolve => setTimeout(resolve, backoffMs));
          attempt++;
          continue;
        }

        return await response.json();
      } catch (error) {
        throw error;
      }
    }

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

  ```python Python theme={null}
  import time
  import requests

  def generate_with_backoff(url, max_retries=3):
      attempt = 0

      while attempt < max_retries:
          response = requests.post(
              'https://app.nouvel.ai/api/v1/generate',
              headers={
                  'Authorization': 'Bearer nvl_...',
                  'Content-Type': 'application/json'
              },
              json={'urls': [url]}
          )

          if response.status_code == 429:
              backoff_s = 2 ** attempt  # 1s, 2s, 4s...
              print(f'Rate limited. Retrying in {backoff_s}s...')
              time.sleep(backoff_s)
              attempt += 1
              continue

          return response.json()

      raise Exception('Max retries exceeded')
  ```
</CodeGroup>

### 2. Cache Responses

Avoid unnecessary API calls by caching responses that don't change frequently:

* Cache completed project data (projects in `completed` or `failed` status won't change)
* Store project metadata locally after the initial fetch
* Use ETags or Last-Modified headers when available (future enhancement)

### 3. Batch Operations

When checking status for multiple jobs, use the list endpoint instead of individual status checks:

<CodeGroup>
  ```typescript Good: List endpoint theme={null}
  // Single request to check multiple jobs
  const projects = await fetch('https://app.nouvel.ai/api/v1/projects?limit=50', {
    headers: { 'Authorization': 'Bearer nvl_...' }
  });
  ```

  ```typescript Bad: Individual checks theme={null}
  // 10 requests for 10 jobs
  for (const jobId of jobIds) {
    await fetch(`https://app.nouvel.ai/api/v1/jobs/${jobId}`, {
      headers: { 'Authorization': 'Bearer nvl_...' }
    });
  }
  ```
</CodeGroup>

### 4. Smart Polling Intervals

Adjust your polling frequency based on the project status:

* **Generating status**: Poll every 15-30 seconds
* **Post-processing status**: Poll every 10-15 seconds
* **Completed/failed status**: Stop polling (cache the result)

<CodeGroup>
  ```typescript Adaptive Polling theme={null}
  async function pollUntilComplete(jobId: string) {
    let status = 'generating';

    while (status !== 'completed' && status !== 'failed') {
      const job = await fetch(`https://app.nouvel.ai/api/v1/jobs/${jobId}`, {
        headers: { 'Authorization': 'Bearer nvl_...' }
      }).then(r => r.json());

      status = job.status;

      // Adaptive interval based on status
      const interval = status === 'generating' ? 30000 : 15000;
      await new Promise(resolve => setTimeout(resolve, interval));
    }

    return job;
  }
  ```
</CodeGroup>

### 5. Monitor Rate Limit Headers

Track your rate limit usage to avoid hitting the limit:

<CodeGroup>
  ```typescript Monitor Headers theme={null}
  const response = await fetch('https://app.nouvel.ai/api/v1/generate', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer nvl_...',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ urls: [productUrl] })
  });

  const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
  const reset = parseInt(response.headers.get('X-RateLimit-Reset') || '0');

  if (remaining < 10) {
    console.warn(`Only ${remaining} requests remaining. Reset at ${new Date(reset * 1000)}`);
  }
  ```
</CodeGroup>

## Webhook Delivery

<Info>
  Webhook deliveries **do not count** toward your rate limit. Webhooks are initiated by Nouvel's servers, not your API key, and are not subject to the 60/minute restriction.
</Info>

When you register a `webhookUrl` with your [generation request](/api-reference/generate), Nouvel will `POST` to your URL when the job reaches a terminal state. These outbound deliveries are separate from your rate-limited API calls. See the [Webhooks guide](/api-reference/webhooks) for full details.

## Concurrent Video Generation

In addition to the 60 requests/minute API rate limit, video generation has its own concurrency limits based on your plan:

| Plan         | Videos/Month | Max Concurrent Jobs |
| ------------ | ------------ | ------------------- |
| **Scale**    | 30           | 3                   |
| **Business** | 120          | 10                  |

<Note>
  Concurrent jobs are counted per organization. If you submit more jobs than your concurrency limit allows, excess requests will be queued and processed as slots become available.
</Note>

## Need Higher Limits?

If your use case requires higher rate limits or concurrency, please contact our team at [support@nouvel.ai](mailto:support@nouvel.ai). Enterprise plans with custom limits are available.

<Tip>
  Most integrations work comfortably within the 60/minute API rate limit by following the best practices above. Since video generation takes 2–5 minutes per job, use [webhooks](/api-reference/webhooks) or poll [job status](/api-reference/jobs) rather than blocking on the response.
</Tip>
