Skip to main content

Documentation Index

Fetch the complete documentation index at: https://vpn-docs.wxapros.com/llms.txt

Use this file to discover all available pages before exploring further.

Error envelope

Every error response uses the same JSON shape:
{
  "error": {
    "code": "rate_limited",
    "message": "Sustained rate limit exceeded — retry after 12 seconds",
    "details": {
      "retry_after_seconds": 12,
      "tier": "pro",
      "limit_rpm_60s": 3000
    }
  }
}
code is a stable machine-readable identifier; message is human-readable and may change. Always switch on code, not message.

Status codes

StatusCodeMeaningRetry?
400bad_requestMalformed request body or invalid IP formatNo — fix the request
401missing_api_keyX-API-Key header missingNo — add the header
401invalid_api_keyKey not found or revokedNo — generate a new key
403tier_lockedEndpoint requires a higher tierNo — upgrade or remove field
403scope_deniedKey doesn’t include the required scopeNo — regenerate with vpn scope
404not_foundResource doesn’t exist (rare)No
422validation_errorPydantic validation failedNo — fix payload
429rate_limitedToo many requestsYes — see headers
429quota_exhaustedMonthly quota usedYes after billing reset
500internal_errorUnexpected server errorYes — exponential backoff
503unhealthyService degradedYes — exponential backoff

Rate-limit headers

Every response includes the current rate-limit state:
X-RateLimit-Limit: 50
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1714339200
Retry-After: 12   # only on 429
X-RateLimit-Limit is your sustained-rate cap (req/s). X-RateLimit-Reset is a Unix timestamp when the current window rolls over. Retry-After appears only on 429 and is the recommended wait in seconds.

Retry strategy

For transient errors (429, 500, 503):
import httpx, time, random

def lookup_with_retry(ip, key, max_attempts=5):
    for attempt in range(max_attempts):
        resp = httpx.get(
            f"https://wxaintel.wxapros.com/api/v1/vpn/ip/{ip}",
            headers={"X-API-Key": key},
            timeout=10,
        )
        if resp.status_code == 200:
            return resp.json()
        if resp.status_code == 429:
            wait = int(resp.headers.get("Retry-After", 1))
            time.sleep(wait)
            continue
        if resp.status_code in (500, 503):
            backoff = 2 ** attempt + random.random()
            time.sleep(backoff)
            continue
        # 4xx other than 429 → don't retry
        resp.raise_for_status()
    raise RuntimeError(f"Exhausted {max_attempts} retries for {ip}")
Don’t retry 4xx errors other than 429. They indicate a problem with your request, and retrying just wastes quota.

Quota-exhausted handling

When you receive 429 quota_exhausted, your monthly quota is consumed. Until the next billing cycle:
  1. Switch to cached or aged data if you have it
  2. Drop low-value lookups (e.g., already-known clean IPs)
  3. Contact sales@whoisxmlapi.com for an emergency quota top-up
  4. Upgrade to a higher tier — pro-rated billing means same-day activation

Circuit breaking

For high-volume integrations, wrap calls in a circuit breaker so a brief outage doesn’t cascade into your application:
from circuitbreaker import circuit

@circuit(failure_threshold=5, recovery_timeout=30)
def lookup(ip, key):
    # ...
We aim for 99.5 % (Pro) / 99.9 % (Business) / 99.95 % (Enterprise) availability, but caller-side circuit breakers are still recommended.