Overview

The OpenFX v1 API enforces rate limits to protect platform stability and ensure fair access for all consumers. Every API response includes rate limit headers so you can monitor your usage proactively and avoid hitting limits.

Response Headers

Every successful API response includes these rate limit headers:
HeaderTypeDescription
X-RateLimit-LimitintegerMaximum number of requests allowed in the current rate-limit window.
X-RateLimit-RemainingintegerNumber of requests remaining in the current window.
X-RateLimit-ResetintegerSeconds until the current rate-limit window resets.
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 997
X-RateLimit-Reset: 45
Content-Type: application/json
In this example, the caller has made 3 requests out of a 1000-request window, and the window resets in 45 seconds.
X-RateLimit-Reset is the number of seconds until the window resets, not a Unix timestamp. When X-RateLimit-Remaining reaches 0, wait X-RateLimit-Reset seconds before making additional requests.

Rate Limit Exceeded (429)

When the rate limit is exceeded, the API returns a 429 Too Many Requests response with a Retry-After header:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 30
Content-Type: application/json
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "Too many requests. Please retry after the delay indicated in the Retry-After header.",
    "status": 429,
    "requestId": "req_01953e1a5f4b7b10",
    "retryable": true
  }
}
The Retry-After header value is in seconds — wait at least that many seconds before retrying.

Handling Rate Limits

Proactive Monitoring

The best strategy is to avoid hitting the limit by monitoring the remaining request count:
import time
import requests

class RateLimitedClient:
    """API client that proactively respects rate limits."""

    def __init__(self, base_url: str, headers: dict):
        self.base_url = base_url
        self.headers = headers
        self.remaining = None
        self.reset_seconds = None

    def request(self, method: str, path: str, **kwargs):
        # Proactive throttle: if we know we're near the limit, slow down
        if self.remaining is not None and self.remaining < 10:
            print(f"Rate limit low ({self.remaining} remaining), "
                  f"waiting {self.reset_seconds}s for window reset...")
            time.sleep(self.reset_seconds)

        response = requests.request(
            method,
            f"{self.base_url}{path}",
            headers=self.headers,
            **kwargs,
        )

        # Update rate limit state from response headers
        self.remaining = int(response.headers.get("X-RateLimit-Remaining", 1000))
        self.reset_seconds = int(response.headers.get("X-RateLimit-Reset", 60))

        # Handle 429 with automatic retry
        if response.status_code == 429:
            retry_after = int(response.headers.get("Retry-After", 30))
            print(f"Rate limited. Waiting {retry_after}s...")
            time.sleep(retry_after)
            return self.request(method, path, **kwargs)

        return response


# Usage
client = RateLimitedClient(
    base_url="https://sandbox.api.openfx.com/v1",
    headers=auth_headers,
)
response = client.request("GET", "/payments", params={"limit": 50})

Reactive Backoff

If you receive a 429, always respect the Retry-After header:
if response.status_code == 429:
    retry_after = int(response.headers.get("Retry-After", 30))
    time.sleep(retry_after)
    # Retry the request
Do not ignore 429 responses or retry immediately. Repeatedly hitting the rate limit without backing off may result in extended throttling or temporary suspension.

Best Practices

Monitor X-RateLimit-Remaining proactively. Track the remaining count and slow down before you hit zero. This avoids the latency penalty of a 429 response and retry cycle.
Always respect the Retry-After header. When you receive a 429, wait the exact number of seconds specified. Do not use a shorter delay.
Use pagination efficiently. When fetching large datasets, use limit=200 (the maximum) to minimize the number of API calls. See Pagination for details.
Batch related operations. If your workflow creates multiple related resources (e.g., a counterparty and a payment method), structure your code to minimize unnecessary list or get calls between mutations.
Cache responses when appropriate. For data that changes infrequently (e.g., asset pairs from GET /fx/asset-pairs, rail details from GET /rails), cache responses locally and refresh on a schedule rather than fetching on every request.
Sandbox and production environments have independent rate limits. Testing against sandbox will not consume your production quota.
  • Error Handling — understanding the rate_limit_error type
  • Pagination — reducing API calls with efficient pagination
  • Idempotency — safely retrying requests after rate limit delays