API reference
Core endpoints
Copy/paste-ready examples + the headers you need in production. For the full schema, use OpenAPI/Swagger.
Authentication
Send your API key (starts with prdx_) in the X-API-Key header. The API also accepts Authorization: Bearer prdx_... if a bearer header fits your HTTP client better — both authenticate identically.
Missing or invalid keys return 401. Quotas are enforced per account: usage from all of your active keys counts against the same daily and monthly limits.
Plan limits
Every plan has four limits: requests per day, requests per month, requests per second, and a max batch size for /check/ip/bulk. Daily quotas reset at 00:00 UTC; monthly quotas reset on the 1st.
| Plan | Per day | Per month | Per second | Bulk batch |
|---|---|---|---|---|
| Free | 1,000 | 5,000 | 2 | 10 |
| Hobby | 2,000 | 25,000 | 10 | 25 |
| Starter | 5,000 | 100,000 | 20 | 50 |
| Pro | 15,000 | 300,000 | 40 | 100 |
| Business | 40,000 | 1,000,000 | 80 | 100 |
| Growth | 120,000 | 3,000,000 | 150 | 100 |
| Scale | 400,000 | 10,000,000 | 300 | 100 |
| Enterprise | 1,000,000 | 25,000,000 | 1,000 | 100 |
POST /api/v1/check/ip
curl -sS https://predax.io/api/v1/check/ip \
-H "X-API-Key: prdx_..." \
-H "Content-Type: application/json" \
-d '{"ip":"8.8.8.8"}'Optional enrichment: send user_id, context, signals, and fingerprint to enable best-effort residential proxy suspicion.
Screen an email in the same call: add email and the response gains an email block and a combined_risk_score. It costs no extra quota, and classification.risk_score is left untouched so existing thresholds keep meaning exactly what they meant before.
Request body (optional fields)
{
"ip": "1.2.3.4",
"user_id": "user_123",
"context": { "user_timezone": "Europe/Dublin" },
"signals": { "distinct_user_agents_24h": 7, "requests_24h": 120 },
"fingerprint": {
"timezone": "Europe/Dublin",
"languages": ["en-IE", "en"],
"webrtc_local_ip": "192.168.1.100"
},
"scoring": { "vpn": 30, "datacenter": 50 },
"email": "[email protected]"
}email— screen an email address alongside the IP. Adds theemailblock described below pluscombined_risk_score, which ismax(ip_risk, email_risk). Free — it does not count as a second request.user_id— a stable identifier for your end user. Lets Predax correlate traffic across requests, which feeds the residential-proxy behavioral analysis.context.user_timezone/fingerprint.timezone— browser timezone; a mismatch with the IP's geolocated timezone is a proxy/VPN tell.signals— your own observed counts for this IP over the last 24h (best-effort inputs to the residential-proxy signal).scoring— per-request weight overrides (0–100) forvpn/proxy/tor/datacenter/crawler/country/timezone, if the defaults don't fit your traffic. Saved account-level weights (dashboard → Scoring) apply automatically; a per-request override wins.
The response always includes classification.residential_proxy_score (0–1) and classification.is_residential_proxy_suspect (true at score ≥ 0.5, which also appends "residential_proxy_suspect" to flags) — but the signal is only meaningful when you send the enrichment fields above; without them it stays near 0.
Response shape (single IP)
The response is a JSON object. All nested objects (classification, network, location) are always present; cloud, crawler, history, and meta are optional and only included when relevant.
{
"ip": "8.8.8.8",
"version": 4,
"classification": {
"risk_score": 15,
"risk_level": "minimal",
"is_vpn": false,
"is_proxy": false,
"is_tor": false,
"is_datacenter": true,
"is_hosting": false,
"is_crawler": false,
"is_private_relay": false,
"is_warp": false,
"is_malicious": false,
"is_residential": false,
"is_mobile": false,
"flags": ["datacenter"],
"confidence": { "datacenter": 0.95, "overall": 0.95 },
"reasons": [
{ "flag": "datacenter", "source": "datacenter_feed" }
]
},
"network": {
"asn": 15169,
"as_name": "GOOGLE",
"cidr": "8.8.8.0/24",
"network_type": "hosting",
"ptr": "dns.google"
},
"location": {
"country_code": "US",
"country_name": "United States",
"city": "Mountain View",
"latitude": 37.4056,
"longitude": -122.0775,
"timezone": "America/Los_Angeles"
},
"cloud": {
"provider": "google",
"service": "Public DNS",
"cidr": "8.8.8.0/24"
},
"history": {
"first_seen": "2026-01-10T12:34:56",
"last_seen": "2026-05-04T09:00:00",
"times_seen": 50
},
"meta": {
"request_id": "req_abc123",
"cached": true,
"cache_ttl_seconds": 3600,
"processed_ms": 4.2
}
}Top-level boolean flags (is_vpn, is_tor, etc.) live under classification — the response has no flat top-level flags. Read result.classification.is_vpn, not result.is_vpn.
risk_levelis derived fromrisk_score:minimal(0–19),low(20–49),medium(50–79),high(80–100).reasonsentries are{flag, source, detail?}and explain why each flag was set.confidencekeys only appear for signals that fired.meta.cachedtells you the verdict came from our server-side cache. Results are cached with a TTL based on how stable the classification is: datacenter 24h, VPN 12h, Tor 6h, proxy 4h, residential/unknown 1h (meta.cache_ttl_seconds).
There is also POST /api/v1/check/ip/details, which accepts the same request body and returns the same response shape — use it if you prefer a separate route for detailed lookups.
POST /api/v1/check/ip/bulk
Synchronous batch check. The max batch size depends on your plan (Free 10, Hobby 25, Starter 50, Pro and above 100 — see the plan limits table). Exceeding your plan's batch size returns 413 with an X-Bulk-Size-Limit header. Use it when you have a list of IPs in hand (log analysis, batch review) rather than looping over the single endpoint — one bulk call counts as a single request against the per-second limit.
curl -sS https://predax.io/api/v1/check/ip/bulk \
-H "X-API-Key: prdx_..." \
-H "Content-Type: application/json" \
-d '{"ips":["8.8.8.8","1.1.1.1"]}'The bulk response is a top-level JSON array, one element per requested IP, each with the same shape as the single-IP response above:
[
{ "ip": "8.8.8.8", "version": 4, "classification": { "risk_score": 15, ... }, "network": { ... }, "location": { ... } },
{ "ip": "1.1.1.1", "version": 4, "classification": { "risk_score": 10, ... }, "network": { ... }, "location": { ... } }
]Bulk requests consume N units where N = ips.length. The 5-minute repeat-lookup forgiveness (see “Headers you should read”) applies to the single-IP endpoint only — bulk bills every IP.
Async bulk jobs — POST /api/v1/bulk-jobs
For lists larger than one bulk request (up to 5,000 IPs per job), create an asynchronous job instead of chunking sync calls yourself. The job is processed in the background; you poll for status and fetch results when it finishes. Quota is charged up-front for the valid IPs in the job.
# Create a job (returns 201 with {"id", "status", "total_count"})
curl -sS https://predax.io/api/v1/bulk-jobs \
-H "X-API-Key: prdx_..." \
-H "Content-Type: application/json" \
-d '{"name":"july-signups","ips":["8.8.8.8","1.1.1.1"]}'
# Poll status
curl -sS https://predax.io/api/v1/bulk-jobs/123 -H "X-API-Key: prdx_..."
# Fetch results when status is "succeeded"
curl -sS https://predax.io/api/v1/bulk-jobs/123/results -H "X-API-Key: prdx_..."GET /api/v1/bulk-jobs— list your jobsGET /api/v1/bulk-jobs/{id}— job status + progress (processed_count/total_count)GET /api/v1/bulk-jobs/{id}/results— per-IP results (invalid IPs carry anerrorfield)POST /api/v1/bulk-jobs/{id}/cancel— cancel a queued/running job- More than 5,000 IPs returns
413
GET /api/v1/billing/usage
Returns monthly usage summary plus a daily quota object.
curl -sS https://predax.io/api/v1/billing/usage \ -H "X-API-Key: prdx_..."
For a daily time-series breakdown, use GET /api/v1/billing/usage/analytics.
POST /api/v1/validate/email
Screens an address against 8,300+ disposable-email domains, checks MX records, and scores the mailbox name. Use it on its own, or pass email to /check/ip to get the same block alongside an IP verdict in a single call.
curl -sS https://predax.io/api/v1/validate/email \
-H "X-API-Key: prdx_..." \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]"}'{
"email": "[email protected]",
"domain": "guerrillamail.info",
"valid": true,
"deliverable": true,
"is_disposable": true,
"is_free_provider": false,
"is_role_account": true,
"is_gibberish": false,
"risk_score": 70,
"reasons": [{ "flag": "disposable_email", "source": "domain" }]
}deliverable—truethe domain accepts mail,falseit definitively cannot,nullwe could not determine it (DNS timeout, orcheck_mx: false). Null never adds risk — an unreachable resolver must not make a real address look fake.is_disposable— matched against a domain list refreshed weekly from a public-domain source. Contributes 70.is_role_account—info@,sales@,support@and similar. Reported only; it contributes nothing to the score, because that is how legitimate businesses buy things.is_gibberish— the mailbox name looks machine-generated. Tuned toward false negatives and capped at 25, below the “risky” line, so it can never block on its own.check_mx— defaults totrue. Cached per domain for an hour behind a 2-second ceiling. Setfalseto skip DNS entirely.
POST /api/v1/validate/phone
E.164 validation (best-effort).
curl -sS https://predax.io/api/v1/validate/phone \
-H "X-API-Key: prdx_..." \
-H "Content-Type: application/json" \
-d '{"phone":"+14155552671"}'Headers you should read
- Request tracing:
X-Request-ID - Daily quota:
X-Quota-Limit-Day,X-Quota-Used-Today,X-Quota-Remaining-Today,X-Quota-Reset-Daily - Monthly quota:
X-Quota-Limit-Month,X-Quota-Used-Month,X-Quota-Remaining-Month,X-Quota-Month - Per-second:
X-RateLimit-Limit-Per-Second,X-RateLimit-Remaining-This-Second - Billing:
X-Quota-Billed—1if this request counted against your quota,0if it was free. A repeat check of the same IP with the same key within 5 minutes is not billed, so client-side retries and page-refresh traffic don't double-charge you. (Single-IP endpoint only; bulk bills every IP.) - On
402/429/413:X-Upgrade-UrlandX-Suggested-Plantell you the smallest plan that clears the limit you hit.
Errors and retries
401: missing/invalid API key — don't retry; fix the key.402: daily or monthly quota exceeded. Quota headers are still returned, anddetailnames the exact limit and plan. Don't retry until the reset time inX-Quota-Reset-Daily(daily limits reset at 00:00 UTC).413: bulk batch larger than your plan allows (X-Bulk-Size-Limithas your cap), or an async job over 5,000 IPs. Split the batch — retrying unchanged will fail again.422: invalid request body (e.g. a malformed IP). The error lists which field failed — don't retry unchanged.429: per-second rate limit exceeded (retry with backoff — the limit is per plan, from 2/s on Free to 1,000/s on Enterprise).5xx: our fault — retry with backoff. Failed requests are never billed against your quota.
For reliability, use short timeouts (1–3s), retry only 429/5xx with exponential backoff, and log X-Request-ID — include it when contacting support and we can find the exact request.
Public endpoints (no auth)
GET /api/v1/public/statusGET /api/v1/public/my-ipGET /api/v1/public/my-country