Skip to main content
Documentation

Rate Limiting

How WarpLink API rate limiting works, the per-minute request limits, response headers, and best practices for staying within them.

WarpLink uses a sliding window rate limiter on all API endpoints.

API keys are rate limited to 60 requests per minute per key, using a sliding window algorithm. The three routes the mobile SDKs call from user devices are limited to 60 requests per minute per key per client IP address. Devices on different IP addresses get separate allowances, and devices behind one public IP address share one. The limit is flat: every plan, from Free through Unlimited, gets the same 60 requests a minute. There is no paid tier that raises it, and no lower limit for a new key. The MCP server shares the per-credential limit too. The REST API accepts API keys only, and the MCP server accepts the same API keys or OAuth tokens.

How It Works

A sliding window is different from a fixed window, and the difference matters once you're near the limit. A fixed window resets at a clock boundary, so a caller can spend its full quota at 0:59 and its full quota again at 1:00, doubling its real rate for one second. A sliding window has no boundary to game: it always looks back exactly 60 seconds from the current request, so the count you're compared against reflects your actual recent request rate, not how close you are to a reset tick.

The limiter keys each request by a hash of the caller's bearer token, not by organization. The three SDK routes add the caller's IP address to that key, as described in SDK routes below. For every other route, the token alone is the key. This has a direct consequence: two different API keys on the same organization get two independent 60-per-minute budgets. A backend script and a CI job, each with its own key, do not compete with each other for quota, and one team member's misbehaving script cannot exhaust the limit for an SDK key or a teammate's key on the same org. If you split load across keys, you get more real throughput, not because the limiter is lenient but because the accounting is genuinely separate. This also means the token itself is never sent anywhere in the clear as a lookup value: it's hashed before being used as the rate-limit key, the same way it's hashed for storage when the key was created.

Three request types are rate limited differently. Authenticated REST and MCP requests are limited by their hashed bearer token, as described above. The SDK routes are limited by the hashed bearer token and the client IP address together. The OAuth endpoints that issue and register credentials in the first place (dynamic client registration and the token endpoint) carry no bearer token to key off of, since that's what they're issuing, so those are limited by client IP address alone. All three paths enforce the same 60-per-minute ceiling; only the key differs.

SDK Routes

Every install of a mobile app ships the same SDK key. If those installs shared one 60-per-minute budget, a burst of installs or link opens would return 429 to devices that had done nothing wrong. So three routes are limited per key per client IP address instead:

RouteCalled by
GET /v1/sdk/validateSDK initialization
GET /v1/links/resolve/{slug}Deep link handling
POST /v1/attribution/matchInstall attribution and deferred deep links

Each client IP address gets its own 60 requests per minute per key for these routes. The route decides the bucket, not the key type, so an API key that calls one of these routes is counted the same way.

A few details worth knowing:

  • Devices behind one public IP address share one allowance. Many phones on the same office, venue, or carrier network address count together against the same 60 requests a minute per key. One device sends far fewer than that in normal use, but a large group of devices opening links at the same moment behind one address can reach the limit together.
  • The IP address is the one the connection arrives from. A caller cannot set it with a request header, so sending a different X-Forwarded-For value does not create a new allowance.
  • IPv6 callers are counted by their /64 network prefix, because a single device can rotate through many addresses inside that prefix.
  • If the address cannot be determined, the request falls back to the per-key budget.
  • Every other route, including every /v1/links management route and the MCP server, keeps the per-key budget described above.

Redirects are not covered by any of this. A visitor tapping a WarpLink URL is served at the edge with its own protections against abusive traffic, entirely separate from the API limiter described here, and that path never returns the headers or error shape below. The 60-per-minute ceiling applies only to calls against api.warplink.app, not to people clicking your links.

Response Headers

Every API response includes rate limit information:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed per window
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetSeconds until the window resets

