Best Practices

Production Optimization Guide

Advanced patterns, performance optimization, and reliability best practices for Predax integration.

Caching Strategy

Caching IP checks reduces API calls, improves response time, and saves on quota usage.

What Predax already does for you:

  • Server-side result cache. Verdicts are cached on our side with a TTL based on how stable the classification is (datacenter 24h, VPN 12h, Tor 6h, proxy 4h, residential 1h). meta.cached and meta.cache_ttl_seconds tell you when a response was served from it. This makes repeat lookups fast, but they still count as requests — which is why a client-side cache matters.
  • 5-minute repeat forgiveness. Re-checking the same IP with the same key within 5 minutes is not billed (the X-Quota-Billed header is 0). This absorbs accidental double-checks and retries — but it is a safety net, not a caching strategy. A working client-side cache should mean you rarely repeat a lookup at all.
  • Errors are never billed. Only 2xx responses count against your quota.

Recommended Cache TTL:

  • IP Risk Scores: 24 hours (risk profiles change slowly)
  • Geolocation: 7 days (rarely changes)
  • ASN/Network: 30 days (very stable)
  • High-Risk IPs: 6 hours (more dynamic)

Example: Redis Caching (Python)

import redis
import json
from datetime import timedelta

redis_client = redis.Redis(host='localhost', port=6379, db=0)

def check_ip_with_cache(ip_address):
    cache_key = f"ip_check:{ip_address}"

    # Try cache first
    cached = redis_client.get(cache_key)
    if cached:
        return json.loads(cached)

    # Cache miss - call API
    result = requests.post(
        'https://predax.io/api/v1/check/ip',
        headers={'X-API-Key': API_KEY},
        json={'ip': ip_address},
        timeout=2.0
    ).json()

    # Cache for 24 hours
    redis_client.setex(
        cache_key,
        timedelta(hours=24),
        json.dumps(result)
    )

    return result

Example: In-Memory Cache (Node.js)

const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 86400 }); // 24 hours

async function checkIpWithCache(ipAddress) {
  const cacheKey = `ip_check:${ipAddress}`;

  // Try cache first
  const cached = cache.get(cacheKey);
  if (cached) {
    return cached;
  }

  // Cache miss - call API
  const response = await fetch('https://predax.io/api/v1/check/ip', {
    method: 'POST',
    headers: {
      'X-API-Key': API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ ip: ipAddress }),
    signal: AbortSignal.timeout(2000)
  });

  const result = await response.json();

  // Cache result
  cache.set(cacheKey, result);

  return result;
}

Robust Error Handling

Handle API errors gracefully to ensure your application remains reliable even when issues occur.

Fail-Safe Principle:

When the API is unavailable or quota is exceeded, fail open (allow the request) rather than blocking all users. False negatives are better than false positives.

Example: Complete Error Handling (Python)

import requests
import logging
from requests.exceptions import Timeout, RequestException

logger = logging.getLogger(__name__)

def check_ip_safe(ip_address, user_id=None):
    """
    Check IP with comprehensive error handling.
    Returns (result_dict, is_cached_or_default)
    """
    try:
        response = requests.post(
            'https://predax.io/api/v1/check/ip',
            headers={
                'X-API-Key': API_KEY,
                'Content-Type': 'application/json'
            },
            json={'ip': ip_address, 'user_id': user_id},
            timeout=2.0
        )

        # Log request ID for debugging
        request_id = response.headers.get('X-Request-ID')
        logger.info(f"IP check for {ip_address}, request_id={request_id}")

        if response.status_code == 200:
            return response.json(), False

        elif response.status_code == 429:
            # Rate limited - wait and retry once
            logger.warning(f"Rate limited for {ip_address}, retrying...")
            time.sleep(1)
            return check_ip_safe(ip_address, user_id)

        elif response.status_code == 402:
            # Quota exceeded - fail open
            logger.error(f"Quota exceeded, failing open for {ip_address}")
            return get_default_result(ip_address), True

        elif response.status_code == 401:
            # Invalid API key - critical error
            logger.critical("Invalid API key!")
            return get_default_result(ip_address), True

        else:
            # Other error - fail open
            logger.error(f"API error {response.status_code} for {ip_address}")
            return get_default_result(ip_address), True

    except Timeout:
        logger.warning(f"Timeout checking {ip_address}, failing open")
        return get_default_result(ip_address), True

    except RequestException as e:
        logger.error(f"Request exception for {ip_address}: {e}")
        return get_default_result(ip_address), True

