WzGate
Build your own website

Rate limits

The per-IP floor, the per-site ceiling, the tighter per-route limits on writes, what a secret key buys, and how to handle a 429.

Limits exist on three levels at once, and a request must pass all of them. Every count is per client IP unless the row says otherwise; the window is a rolling one, not a calendar minute.

The floor

ScopeLimit
Every /api/public/* route, per client IP120 requests / minute
Every /api/public/* route, per site600 requests / minute

The per-site ceiling protects one tenant's database from another tenant's scraper: a scraper has many IP addresses, and a site has one id.

A call authenticated with a secret key (sk_…) gets five times both numbers — 600/minute per IP and 3 000/minute per site. That is the difference between a browser and your own server: one is many visitors, the other is one program you control.

Per-route limits on writes

The floor is a floor. The routes worth grinding carry their own, tighter ceilings — per IP, so one visitor cannot burn a whole site's budget.

RouteLimit
POST /public/leads5 / 10 minutes
POST /public/viewing-requests5 / 10 minutes
POST /public/auth/register5 / 10 minutes
POST /public/auth/verify-otp10 / 10 minutes
POST /public/auth/resend-otp3 / 10 minutes
POST /public/auth/login10 / 10 minutes
POST /public/auth/forgot-password3 / 10 minutes
POST /public/auth/reset-password5 / 10 minutes
POST /public/newsletter5 / 10 minutes
POST /public/search60 / minute
POST /public/properties/{id}/view60 / minute
POST /public/me/saved-searches20 / 10 minutes
PUT / DELETE /public/me/favorites/…30 / minute

Read routes are covered by the floor only.

What this means for your pages

  • A property page that records a view on every render is fine at 60/minute, but not if you also record one per client-side navigation. Record once per property per visit and dedupe locally.
  • Search-as-you-type must be debounced. 60 POST searches a minute is one per second; a keystroke-per-request input exceeds it on one sentence.
  • Never retry a failed write automatically. A lead form that retries on error will hit 5-per-10-minutes and then look broken to a real person.
  • Server-rendered pages share one IP — your server's. That is what the secret key's higher limits are for, so set SITE_SECRET_KEY on any deployment that renders on the server. Cache what you can (next: { revalidate }) rather than re-fetching the same catalogue on every visitor's request.

Handling a 429

A refusal is HTTP 429 with no code in the body — the status is the signal.

if (res.status === 429) {
  // Not an error in your code. Show a "try again in a moment" message,
  // keep the form filled in, and let the person press the button again.
  return { tooMany: true };
}

Back off rather than retrying, keep whatever the visitor typed, and re-enable the button after a pause. On a write, a silent retry is worse than useless: the first attempt may already have succeeded before the limit refused the second.

Limits are counted on the real client IP, resolved from the proxy in front of the API. Visitors behind the same office network or the same mobile carrier NAT therefore share a bucket for the tighter write limits — another reason to keep a lead form to one submit.

On this page