The S4E API enforces rate limits to ensure fair usage and platform stability. This page documents default limits, per-endpoint overrides, response headers, and retry strategies.

Default Limits

Tier Requests per Minute Burst Allowance
Standard 100 20
Premium 1,000 100
Enterprise 10,000 500

The burst allowance permits short spikes above the per-minute rate. Burst tokens are replenished at the per-minute rate.

Per-Endpoint Limits

Some endpoints have tighter limits due to resource intensity:

Endpoint Limit Notes
POST /api/scan/create 10 req/min Scan creation is resource-heavy.
POST /api/findings/export 5 req/min Export generates large files.
POST /api/assets/bulk 5 req/min Bulk import processes many records.
POST /api/user/auth/login 10 req/min Prevents brute-force attacks.
GET /api/events/stream 5 connections Concurrent SSE connections.
All other endpoints Tier default See table above.

Rate Limit Headers

Every API response includes rate limit headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1714305660
Header Description
X-RateLimit-Limit Maximum requests allowed in the current window.
X-RateLimit-Remaining Requests remaining in the current window.
X-RateLimit-Reset Unix timestamp when the window resets.

429 Response

When the rate limit is exceeded, the API returns 429 Too Many Requests:

{
  "status": "error",
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded. Retry after 23 seconds.",
    "retry_after": 23
  }
}

The Retry-After header is also set:

HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1714305660

Retry Strategy

Implement exponential backoff with jitter to handle rate limits gracefully.

Python Example

import time
import random
import requests

def api_request(url, headers, max_retries=5):
    for attempt in range(max_retries):
        response = requests.get(url, headers=headers)

        if response.status_code != 429:
            return response

        retry_after = int(response.headers.get("Retry-After", 60))
        jitter = random.uniform(0, retry_after * 0.1)
        wait_time = retry_after + jitter

        print(f"Rate limited. Retrying in {wait_time:.1f}s (attempt {attempt + 1})")
        time.sleep(wait_time)

    raise Exception("Max retries exceeded")

response = api_request(
    "https://api.s4e.io/api/findings",
    headers={"Authorization": "Bearer YOUR_API_KEY"}
)

cURL with Retry

retry_count=0
max_retries=5

while [ $retry_count -lt $max_retries ]; do
  response=$(curl -s -w "\n%{http_code}" \
    "https://api.s4e.io/api/findings" \
    -H "Authorization: Bearer YOUR_API_KEY")

  http_code=$(echo "$response" | tail -1)

  if [ "$http_code" != "429" ]; then
    echo "$response" | head -n -1
    break
  fi

  retry_count=$((retry_count + 1))
  sleep_time=$((2 ** retry_count + RANDOM % 5))
  echo "Rate limited. Retrying in ${sleep_time}s..."
  sleep $sleep_time
done

SDK Handling

The S4E Python SDK handles rate limits automatically:

from s4e_sdk import S4EClient

client = S4EClient(
    api_key="YOUR_API_KEY",
    retry_on_rate_limit=True,
    max_retries=5
)

findings = client.findings.list(severity="critical")

The SDK respects the Retry-After header and uses exponential backoff with jitter by default.

Monitoring Your Usage

Check your current rate limit status:

curl -I "https://api.s4e.io/api/assets" \
  -H "Authorization: Bearer YOUR_API_KEY"

Review only the rate limit headers:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 942
X-RateLimit-Reset: 1714305660

Rate Limit Exemptions

The following are not counted against your rate limit:

  • Webhook deliveries from S4E to your endpoint.
  • SSE event stream messages (the connection itself counts, but individual events do not).
  • Health check endpoint (GET /api/health).

Requesting Higher Limits

If your integration requires higher rate limits:

  1. Review your API usage patterns to ensure you are not making unnecessary calls.
  2. Implement caching for frequently accessed data.
  3. Use bulk endpoints where available (e.g., POST /api/assets/bulk).
  4. Contact S4E support or your account manager to discuss Premium or Enterprise tier upgrades.

Tip

Use the If-Modified-Since header on GET requests to avoid fetching unchanged data. The API returns 304 Not Modified without consuming a rate limit token when the data has not changed.

Best Practices

Practice Recommendation
Monitor headers Track X-RateLimit-Remaining to preemptively slow down.
Implement backoff Use exponential backoff with jitter, not fixed delays.
Cache responses Cache asset lists and finding counts to reduce API calls.
Batch requests Use bulk endpoints for asset import and finding export.
Stagger requests Distribute API calls evenly rather than bursting.

Next Steps