> ## Documentation Index
> Fetch the complete documentation index at: https://developer.jtl-software.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate Limiting

> Understand how rate limits work across JTL's APIs: quotas, response headers, and strategies for staying within limits.

Rate limiting controls how many requests your app can make to JTL's APIs within a given time window. It ensures fair usage across all developers and prevents any single app from overloading the system.

Exceeding the limit returns an **HTTP 429 "Too Many Requests"** error. Some endpoints have lower limits than others to protect resource-intensive operations.

## How Rate Limiting Works

```mermaid theme={null}
flowchart TD
    A["App sends API request"] --> B{"Within rate limit?"}
    B -->|Yes| C["API processes request"]
    C --> D["Response includes rate limit headers"]
    D --> E{"X-RateLimit-Remaining > 0?"}
    E -->|Yes| A
    E -->|No| F["Wait for interval to reset"]
    F --> A
    B -->|No| G["HTTP 429 Too Many Requests"]
    G --> H["Wait + retry with backoff"]
    H --> F

    style A fill:#E8F4FF,stroke:#89D2FF,stroke-width:2px,color:#0B1B45
    style C fill:#E8F4FF,stroke:#89D2FF,stroke-width:2px,color:#0B1B45
    style G fill:#FCE4EC,stroke:#FB581F,stroke-width:2px,color:#0B1B45
    style H fill:#FFF2EB,stroke:#FB581F,stroke-width:2px,color:#0B1B45
```

Every API response includes headers that tell you your current quota status. Monitor these headers to stay within limits proactively rather than reacting to 429 errors.

## Rate Limit Response Headers

Every response from JTL's APIs includes these headers:

| Header                                | Description                                      | Example |
| ------------------------------------- | ------------------------------------------------ | ------- |
| `X-RateLimit-Limit`                   | Maximum requests allowed in the current interval | `10`    |
| `X-RateLimit-Interval-Length-Seconds` | Length of the rate limit window in seconds       | `45`    |
| `X-RateLimit-Remaining`               | Requests remaining in the current interval       | `5`     |

**Read these headers after every request.** They're your real-time view of how much quota you have left. When `X-RateLimit-Remaining` approaches zero, slow down or pause until the interval resets.

```typescript theme={null}
const response = await fetch('https://api.jtl-cloud.com/erp/v2/products', {
	headers: {
		Authorization: `Bearer ${accessToken}`,
		'X-Tenant-ID': tenantId,
	},
});

// Check rate limit status
const limit = response.headers.get('X-RateLimit-Limit');
const remaining = response.headers.get('X-RateLimit-Remaining');
const interval = response.headers.get('X-RateLimit-Interval-Length-Seconds');

console.log(
	`Rate limit: ${remaining}/${limit} remaining (resets every ${interval}s)`,
);
```

***

## Rate Limits by API

### Cloud-ERP API

The Cloud-ERP API applies rate limits to all endpoints. Use the `X-RateLimit-*` response headers to determine the limits for each endpoint in your environment.

### SCX Channel API

The SCX Channel API has published rate limits per route pattern:

| Route pattern                             | Requests | Interval       | Effective Rate       |
| ----------------------------------------- | -------- | -------------- | -------------------- |
| Default (all other routes)                | 10       | 60 seconds     | \~0.17/sec           |
| `/v[version]/channel*`                    | 60       | 60 seconds     | 1/sec                |
| `/v[version]/channel/order*`              | 600      | 60 seconds     | 10/sec               |
| `/v[version]/channel/event*`              | 240      | 60 seconds     | 4/sec                |
| `/v[version]/channel/offer*`              | 1,500    | 60 seconds     | 25/sec               |
| `/v[version]/channel/attribute/category*` | 86,400   | 86,400 seconds | 1/sec (daily)        |
| `/v[version]/channel/attribute/global*`   | 10       | 3,600 seconds  | \~0.003/sec (hourly) |
| `/v[version]/channel/categories*`         | 10       | 3,600 seconds  | \~0.003/sec (hourly) |

<Warning>
  Sandbox rate limits are different from production. Don't assume your sandbox
  testing reflects production capacity. Always check the `X-RateLimit-*`
  headers in each environment.
</Warning>

## Handling Rate Limit Errors

When you exceed the limit, the API returns HTTP `429 Too Many Requests`. Here's how to handle it properly.

### 1. Monitor Headers Proactively

Don't wait for a 429, prevent it. Track `X-RateLimit-Remaining` and pause before you hit zero:

```typescript theme={null}
async function fetchWithRateLimit(
	url: string,
	options: RequestInit,
): Promise<Response> {
	const response = await fetch(url, options);

	const remaining = parseInt(
		response.headers.get('X-RateLimit-Remaining') || '999',
	);
	const interval = parseInt(
		response.headers.get('X-RateLimit-Interval-Length-Seconds') || '60',
	);

	// If running low, pause before the next request
	if (remaining <= 2) {
		console.warn(
			`Rate limit nearly exhausted (${remaining} remaining). Pausing...`,
		);
		await new Promise((resolve) => setTimeout(resolve, interval * 1000));
	}

	return response;
}
```

