# Rate Limits Synthient enforces two distinct limits: **lookup credits** for the synchronous IP and domain APIs, and **per-endpoint request-rate limits** that apply to every HTTP call. Both are per-team. ## Lookup credits Every call to the [IP API](https://docs.synthient.com/ipapi) or domain lookup consumes credits from your plan's monthly allotment. Inspect the remaining balance and reset cadence on [`/account/me`](https://docs.synthient.com/account): ```json { "lookup_quota": { "credits": 982341, "resets_in": 1893456 } } ``` `credits` is the integer balance remaining; `resets_in` is the seconds until the balance refills. ### How requests are charged | Endpoint | Cost | | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | `GET /api/v4/lookup/ip/{ip}` | **1 credit** per request. | | `POST /api/v4/lookup/ips` | **`ceil(n × 0.9)` credits**, where `n` is the number of unique, valid IPs in the body (10% batch discount). | | `GET /api/v4/lookup/domain/{domain}` | **1 credit** per request. | | Feed exports (`/feeds/{stream}/export*`) | Free, counted against your feed subscription, not lookup credits. | | Feed streams (`/feeds/{stream}/stream`) | Free, counted against your concurrent-stream allowance. | #### Worked example A batch request with 100 IPs costs `ceil(100 × 0.9) = 90` credits versus 100 credits as individual lookups, a 10-credit saving. A batch with 1,000 IPs costs `900` credits. Duplicate and invalid entries are stripped before charging, so they don't count toward your bill. Track your remaining balance via [`/account/me`](https://docs.synthient.com/account). ### Out of credits When `credits` reaches zero, lookup endpoints return [`402 Payment Required`](https://docs.synthient.com/errors). Either add credits, upgrade your plan, or wait for `resets_in` to elapse. Streaming and export endpoints are unaffected; they don't consume lookup credits. ## Per-endpoint rate limits Every endpoint is rate-limited per **team** (the API key's owning organization), not per IP, so multiple keys for the same team share the same buckets. The defaults below are sized for a healthy production caller; reach out to [support](https://docs.synthient.com/misc/support) before designing around higher throughput. | Endpoint | Sustained rate | Burst | | ------------------------------------------------------- | ----------------------- | ----- | | `GET /lookup/ip/{ip}` and `POST /lookup/ips` | 100 req/sec | 200 | | `GET /lookup/domain/{domain}` | 100 req/sec | 200 | | `GET /account/me` | 10 req/sec | 10 | | `GET /feeds/{stream}/stream` (connection establishment) | 0.5 req/sec | 20 | | `GET /feeds/{stream}/export` and `…/meta` | 2 req/sec | 60 | | `GET /feeds/{stream}/export/{snapshot_id}` (downloads) | 0.1 req/sec (≈360/hour) | 120 | Stream and export buckets are **shared across all seven feeds** for a team; fanning out to every stream doesn't multiply the budget. Excess requests get [`429 Too Many Requests`](https://docs.synthient.com/errors). Long-lived stream connections are not re-charged once established; only the initial GET counts against the connection-rate bucket. ### Rate-limit response headers Every rate-limited HTTP response carries the standard [IETF `RateLimit` headers](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/): | Header | Meaning | | --------------------- | --------------------------------------- | | `RateLimit-Limit` | Total tokens in the bucket. | | `RateLimit-Remaining` | Tokens left after this request. | | `RateLimit-Reset` | Seconds until the bucket fully refills. | Read these headers to pace your calls instead of polling for `429`s. ## Retry guidance For any `429`, `500`, or `503` response, retry with **exponential backoff and jitter**. Honour the `Retry-After` header when present. The recipe: 1. Start with a 1 second base delay. 2. On each successive failure, double the delay. 3. Cap the delay at 60 seconds. 4. Add ±25% random jitter so concurrent clients don't synchronize. 5. Give up after a reasonable number of attempts (typically 5–8) and surface the failure to the caller. **Retry with backoff** ```python title="Python" import os, random, time, requests URL = "https://api.synthient.com/api/v4/lookup/ip/8.8.8.8" HEADERS = {"x-api-key": os.environ["SYNTHIENT_API_KEY"]} def lookup_with_retry(max_attempts: int = 6): for attempt in range(max_attempts): r = requests.get(URL, headers=HEADERS, timeout=10) if r.status_code < 400: return r.json() if r.status_code in (429, 500, 502, 503, 504): retry_after = int(r.headers.get("Retry-After", 0)) base = max(retry_after, min(60, 2 ** attempt)) time.sleep(base * random.uniform(0.75, 1.25)) continue r.raise_for_status() raise RuntimeError("max retries exceeded") ``` ## Streaming reconnects Long-lived streams are closed cleanly by the server every \~30 minutes. Treat that as the normal case and reconnect immediately. If the reconnect itself fails, fall through to the same exponential backoff used for one-shot requests so a transient outage doesn't hammer the service. See the [Firehose](https://docs.synthient.com/enterprise/firehose) page for full NDJSON consumer examples in Python and Go. ## What to instrument A small amount of client-side telemetry pays for itself the first time you hit a limit: - **Per-status counters:** track `2xx`, `4xx`, `5xx` rates separately. A creeping `429` rate is the earliest sign you should batch or cache. - **Backoff distribution:** log the actual sleep durations. If you're frequently hitting the 60s cap, raise the cap or move work to off-peak. - **Quota gauge:** pull `lookup_quota.credits` periodically and alert on burn rate, not just zero. ## Next steps - [Errors](https://docs.synthient.com/errors): full status code reference and response shapes. - [Authentication](https://docs.synthient.com/authentication): scope and key management. - [Account](https://docs.synthient.com/account): programmatic quota inspection.