X-RateLimit-Limit is always 60. X-RateLimit-Remaining counts down within the current sliding window and is safe to poll before sending a burst of requests, since reading it costs nothing extra. X-RateLimit-Reset is the number of seconds until the oldest request in the current window ages out, not a fixed countdown to a clock boundary, which is consistent with the sliding window described above: as older requests fall outside the 60-second lookback, the count that matters against your limit keeps shrinking on its own.

Handling Rate Limits

When the limit is exceeded, the API returns 429 Too Many Requests with a Retry-After header indicating how many seconds to wait.

The Retry-After value is derived from the same sliding window as X-RateLimit-Reset, so waiting exactly that long and retrying is the correct behavior rather than a guess. A client that retries immediately without honoring Retry-After just spends another request confirming the same 429.

Worked Example

A request that's still within its budget gets the full header set at 200:

The same key, once it has spent its budget within the current 60-second window, gets a 429 instead, with a Retry-After matching the header above:

When to Use

Rate limiting matters most for two kinds of integrations: backend jobs that create or update many links in a short burst, and AI agents connected through MCP that can issue several tool calls per user turn without a human pacing them. Both patterns are more likely to approach 60 requests a minute than a person clicking around a dashboard, so both benefit from reading X-RateLimit-Remaining before firing off the next call rather than reacting only after a 429.

Limits and Edge Cases

  • The limit is per API key or OAuth connection, not per organization. Creating a second key does not raise your organization's effective ceiling in aggregate, but it does give that key its own independent 60-per-minute budget, which is the right lever for isolating one integration's traffic from another's.
  • Money operations over REST and MCP (money inputs, expenses, and money exports) share one 60-requests-per-minute budget per organization across all of its keys.
  • The pre-auth OAuth endpoints (client registration, token exchange) are limited by IP, so multiple OAuth clients registering from behind the same network address share one budget until each has its own token.

Best Practices

  • Implement exponential backoff when you receive a 429 response, rather than retrying immediately.
  • Cache API responses when possible to reduce request volume against the 60-per-minute ceiling.
  • Use the X-RateLimit-Remaining header to proactively throttle requests before you hit the limit.
  • For bulk operations, batch requests rather than making many individual calls, since each call counts separately against the same budget.

Troubleshooting

I'm getting 429s well under what feels like 60 requests a minute. Check whether more than one process shares the same API key. The limiter counts by hashed token, so two workers or two deploys of the same job using one key split one 60-per-minute budget between them. Give each process its own key. On the SDK routes the budget is per key per client IP address.

X-RateLimit-Remaining looks wrong after a burst. Because the window is sliding, Remaining recovers gradually as old requests age out of the 60-second lookback, not all at once at a fixed reset time. If you burst 60 requests in one second, expect Remaining to climb back only as those requests individually turn 60 seconds old.

I don't see rate-limit headers on a redirect response. That's expected. The headers documented here appear on api.warplink.app responses. Redirect traffic on your short-link domain is a different path with its own edge-level protections and does not carry these headers.

FAQ

Does the rate limit differ by pricing plan? No. All plans, Free through Unlimited, share the same flat 60 requests per minute per API key. On the SDK routes it is 60 requests per minute per key per client IP address.

Does resolving a link through the API count against my rate limit? Yes. Any authenticated call to api.warplink.app counts toward the calling key's 60-per-minute budget. GET /v1/links/resolve/{slug} is one of the three SDK routes, so it counts toward the budget for that key and the caller's IP address.

How can I handle more than 60 requests a minute? Each API key carries its own budget of 60 requests per minute. For more throughput, split the workload across multiple API keys so each carries its own independent budget rather than sharing one. The mobile SDKs do not need this: on the SDK routes, devices on different IP addresses already have separate allowances.

Is the MCP server rate limited separately from the REST API? No, they share the same limiter and the same 60-per-minute ceiling, keyed by the same bearer token or IP, whichever authentication path the request used.