Skip to main content
    Ivy Biosciences Docs/Call the platform/API/Start here/Rate Limits

    Rate Limits

    Rate limits per authentication method, response headers, and 429 handling strategies

    referencerate-limitsthrottling

    The API counts requests per client and per route, and answers a 429 with how long to wait. Read this page when you're sizing a batch of calls, seeing throttling errors, or deciding how often to poll: it lists the per-minute and per-second ceilings, the headers a throttled response carries, and how to back off.

    Difficulty

    beginner

    Time

    5 min

    Prerequisites

    Rate Limits

    Two layers throttle every web API request. The per-minute layer counts by client IP, so a Clerk JWT and an API key from the same machine share that budget; the burst layer counts per route and caller. Neither depends on which credential you send.


    Per-minute limits (every /api route)

    The edge middleware picks a tier from the path and method, then counts requests per client IP over a one-minute window:

    TierApplies toRequests per minute
    ReadGET requests200
    WritePOST, PUT, PATCH, DELETE requests50
    ExpensivePaths containing /compute, /rdkit, /orchestrator or /export20
    SearchPaths containing /search, /suggest or /autocomplete120

    Burst limits (per route)

    Each route also sets a one-second and a three-second ceiling. Job submission is the tightest:

    Route1 second3 seconds
    POST /api/v1/jobs25
    GET /api/v1/jobs512
    Harness session routes (create, get, checkpoints, events)615
    POST /api/harness/sessions/:id/execute and /sync1030

    Guest calls

    A caller with no key uses the keyless orchestrate request: one POST to /api/v1/orchestrate with a prompt. Its daily limit is stated once, with the request, in Guest curl and in llms.txt. Each guest response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; read them rather than counting locally.


    Rate Limit Headers

    Throttled responses (HTTP 429) carry Retry-After. The per-minute layer also sends its counters on the 429:

    HeaderDescription
    Retry-AfterSeconds to wait before retrying
    X-RateLimit-LimitMaximum requests in the current window
    X-RateLimit-RemainingRemaining requests in the current window
    X-RateLimit-ResetWhen the window resets
    X-RateLimit-TierWhich per-minute tier refused the request

    Successful authenticated responses do not carry these headers; only the guest orchestrate route reports its remaining quota on every response.


    429 Response Format

    The per-minute layer answers:

    {
      "error": "Rate limit exceeded for Write-heavy API endpoints (POST/PUT/DELETE) endpoints",
      "retryAfter": 42
    }

    A burst limit answers with the standard error envelope:

    {
      "success": false,
      "code": "RATE_LIMITED",
      "message": "Too many requests. Please try again shortly.",
      "details": { "retryAfterSec": 1 }
    }

    Handling Rate Limits

    TypeScript SDK

    The SDK automatically retries 429 responses with exponential backoff (up to config.retries attempts):

    const ivy = createIvyClient({
      apiKey: "ivy_your_key",
      retries: 3, // Retries 429 and 5xx up to 3 times
    })

    Manual Retry (Any Language)

    async function submitWithRetry(params: SubmitJobParams, maxRetries = 3) {
      for (let attempt = 0; attempt < maxRetries; attempt++) {
        try {
          return await ivy.jobs.submit(params)
        } catch (e) {
          if (e instanceof IvyApiError && e.isRateLimited && attempt < maxRetries - 1) {
            const wait = Math.pow(2, attempt) * 1000
            await new Promise(r => setTimeout(r, wait))
          } else {
            throw e
          }
        }
      }
    }

    React with Rate Limit UI

    import { IvyApiError } from "@ivybiosciences/sdk"
    import { useIvyJob } from "@ivybiosciences/sdk/react"
    import { useState } from "react"
    
    function RateLimitAwareSubmit() {
      const [cooldown, setCooldown] = useState(0)
    
      const { submit, isLoading } = useIvyJob({
        onError: (e) => {
          if (e instanceof IvyApiError && e.isRateLimited) {
            setCooldown(60) // Show countdown
            const timer = setInterval(() => {
              setCooldown((c) => {
                if (c <= 1) { clearInterval(timer); return 0 }
                return c - 1
              })
            }, 1000)
          }
        },
      })
    
      return (
        <button disabled={isLoading || cooldown > 0}>
          {cooldown > 0 ? `Wait ${cooldown}s` : isLoading ? "Running..." : "Submit"}
        </button>
      )
    }

    Best Practices

    1. Use the SDK — built-in retry handles 429 automatically
    2. Honor Retry-After — wait the stated seconds before the next attempt
    3. Batch when possible — use the Batch API for multiple items
    4. Use WebSocket/SSE — avoid polling for job status; subscribe to real-time updates instead
    5. Cache lookups — public endpoints like /lookup/uniprot/:id return stable data suitable for caching

    Was this page helpful?

    On this page

    On this page

    Loading...