def get_default_result(ip_address):
    """Return safe default when API unavailable"""
    return {
        'ip': ip_address,
        'classification': {
            'risk_score': 0,
            'risk_level': 'unknown',
            'is_proxy': False,
            'is_vpn': False,
            'is_tor': False,
            'is_datacenter': False
        },
        'meta': {
            'request_id': 'fallback',
            'note': 'API unavailable - using defaults'
        }
    }

Retry Logic with Exponential Backoff

Implement smart retries for transient failures (429, 5xx) without overwhelming the API.

Example: Exponential Backoff (Python)

import time
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def create_session_with_retries():
    """Create session with automatic retry logic"""
    session = requests.Session()

    # Retry on 429 (rate limit) and 5xx (server errors)
    retry_strategy = Retry(
        total=3,
        status_forcelist=[429, 500, 502, 503, 504],
        backoff_factor=1,  # 1s, 2s, 4s
        allowed_methods=["POST"]
    )

    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount("https://", adapter)
    session.mount("http://", adapter)

    return session

# Usage
session = create_session_with_retries()
response = session.post(
    'https://predax.io/api/v1/check/ip',
    headers={'X-API-Key': API_KEY},
    json={'ip': '8.8.8.8'},
    timeout=2.0
)

Example: Manual Retry (Node.js)

async function checkIpWithRetry(ipAddress, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch('https://predax.io/api/v1/check/ip', {
        method: 'POST',
        headers: {
          'X-API-Key': API_KEY,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ ip: ipAddress }),
        signal: AbortSignal.timeout(2000)
      });

      if (response.ok) {
        return await response.json();
      }

      // Retry on rate limit or server errors
      if (response.status === 429 || response.status >= 500) {
        if (attempt < maxRetries) {
          const backoffMs = Math.pow(2, attempt - 1) * 1000; // 1s, 2s, 4s
          console.log(`Retry ${attempt}/${maxRetries} after ${backoffMs}ms`);
          await new Promise(resolve => setTimeout(resolve, backoffMs));
          continue;
        }
      }

      throw new Error(`API error: ${response.status}`);

    } catch (error) {
      if (attempt === maxRetries) {
        throw error;
      }
    }
  }
}

Client-Side Rate Limiting

Prevent hitting rate limits by implementing client-side throttling. The per-second limit is per plan — from 2 requests/second on Free up to 1,000/second on Enterprise — and every response reports it in X-RateLimit-Limit-Per-Second / X-RateLimit-Remaining-This-Second, so read your actual limit from the headers rather than hardcoding a number.

Example: Token Bucket (Python)

import time
import threading

class RateLimiter:
    def __init__(self, max_requests_per_second):
        self.max_requests = max_requests_per_second
        self.tokens = max_requests_per_second
        self.last_update = time.time()
        self.lock = threading.Lock()

    def acquire(self):
        """Wait until a token is available"""
        with self.lock:
            now = time.time()
            elapsed = now - self.last_update

            # Replenish tokens
            self.tokens = min(
                self.max_requests,
                self.tokens + elapsed * self.max_requests
            )
            self.last_update = now

            if self.tokens >= 1:
                self.tokens -= 1
                return True

            # Wait for next token
            wait_time = (1 - self.tokens) / self.max_requests
            time.sleep(wait_time)
            self.tokens = 0
            return True

# Usage — set this to your plan's X-RateLimit-Limit-Per-Second value
rate_limiter = RateLimiter(max_requests_per_second=2)

def check_ip_rate_limited(ip_address):
    rate_limiter.acquire()
    return check_ip_safe(ip_address)

Batch Processing Optimization

Use the bulk endpoint to check multiple IPs efficiently. The max batch size is per plan (Free 10, Hobby 25, Starter 50, Pro and above 100); an oversized batch returns 413 with your cap in the X-Bulk-Size-Limit header. One bulk call counts as a single request against the per-second limit but consumes one quota unit per IP.

