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

# Rate Limits

> Daily validation quotas per product, what counts against them, and how to handle 429 responses.

KeyPort enforces a daily request limit on the public validation API. Limits are tracked per product API key and reset at midnight UTC.

<Tip>
  You can test normal request behavior directly from the [Validate endpoint playground](/api-reference/validate). For rate-limit scenarios, use the playground for request shape and handle `429` responses in your own integration logic.
</Tip>

## Plan limits

| Plan       | Daily limit per product            |
| ---------- | ---------------------------------- |
| Free       | 10,000 requests                    |
| Pro        | 100,000 requests                   |
| Enterprise | Organization override or unlimited |

## API call counting rules

A request is counted once the product API key is successfully authenticated.

This matches the live backend flow: the guard checks the daily limit first, then increments usage for requests with a valid product API key before product- or license-level business failures are returned.

### Counted

* Valid key + license found (`valid: true`)
* Valid key + license not found on validate (`200`, `valid: false`, `status: license_not_found`)
* Valid key + license revoked (`200`, `valid: false`, `status: revoked`)
* Valid key + `ip_blocked` (`200`, `valid: false`, `status: ip_blocked`)
* Valid key + product archived (`200`, `valid: false`, `status: product_archived`)
* Valid key + license lookup request on `GET /api/v1/license/:key`, including a lookup that later returns `404`

### Not counted

* Invalid or missing API key (`401 invalid_api_key`)
* Empty or malformed bearer token that fails auth (`401`)
* Requests already rejected because the rate limit is exhausted (`429`)

<Note>
  The older wording "valid key + license not found (404)" is not correct for the validate endpoint. In the live public validate API, `license_not_found` returns HTTP `200` with `valid: false`. A `404` can still happen on the separate read-only license lookup endpoint.
</Note>

## When the limit is hit

Once the daily limit is reached, further requests return HTTP 429 with the following body:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "valid": false,
  "status": "rate_limit_exceeded",
  "message": "This license provider or organization owner has reached the daily API request limit for their current plan. Please ask them to upgrade to a paid plan for higher limits, or try again after midnight UTC.",
  "retry_after": 3600
}
```

The `retry_after` value is the number of seconds remaining until midnight UTC, when the counter resets.

The response also includes a `Retry-After` HTTP header with the same value.

## Handling 429 gracefully

<Warning>
  Do not retry immediately on a 429. The limit is daily, so retrying within the same window will continue to fail.
</Warning>

Read `retry_after` from the response body (or the `Retry-After` header) to determine when to resume. In most cases, the right behavior is to surface a clear message to your user and avoid hammering the API.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
async function validateWithRetry(licenseKey: string): Promise<void> {
  const response = await fetch('https://api.keyport.sbs/api/v1/validate', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer kp_live_xxxxxxxxxxxxxxxxxxxxxxxxx',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ license_key: licenseKey }),
  });

  if (response.status === 429) {
    const data = await response.json();
    const retryAfter = data.retry_after ?? 3600;
    console.warn(`Rate limit reached. Try again in ${retryAfter} seconds (resets at midnight UTC).`);
    return;
  }

  // Handle other responses normally
}
```

<Tip>
  If you regularly approach your plan limit, consider upgrading to Pro or Enterprise, or caching validation results on your side to reduce redundant calls.
</Tip>
