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

# OTP.ID API Rate Limits: Global and Per-Destination Policies

> OTP.ID enforces a global 20 req/s limit per API key and per-destination limits of 5 OTPs per 10 minutes and 10 per hour. Learn how to handle both.

OTP.ID enforces two independent rate-limiting policies: a global per-API-key limit that applies to every endpoint, and a per-destination limit that protects individual users from receiving too many OTP messages in a short period. Both limits return HTTP 429 responses with distinct error codes so you can handle them separately.

## Summary

| Policy                         | Scope                                                        | Limit                | Error Code                 |
| ------------------------------ | ------------------------------------------------------------ | -------------------- | -------------------------- |
| Global                         | All `/v3/*` endpoints, per API key                           | 20 requests / second | `RATE_LIMITED`             |
| Per-destination (short window) | `POST /v3/request` and `POST /v3/send` only, per destination | 5 OTPs / 10 minutes  | `DESTINATION_RATE_LIMITED` |
| Per-destination (long window)  | `POST /v3/request` and `POST /v3/send` only, per destination | 10 OTPs / hour       | `DESTINATION_RATE_LIMITED` |

***

## Global per-API-key limit

Every request you make to any `/v3/*` endpoint counts toward your global rate limit. The window is a fixed 1-second bucket. If your API key sends more than 20 requests within that second — across `POST /v3/request`, `POST /v3/send`, `POST /v3/verify`, `GET /v3/otp/{otp_id}`, or any other v3 endpoint — all additional requests within that window are rejected.

**Error response:**

```json theme={null}
{
  "success": false,
  "data": null,
  "error": {
    "code": "RATE_LIMITED",
    "message": "Terlalu banyak request, coba lagi sebentar"
  }
}
```

### Handling `RATE_LIMITED`

Implement **exponential backoff** when you receive this error. A simple strategy:

1. On the first `RATE_LIMITED` response, wait \~100 ms and retry.
2. On each subsequent failure, double the wait time (200 ms, 400 ms, 800 ms…).
3. Add a small random jitter (±20%) to prevent synchronized retries from multiple instances of your service all hitting the limit at the same moment.
4. Set a maximum retry count (e.g., 5 attempts) and surface an error to the user if all retries are exhausted.

```python theme={null}
import time
import random

def send_otp_with_backoff(payload, max_retries=5):
    delay = 0.1  # initial delay in seconds
    for attempt in range(max_retries):
        response = call_otp_api(payload)
        if response["success"]:
            return response
        if response["error"]["code"] == "RATE_LIMITED":
            jitter = delay * random.uniform(0.8, 1.2)
            time.sleep(jitter)
            delay *= 2
        else:
            raise OtpApiError(response["error"])
    raise MaxRetriesExceeded()
```

<Tip>
  If you regularly hit the global limit in production, batch your OTP requests or spread them more evenly over time. Contact OTP.ID support to discuss a higher limit for high-volume use cases.
</Tip>

***

## Per-destination limit

When you call `POST /v3/request` or `POST /v3/send`, OTP.ID also enforces a per-destination limit. This limit applies to the combination of your API key (merchant) and the destination phone number or email address — so separate merchants are counted independently.

Two windows are enforced simultaneously:

* **Short window:** No more than **5 OTPs per 10 minutes** to the same destination
* **Long window:** No more than **10 OTPs per hour** to the same destination

Both conditions must be satisfied for a request to succeed. If either window is exhausted, the request is rejected.

<Note>
  This limit applies only to OTP creation endpoints (`POST /v3/request` and `POST /v3/send`). Calls to `POST /v3/verify` and `GET /v3/otp/{otp_id}` are not subject to the per-destination policy.
</Note>

**Error response:**

```json theme={null}
{
  "success": false,
  "data": null,
  "error": {
    "code": "DESTINATION_RATE_LIMITED",
    "message": "Terlalu banyak OTP ke nomor ini, coba lagi nanti"
  }
}
```

### Why this limit exists

The per-destination limit protects your users from OTP flooding — a situation where an attacker (or a bug in your application) repeatedly triggers OTP messages to the same phone number or email address. Without this limit, a single user could receive dozens of messages in quick succession, which is a poor experience and a potential harassment vector.

### Handling `DESTINATION_RATE_LIMITED`

Unlike the global limit, you cannot simply retry immediately — you must wait for the current window to expire before sending another OTP to that destination.

**Recommended approach:**

1. **Track OTP send counts on your side.** Maintain a counter per destination in your own cache or database so you can check the limit before calling the API and show users a friendly waiting message instead of an opaque error.
2. **Surface a user-facing message.** Tell the user something like "Too many OTP requests. Please wait a few minutes before requesting a new code."
3. **Do not retry automatically.** This error is not a transient failure — retrying the same destination will continue to fail until the window resets.

```javascript theme={null}
// Example: client-side guard before calling the API
async function requestOtp(destination) {
  const recentCount = await getRecentOtpCount(destination, 10 * 60); // last 10 min
  if (recentCount >= 5) {
    throw new Error("Please wait before requesting another OTP.");
  }
  const response = await fetch("https://api.otp.id/v3/request", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ channel: "whatsapp", number: destination })
  });
  return response.json();
}
```

<Warning>
  Do not silently swallow `DESTINATION_RATE_LIMITED` errors and retry in the background — this will burn through your API balance and still fail. Always propagate a meaningful message to the user.
</Warning>

***

## Both limits together

It is possible to receive either limit in a single high-traffic session. The table below summarises how to differentiate them:

| Scenario                                                    | Error Code                 | Right response                                 |
| ----------------------------------------------------------- | -------------------------- | ---------------------------------------------- |
| Your system is making too many API calls per second overall | `RATE_LIMITED`             | Exponential backoff, then retry                |
| You've sent too many OTPs to one specific number/email      | `DESTINATION_RATE_LIMITED` | Wait for the window to expire; notify the user |

If you need to increase the global rate limit beyond 20 requests/second, contact [OTP.ID support](https://otp.id). Per-destination limits are fixed and cannot be adjusted.
