API Reference
Rate Limits

Rate Limits

The oec.sh API limits how many requests each API key can make per minute. The limit depends on the kind of request, not on the kind of key.


Limits

RequestsLimit per keyWindow
Reads (GET)120 requests1 minute
Writes (POST, PATCH, PUT, DELETE)20 requests1 minute
Webhook changes (create, update, delete a webhook)10 requests60 seconds

Reads and writes are counted separately. A full-access key can make 120 reads and 20 writes in the same minute, so polling a deployment or listing environments never uses up the budget you need to deploy. A read-only key can only call GET endpoints, so only the read limit applies to it.

The read and write windows are clock minutes: the counters start again at zero at the start of every minute.

The webhook limit applies on top of the write limit. Its 60-second window starts with the first webhook change.

Requests rejected before your key is recognised (a missing, malformed, unknown or expired key) do not count against your limits. Ten requests with an unknown key from the same IP address in quick succession (less than a minute apart) block that address for 15 minutes; during the block, requests from it that carry a key get 429 with error ip_blocked.

The address is the one your connection reaches our network from. Headers you send, such as X-Forwarded-For, do not change it, so nobody can get another address blocked by naming it. Requests relayed by the oec.sh MCP server are attributed to the original caller, so one user's failed attempts do not block everyone else using that server.


Rate Limit Headers

Every successful response includes the limit that applied to the request:

HeaderDescription
X-RateLimit-LimitRequests allowed in the window for this kind of request (120 for reads, 20 for writes)
X-RateLimit-RemainingRequests of this kind left in the current window
X-RateLimit-WindowWindow length in seconds (always 60)

Example response headers on a GET:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 87
X-RateLimit-Window: 60

Other error responses (for example 404 or 409) do not carry these headers.


When You Hit the Limit

When a key goes over its read or write limit, the API answers:

HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-RateLimit-Limit: 20
X-RateLimit-Remaining: 0
X-RateLimit-Window: 60
{
  "detail": {
    "error": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Slow down your requests."
  }
}

Retry-After is the number of seconds until the current window ends. Wait that long and the next request is allowed.

Going over the webhook limit returns 429 with error webhook_mutation_rate_limit and no Retry-After header; wait up to 60 seconds before the next webhook change.

An address blocked after failed authentication gets 429 with error ip_blocked. Its Retry-After is the number of seconds left on the 15-minute block, and it carries no X-RateLimit-* headers.


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 of the same kind:

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()

Wait for Retry-After on 429

If you receive a 429, wait the number of seconds in Retry-After, then retry:

import requests
import time
 
def request_with_retry(session, url, max_retries=3):
    for attempt in range(max_retries):
        response = session.get(url)
 
        if response.status_code == 429:
            wait = int(response.headers.get("Retry-After", 60))
            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")

When you retry a write after a 429, send the same idempotency key as the first attempt (see Idempotency).

Reduce Request Volume

  • Cache responses. Environment status does not need to be fetched more than once every 30 seconds.
  • Use webhooks instead of polling. Subscribe to environment.status_changed instead of polling /environments/{id} in a loop.
  • Use list endpoints. List endpoints return up to 100 items per page; one list call is cheaper than many single GET /environments/{id} calls.

Webhooks are the right tool for status monitoring. Instead of polling the API to see whether a deployment finished, subscribe to the deploy.completed and deploy.failed webhook events and you are told as soon as it happens.


Increasing Your Limit

Rate limits are the same for every key and are not currently configurable. If you have a use case that needs higher limits (for example an agency managing 50+ environments with automated tooling), contact support at support@oec.sh.