Rate Limits
The oec.sh API uses per-key rate limiting to ensure fair usage and platform stability.
Limits by Key Type
| Key Type | Limit | Window |
|---|---|---|
Read-only (oec_live_ro_*) | 120 requests | 60 seconds |
Full access (oec_live_rw_*) | 20 requests | 60 seconds |
| Webhook mutations (create/update/delete) | 10 requests | 60 seconds |
The webhook mutation limit applies in addition to the key-level limit. A full-access key can make 20 total requests per minute, of which at most 10 can be webhook write operations.
Rate Limit Headers
Every API response includes headers showing your current limit status:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the window |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Window | Window duration in seconds (always 60) |
Example response headers:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 87
X-RateLimit-Window: 60When You Hit the Limit
When you exceed the rate limit, the API returns:
HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-RateLimit-Limit: 20
X-RateLimit-Remaining: 0
X-RateLimit-Window: 60{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded. You have made 20 requests in the last 60 seconds. Please wait before retrying.",
"retry_after": 23
}The Retry-After header and retry_after field tell you how many seconds to wait before your next request will succeed.
Best Practices
Check Headers Before Hitting 429
Read X-RateLimit-Remaining on every response. If it drops to 0 or 1, pause before the next request:
import requests
import time
def api_request(session, url):
response = session.get(url)
remaining = int(response.headers.get("X-RateLimit-Remaining", 1))
if remaining <= 1:
# Wait for the window to reset before sending more requests
time.sleep(5)
return response.json()Use Exponential Backoff on 429
If you receive a 429, wait for the Retry-After seconds, then retry with exponential backoff:
import requests
import time
def request_with_backoff(session, url, max_retries=3):
for attempt in range(max_retries):
response = session.get(url)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 5))
wait = retry_after * (2 ** attempt) # exponential backoff
print(f"Rate limited. Waiting {wait}s before retry {attempt + 1}/{max_retries}")
time.sleep(wait)
continue
return response.json()
raise Exception("Max retries exceeded")Reduce Request Volume
- Cache responses — environment status doesn't need to be fetched more than once every 30 seconds
- Use webhooks instead of polling — subscribe to
environment.status_changedevents instead of polling/environments/{id}in a loop - Batch where possible — listing endpoints return up to 100 items per page; prefer one list call over many individual
GET /environments/{id}calls
Webhooks are the right tool for status monitoring. Instead of polling the API every few seconds to check if a deployment finished, subscribe to the deployment.completed and deployment.failed webhook events — you'll be notified instantly without any polling overhead.
Increasing Your Limit
Rate limits are set per key type and are not currently configurable. If you have a legitimate use case that requires higher limits (e.g., a large agency managing 50+ environments with automated tooling), contact support at support@oec.sh to discuss options.