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

# Rate Limits

> Understanding rate limits in the Widgetic API

Rate limits protect the API from abuse and keep it fair for everyone. This page explains how they work and what a refused request looks like.

## How Rate Limits Work

Requests are counted over a 15-minute window. When a window's allowance is used up, further requests are refused with `429 Too Many Requests` until the window ends. A refused request is not run.

* **A recognized API key is counted on its own**, so one customer's traffic never uses up another's.
* **Anything else is counted by IP address**: calls with no key, and calls with a key the API cannot vouch for.
* **A partner key is held to a ceiling of 1,000,000 requests per 15 minutes** (about 1,100 a second). It is there to stop a runaway loop, not to meter a platform that serves hundreds of thousands of its own users.

The allowance that applies to a call is in the `RateLimit-Limit` header of its response, so read it there instead of hard-coding a number.

## Reading Rate Limit Headers

Responses that were counted include rate limit headers:

```http theme={null}
RateLimit-Policy: 3000;w=900
RateLimit-Limit: 3000
RateLimit-Remaining: 2995
RateLimit-Reset: 540
```

* `RateLimit-Policy`: the allowance and the length of the window in seconds (`3000;w=900` is 3,000 requests per 900 seconds)
* `RateLimit-Limit`: the requests allowed in the window
* `RateLimit-Remaining`: the requests left in the window
* `RateLimit-Reset`: the seconds until the window ends (a number of seconds to wait, not a timestamp)

The older `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` headers are sent as well. The first two carry the same numbers. `X-RateLimit-Reset` does not: it is the time the window ends, in Unix seconds, not a number of seconds to wait. Read the `RateLimit-*` headers.

Use these to track your usage and slow down before you are refused.

## Handling 429 Responses

Whichever limit you ran into, a refusal looks the same: a `Retry-After` header and the standard error envelope. `Retry-After` and `error.retryAfter` are the same number, in whole seconds (at least 1).

```http theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 540
Content-Type: application/json

{
  "success": false,
  "message": "Too many requests, please try again later.",
  "error": {
    "code": "RATE_LIMIT_API_KEY_EXCEEDED",
    "message": "Too many requests, please try again later.",
    "retryAfter": 540,
    "details": { "limit": 3000 },
    "timestamp": "2026-10-02T12:00:00.000Z",
    "docUrl": "/api-reference/errors#rate-limiting-errors-429",
    "suggestedAction": "Wait before making more requests or upgrade your plan"
  }
}
```

`error.code` tells you which limit it was:

* `RATE_LIMIT_EXCEEDED`: the general limit for calls counted by IP address, the sign-in limit, or the limit on public embed endpoints
* `RATE_LIMIT_API_KEY_EXCEEDED`: the limit on a recognized API key
* `RATE_LIMIT_USER_EXCEEDED`: the brake on one person's use of one kind of operation, such as billing changes (20 a minute), creating widgets (120 a minute), buying extra credits (10 an hour) or replaying a notification delivery (5 a minute). It is separate from the allowance on your key, and it is counted across all of that person's keys and sessions. `error.details.operation` names the operation. [Usage Limits](https://widgetic.com/api-reference/usage-limits.md#endpoint-specific-limits) lists them all
* `COST_LIMIT_EXCEEDED`: a usage budget per hour or per day, not a count of requests. `Retry-After` is the end of the hour or day whose budget was used up
* `CONCURRENCY_LIMIT_EXCEEDED`: too many operations of yours are running at once. It has no `Retry-After`: retry once one of them has finished

`error.details` carries what is particular to the limit (the `limit`, and for some codes the operation or the budgets). Do not depend on a key that is not documented for its code.

> [!NOTE]
>
> A refusal never reaches the operation, so it is safe to retry. When you retry a `POST`, send the **same** `Idempotency-Key` as the first attempt (see [Idempotency](https://widgetic.com/docs/reference/idempotency.md)).

### Implementation Example

```typescript theme={null}
async function callWithRetry(url: string, init: RequestInit, maxRetries = 3): Promise<Response> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch(url, init);
    if (response.status !== 429) return response;
    if (attempt === maxRetries) break;

    // `Retry-After` is a number of seconds. A CONCURRENCY_LIMIT_EXCEEDED refusal has none:
    // wait a few seconds and try again.
    const waitSeconds = Number(response.headers.get('Retry-After') ?? 5);
    console.log(`Rate limited. Waiting ${waitSeconds}s...`);
    await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1000));
  }
  throw new Error('Max retries exceeded');
}
```

## Best Practices

1. **Honor `Retry-After`** - It is the time left until your counter starts over; retrying sooner is refused again
2. **Monitor headers proactively** - Slow down before hitting limits
3. **Implement exponential backoff** - When a refusal has no `Retry-After`, don't retry immediately
4. **Use batch endpoints** - Reduce total request count
5. **Cache responses** - Avoid redundant requests
6. **Spread requests** - Don't burst all at once

## Need Higher Limits?

Contact [support@widgetic.com](mailto:support@widgetic.com) to discuss limits above what your key has today.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.