### 2. Retry with Exponential Backoff

When you get a 429, don't retry immediately. Wait at increasing intervals before retrying:

```typescript theme={null}
async function fetchWithRetry(
	url: string,
	options: RequestInit,
	maxRetries = 3,
): Promise<Response> {
	for (let attempt = 0; attempt <= maxRetries; attempt++) {
		const response = await fetch(url, options);

		if (response.status === 429 && attempt < maxRetries) {
			const waitTime = Math.pow(2, attempt) * 1000 + Math.random() * 500;
			console.warn(
				`Rate limited. Retrying in ${Math.round(waitTime)}ms...`,
			);
			await new Promise((resolve) => setTimeout(resolve, waitTime));
			continue;
		}

		return response;
	}

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

**Backoff Schedule (with Jitter):**

| Attempt   | Base wait | With Jitter     |
| --------- | --------- | --------------- |
| 1st retry | 1 second  | 1.0–1.5 seconds |
| 2nd retry | 2 seconds | 2.0–2.5 seconds |
| 3rd retry | 4 seconds | 4.0–4.5 seconds |

### 3. Queue and Spread Requests

Use a queue to enforce a fixed request rate and avoid bursts.

```typescript theme={null}
class RequestQueue {
	private queue: (() => Promise<void>)[] = [];
	private processing = false;
	private delayMs: number;

	constructor(requestsPerMinute: number) {
		this.delayMs = Math.ceil(60000 / requestsPerMinute);
	}

	async add<T>(fn: () => Promise<T>): Promise<T> {
		return new Promise((resolve, reject) => {
			this.queue.push(async () => {
				try {
					resolve(await fn());
				} catch (err) {
					reject(err);
				}
			});
			this.process();
		});
	}

	private async process() {
		if (this.processing) return;
		this.processing = true;

		while (this.queue.length > 0) {
			const task = this.queue.shift()!;
			await task();
			await new Promise((resolve) => setTimeout(resolve, this.delayMs));
		}

		this.processing = false;
	}
}

// Usage: limit to 50 requests per minute
const queue = new RequestQueue(50);

const result = await queue.add(() =>
	fetch('https://api.jtl-cloud.com/erp/v2/products', { headers }),
);
```

### 4. Cache Responses

Cache data based on how often it changes:

* Rarely changes → cache longer (categories, attributes)
* Frequently changes → cache briefly or not at all (orders)

## Best Practices

| Practice                                      | Why                                                                                                                                     |
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| **Read rate limit headers on every response** | Know your remaining quota before it runs out                                                                                            |
| **Spread requests evenly**                    | Avoid sending many requests at once. It wastes your quota window                                                                        |
| **Add jitter to retries**                     | Prevents multiple clients from retrying at the exact same time                                                                          |
| **Cache aggressively for reference data**     | Categories, attributes, and settings don't change often                                                                                 |
| **Use webhooks instead of polling**           | Instead of checking "did anything change?" every minute, let JTL tell you. See [Webhooks](/guides/essentials/common-patterns/webhooks). |
| **Max 3 retries on 429**                      | If you're still rate limited after 3 retries, something is fundamentally wrong with your request pattern                                |
| **Log rate limit events**                     | Track when you're being throttled to identify patterns and optimise your request timing                                                 |

## Quick Reference

| Question                                   | Answer                                                 |
| ------------------------------------------ | ------------------------------------------------------ |
| What status code means rate limited?       | `429 Too Many Requests`                                |
| How do I check my remaining quota?         | Read the `X-RateLimit-Remaining` response header       |
| How long until the limit resets?           | Check `X-RateLimit-Interval-Length-Seconds`            |
| What's the max requests per interval?      | Check `X-RateLimit-Limit` (varies by endpoint)         |
| Should I retry on 429?                     | Yes, with exponential backoff + jitter (max 3 retries) |
| Are sandbox limits the same as production? | No. Always check the response headers per environment  |

***

## What's Next?

<CardGroup cols={2}>
  <Card title="Webhooks" icon="bell" href="/guides/essentials/common-patterns/webhooks">
    Reduce polling and API calls by reacting to events in real time.
  </Card>

  <Card title="Pagination" icon="arrow-right" href="/guides/essentials/common-patterns/pagination">
    How to page through large result sets without hitting rate limits.
  </Card>

  <Card title="Error Handling" icon="triangle-alert" href="/guides/essentials/common-patterns/error-handling">
    Handle 429 errors alongside other error types.
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/overview">
    Browse endpoints and their specific rate limits.
  </Card>
</CardGroup>
