Back to Blog
Technical7 min readNov 10, 2024

API Security: Protecting Your Integration Points

How to securely integrate a threat intelligence API: key management, TLS, rate limiting, caching, monitoring and gateway patterns that hold up in production.

B

Bob Adams

Threat Analyst at Revealer

Introduction

Wiring a threat intelligence API into your security stack adds real capability, and it also adds an integration point that an attacker can target. Keys leak, responses go unvalidated, retries turn into outages. The rest of this post is the production checklist: secrets, TLS, validation, rate limits, and what to watch once the integration is live.

API Authentication Best Practices

API Key Management

Your API keys are as sensitive as passwords. Treat them that way.

Do:

  • Store keys in a secrets manager (HashiCorp Vault, AWS Secrets Manager)
  • Rotate keys on a schedule, quarterly at minimum
  • Use separate keys per environment
  • Monitor key usage for anomalies

Don't:

  • Commit keys to version control
  • Share keys over email or chat
  • Reuse one key across multiple applications
  • Leave keys in plaintext configuration files

Implementation Example

# Good: loaded from environment or a secrets manager
import os
from your_secrets_manager import get_secret

api_key = get_secret("revealer_api_key")
# or
api_key = os.environ.get("REVEALER_API_KEY")

# Bad: hardcoded key
api_key = "sk_live_abc123..."  # never do this

Secure Communication

TLS Requirements

All API traffic should use TLS 1.2 or higher:

  • Verify server certificates; do not disable verification to fix a local error
  • Use modern cipher suites
  • Consider certificate pinning for high-security applications

Request and Response Validation

  • Validate every API response before processing it
  • Implement explicit error handling rather than a bare except
  • Never trust response structure implicitly
import requests

def query_intelligence(query):
    try:
        response = requests.get(
            "https://api.revealer.us/v1/resolve",
            headers={"Authorization": f"Bearer {api_key}"},
            params={"query": query},
            timeout=30
        )
        response.raise_for_status()

        data = response.json()

        # Validate expected structure
        if not isinstance(data, dict):
            raise ValueError("Unexpected response format")

        return data

    except requests.exceptions.RequestException as e:
        # Log and handle appropriately
        log_error(f"API request failed: {e}")
        raise

Note the explicit timeout. A request without one can hang until the worker pool is exhausted, which turns a slow upstream into your outage.

Rate Limiting and Resilience

Respecting Rate Limits

A 429 is not a bug. Honor it, or you will get locked out and take the rest of your queue with you:

import time
from functools import wraps

def rate_limit_handler(max_retries=3):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except RateLimitError as e:
                    if attempt < max_retries - 1:
                        wait_time = e.retry_after or (2 ** attempt)
                        time.sleep(wait_time)
                    else:
                        raise
        return wrapper
    return decorator

Honor the server's Retry-After value when one is supplied, and fall back to exponential backoff only when it is absent.

Circuit Breaker Pattern

When the upstream is down, stop calling it. An open circuit is cheaper than a thread pool full of hung requests:

  • Track failure rates over a rolling window
  • Open the circuit once the threshold is exceeded
  • Probe periodically to test for recovery
  • Define fallback behavior for the open state

Data Handling

Minimizing Data Retention

  • Query only what you need for the decision at hand
  • Set and enforce retention policies on returned data
  • Encrypt sensitive results at rest
  • Log access for audit purposes

Caching Considerations

Cache cuts latency. It also leaves a second copy of someone else's credentials sitting on disk:

  • Cache only non-sensitive, relatively static data
  • Set TTLs that match how fast the underlying data changes
  • Secure the cache store with the same controls as primary storage
  • Flush caches on security events

Monitoring and Alerting

What to Monitor

  • API response times
  • Error rates by status code
  • Unusual query patterns
  • Key usage broken down by application and environment

Alert Conditions

  • Sudden spike in API errors
  • Unusual query volumes
  • Requests originating from unexpected IPs
  • Repeated authentication failures

Authentication failures on a machine-to-machine integration deserve a real alert. A working key does not start failing on its own.

Architecture Patterns

API Gateway Pattern

Put one gateway in front of the vendor. Applications talk to that, not to the vendor directly:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ Application │────▶│ API Gateway │────▶│ Revealer.US │
│      A      │     │             │     │     API     │
└─────────────┘     │  - Auth     │     └─────────────┘
                    │  - Logging  │
┌─────────────┐     │  - Caching  │
│ Application │────▶│  - Rate     │
│      B      │     │    Limiting │
└─────────────┘     └─────────────┘

Benefits:

  • Centralized key management, so rotation touches one place
  • Unified logging and monitoring
  • Consistent rate limiting across consumers
  • Simpler application code

Async Processing

If the lookup volume would stall a request path, queue it:

  • Queue intelligence requests
  • Process them asynchronously
  • Return results via callbacks or polling
  • Add dead-letter queues so failures are visible

Revealer.US API Integration

Quick Start

import requests

def revealer_lookup(query, query_type="auto"):
    response = requests.get(
        "https://api.revealer.us/v1/resolve",
        headers={
            "Authorization": f"Bearer {REVEALER_API_KEY}",
            "Content-Type": "application/json"
        },
        params={
            "query": query,
            "type": query_type
        },
        timeout=30
    )
    response.raise_for_status()
    return response.json()

# Example usage
result = revealer_lookup("[email protected]")

Webhook Integration

If you want alerts as new exposures land, subscribe to webhooks:

  • Configure the endpoint in your dashboard
  • Verify webhook signatures before processing anything
  • Process events asynchronously
  • Implement idempotency so redelivery is safe

Conclusion

Keys stay out of the repo. Responses get validated. 429s and outages fail closed, not in a retry storm. Then you watch the integration the way you watch any other dependency. Miss those and the API is just another hole; hit them and it is a lookup you can actually ship.

Frequently asked questions

How often should API keys be rotated? Quarterly is a reasonable floor for a production integration, and immediately after any suspected exposure or when someone with key access leaves. Rotation is much easier if keys live in a secrets manager and only one component reads them.

Should each environment have its own API key? Yes. Separate keys for development, staging and production let you revoke a leaked key without taking production down, and they make usage monitoring meaningful.

What is the safest way to store an API key? A dedicated secrets manager with audit logging. Environment variables are acceptable when the platform injects them at runtime from such a store. Plaintext config files committed to a repository are the common failure case.

How should I handle a 429 rate limit response? Back off and retry using the Retry-After header when the server sends one, exponential backoff when it does not, and cap the retry count. Never retry immediately in a tight loop.

Do I need a circuit breaker for a single API dependency? If a request path blocks a user-facing operation, yes. Without one, an upstream slowdown propagates into your own service through exhausted connections and threads.

Is it safe to cache intelligence API responses? Only with deliberate limits. Cache low-sensitivity, slow-changing data, apply short TTLs, secure the cache as you would primary storage, and flush it during a security event.


Full API reference lives in the documentation. Get started when you are ready for a key.

Get started

Ready to check your exposure?

Create a free account and search live sources and known breach datasets.

Create account