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 or domain lookup consumes credits from your plan's monthly allotment. Inspect the remaining balance and reset cadence on /account/me:
{
"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.
Out of credits
When credits reaches zero, lookup endpoints return 402 Payment Required. 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 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} | 20 req/min | 20 |
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 eight feeds for a team; fanning out to every stream doesn't multiply the budget.
Excess requests get 429 Too Many Requests. 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:
| 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 429s.
Retry guidance
Retry policy depends on the endpoint. The SDKs use these rules:
- IP, batch, and domain lookups, plus health: retry only
429responses that carryRetry-After. A server error or lost connection can leave a lookup's outcome unknown, so automatically repeating it risks another charge. - Account and feed requests: also retry
500,502,503,504, and transient connection failures. Errors while reading an established stream or download are surfaced to the caller. 429withoutRetry-After: stop. A missing header indicates an exhausted monthly request budget or too many concurrent streams; retrying in a loop will not resolve either condition.
The SDKs allow two retries by default, for three total attempts. Start the backoff at 1 second, double it up to a 30-second base, then apply ±25% jitter. The actual wait is the larger of the jittered backoff and Retry-After, so jitter never shortens the server's requested delay. Retry-After accepts seconds or an HTTP date. If it asks for more than 60 seconds, surface the error instead of retrying early.
This HTTP lookup example follows the same policy:
Retry with backoff
import os
import random
import re
import time
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
import requests
URL = "https://api.synthient.com/api/v4/lookup/ip/8.8.8.8"
HEADERS = {"x-api-key": os.environ["SYNTHIENT_API_KEY"]}
def parse_retry_after(value):
if value is None:
return None
value = value.strip()
if re.fullmatch(r"-?[0-9]+", value):
return max(0.0, float(value))
try:
when = parsedate_to_datetime(value)
except (TypeError, ValueError, OverflowError):
return None
if when.tzinfo is None:
when = when.replace(tzinfo=timezone.utc)
return max(0.0, (when - datetime.now(timezone.utc)).total_seconds())
def lookup_with_retry(max_attempts: int = 3):
if max_attempts < 1:
raise ValueError("max_attempts must be positive")
for attempt in range(max_attempts):
with requests.get(URL, headers=HEADERS, timeout=10) as response:
if response.status_code < 400:
return response.json()
retry_after = parse_retry_after(response.headers.get("Retry-After"))
if (
response.status_code != 429
or retry_after is None
or retry_after > 60
or attempt == max_attempts - 1
):
response.raise_for_status()
base = min(30, 2 ** min(attempt, 5))
delay = max(base * random.uniform(0.75, 1.25), retry_after)
time.sleep(delay)
Streaming reconnects
Long-lived streams are closed cleanly by the server every ~30 minutes. Reconnect immediately after a clean close. If establishing the next connection fails, apply the account/feed retry policy above. Stop on permanent errors and on 429 without Retry-After. A failure while reading an established stream is surfaced by the SDK; there is no resume cursor, so reconnecting can miss events.
See the 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,5xxrates separately. A creeping429rate is the earliest sign you should batch or cache. - Backoff distribution: record actual wait durations. Frequent long waits indicate that traffic needs pacing; preserve
Retry-Afteras the minimum wait. - Quota gauge: pull
lookup_quota.creditsperiodically and alert on burn rate, not just zero.
Next steps
- Errors: full status code reference and response shapes.
- Authentication: scope and key management.
- Account: programmatic quota inspection.