When to Use Bulk:

  • • Processing historical data
  • • Batch fraud review
  • • Log analysis
  • • Warming up caches

For lists longer than a few batches, prefer the async job API — POST /api/v1/bulk-jobs accepts up to 5,000 IPs per job, processes them in the background, and lets you poll status and fetch results. See the API reference.

Example: Batch Processing (Python)

def check_ips_in_batches(ip_list, batch_size=100):
    """
    Check multiple IPs using bulk endpoint.
    Set batch_size to your plan's bulk cap
    (Free 10, Hobby 25, Starter 50, Pro+ 100).
    """
    results = []

    for i in range(0, len(ip_list), batch_size):
        batch = ip_list[i:i + batch_size]

        try:
            response = requests.post(
                'https://predax.io/api/v1/check/ip/bulk',
                headers={
                    'X-API-Key': API_KEY,
                    'Content-Type': 'application/json'
                },
                json={'ips': batch},
                timeout=5.0
            )

            if response.status_code == 200:
                batch_results = response.json()
                results.extend(batch_results)
            else:
                logger.error(f"Bulk check failed: {response.status_code}")

        except Exception as e:
            logger.error(f"Bulk check exception: {e}")

        # Rate limiting between batches
        time.sleep(0.1)

    return results

# Usage
all_ips = ["1.1.1.1", "8.8.8.8", ...] # Your IP list
results = check_ips_in_batches(all_ips)

Monitoring & Alerting

Track API health, quota usage, and error rates in production.

Key Metrics to Monitor:

API Response Time

Track p50, p95, p99 latencies. Alert if p95 > 2s

Error Rate

Track 4xx and 5xx errors. Alert if > 1%

Quota Usage

Alert when 80% of daily or monthly quota used

Cache Hit Rate

Track cache effectiveness. Aim for > 60%

Example: Prometheus Metrics (Python)

from prometheus_client import Counter, Histogram

# Define metrics
api_requests_total = Counter(
    'predax_api_requests_total',
    'Total Predax API requests',
    ['status_code']
)

api_request_duration = Histogram(
    'predax_api_request_duration_seconds',
    'Predax API request duration'
)

quota_remaining = Gauge(
    'predax_quota_remaining',
    'Remaining quota',
    ['period']  # 'daily' or 'monthly'
)

# Track in API calls
@api_request_duration.time()
def check_ip_monitored(ip_address):
    try:
        response = requests.post(...)

        # Record metrics
        api_requests_total.labels(
            status_code=response.status_code
        ).inc()

        # Track quota
        remaining_today = int(
            response.headers.get('X-Quota-Remaining-Today', 0)
        )
        quota_remaining.labels(period='daily').set(remaining_today)

        return response.json()

    except Exception as e:
        api_requests_total.labels(status_code='error').inc()
        raise

Security Best Practices

Protect your API keys and implement secure integration patterns.

🔒 Never Expose API Keys

Never commit API keys to version control or expose them in client-side code. Use environment variables or secrets managers.

🔒 Use Backend Proxies

Always call Predax from your backend, never directly from browsers or mobile apps. This prevents key exposure and rate limit abuse.

🔒 Rotate Keys Periodically

Generate new API keys every 90 days and update your application. Keep old keys active briefly for zero-downtime rotation.

🔒 Use HTTPS Only

Always use https:// endpoints. Never use http:// in production.

Performance Optimization Tips

⚡ Run Checks Asynchronously

Don't block user requests waiting for IP checks. Use background jobs or async workers.

⚡ Check Only When Necessary

Only check IPs on sensitive actions (login, signup, checkout). Don't check every page view.

⚡ Use Connection Pooling

Reuse HTTP connections with session objects. Don't create new connections for each request.

⚡ Pre-warm Caches

For high-traffic applications, pre-populate your cache with known IPs using bulk checks.

Quick Reference Checklist

Before Production:

  • ✓ Implement caching (24h TTL)
  • ✓ Add timeout (2-3s)
  • ✓ Add retry logic with backoff
  • ✓ Implement fail-open error handling
  • ✓ Log X-Request-ID headers

Monitoring Setup:

  • ✓ Track API response time
  • ✓ Monitor error rates
  • ✓ Alert on 80% quota usage
  • ✓ Track cache hit rate
  • ✓ Monitor risk score distribution