import type { Metadata } from "next"; import { PLAN_LIMITS } from "@fetcha/core"; import { CodeBlock } from "@/components/ui/code-block"; import { DocPage } from "@/components/docs/doc-page"; import { A, Code, H2, H3, Li, P, Strong, Table, TBody, Td, Th, THead, Tr, Ul } from "@/components/docs/prose"; import { Callout } from "@/components/docs/callout"; import { Endpoint } from "@/components/docs/endpoint"; import { CodeTabs } from "@/components/docs/code-tabs"; import { ResponseExample } from "@/components/docs/response-example"; import { apiTabs } from "@/components/docs/snippets"; export const metadata: Metadata = { title: "Rate Limits", description: "Concurrency, sliding-window rate limits, RATE_LIMITED vs CONCURRENCY_LIMIT, optional spending limits, USAGE_LIMIT_REACHED and response headers on the single unlimited plan.", }; const L = PLAN_LIMITS.unlimited; const fmt = (n: number) => n.toLocaleString("en-US"); export default function RateLimitsPage() { const perOrgMinute = Math.max(60, L.concurrency * 60); const perKeySecond = Math.max(10, L.concurrency * 2); return ( Every organization has the same limits, listed below and returned by GET /v1/usage. There is nothing to upgrade: hitting a limit means smoothing your traffic, not paying more.

Concurrency

Concurrency is the number of fetch requests in flight at the same time, counted per organization (all projects and keys together). It is the limit that matters most in practice: a fetch takes a second or more, so sustained throughput is roughly concurrency divided by average latency.

Limit Value Scope
Concurrent requests {fmt(L.concurrency)} Organization
Concurrent browser renders {fmt(L.browser_concurrency)} Organization (subset of the above)
Concurrent crawl jobs {fmt(L.crawl_concurrent_jobs)} Organization
Monthly requests Unlimited —
Log retention {L.retention_days} days Organization

When a request would exceed the limit it is refused immediately with 429 CONCURRENCY_LIMIT; nothing is queued on Fetcha's side. A slot is released as soon as a request completes (successfully or not). As a safety net, a slot that is somehow never released expires after 150 seconds.

Request rate

Independently of concurrency, submissions are metered with sliding windows. They are set well above what the concurrency limit lets you sustain, so they only bite on bursts or runaway loops. Three windows are checked on every fetch:

Window Scope Rule Value
60 seconds Organization (sustained) concurrency × 60 per minute {fmt(perOrgMinute)} / min
1 second API key (burst) concurrency × 2 per second {fmt(perKeySecond)} / s
1 second Client IP (abuse) Fixed 200 / s

Exceeding any window returns 429 RATE_LIMITED with a Retry-After header (seconds, rounded up) and details.retry_after_ms.

RATE_LIMITED vs CONCURRENCY_LIMIT

  • RATE_LIMITED: you submitted too many requests in a short window. Wait Retry-After seconds, then continue. Smooth your submission rate.
  • CONCURRENCY_LIMIT: too many requests are running right now. Do not sleep a fixed time; retry when one of your in-flight requests finishes. Use a semaphore or worker pool sized to {fmt(L.concurrency)} or less (see the concurrency example).

No monthly quota

There is no cap on the number of requests per month and no overage. Usage is still counted per organization and per project (every request that reaches the execution pipeline counts, including those that end blocked or failed) so that the dashboard, GET /v1/usage and your optional spending limits have accurate figures. Requests refused earlier (authentication, validation, rate limits, session errors) are not counted.

Spending and project limits

You can cap usage yourself in the dashboard. These are guard-rails against runaway jobs, not billing: spend is an internal cost estimate and nothing is invoiced. All of them produce the same{" "} USAGE_LIMIT_REACHED code with a specific message and details:

Limit Scope Effect when reached details
Monthly request limit Project Fetches in that project are refused {`{ limit, used }`}
Hard spending limit (USD estimate) Organization All fetches are refused {`{ limit_usd, spent_usd }`}
Hard spending limit (USD estimate) Project Fetches in that project are refused {`{ limit_usd, spent_usd }`}
Soft spending limit (USD estimate) Organization or project Alert only; traffic continues —
Spend is Fetcha's internal cost estimate of your usage, accumulated over the calendar month (UTC). It is what GET /v1/usage reports as spend_usd. Usage counters are cached for up to 20 seconds, so enforcement of a limit you set can lag a few requests behind.

Checking your usage

  • organization covers every project; project is the key's project only.
  • plan.monthly_requests and remaining_requests are null: there is no quota. A project-level request limit you configured is reported in the dashboard, not here.
  • success_rate is a percentage with one decimal, or null when the project made no requests this month.

Response headers

Fetcha currently sets these headers on every API response. There are no X-RateLimit-* counters; use GET /v1/usage for month-to-date figures.

# only on 429 RATE_LIMITED`} />
); }