Guides
Rate limits
Limits are applied per API key, so one busy integration can’t slow down another.
Limits
| Key type | Limit |
|---|---|
Live keys (so_live_) | 120 requests per minute |
Test keys on trial stores (so_test_) | 30 requests per minute |
Rate-limit headers
Every response tells you where you stand:
| Header | Description |
|---|---|
X-RateLimit-Limit | Requests allowed per window. |
X-RateLimit-Remaining | Requests left in the current window. |
X-RateLimit-Reset | When the window resets (Unix timestamp, seconds). |
Retry-After | Only on 429: seconds to wait before retrying. |
Handling 429 responses
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1793714400
{"error":{"type":"rate_limited","message":"Rate limit exceeded. Retry in 12 seconds."}}async function request(url, init = {}, attempt = 0) {
const res = await fetch(url, init);
if (res.status === 429 && attempt < 5) {
const wait = Number(res.headers.get("Retry-After") ?? 1);
await new Promise((r) => setTimeout(r, wait * 1000));
return request(url, init, attempt + 1);
}
return res;
}function request(string $url, array $headers, int $attempt = 0): array
{
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_HEADER => true]);
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
if ($status === 429 && $attempt < 5) {
preg_match('/^Retry-After:\s*(\d+)/mi', substr($raw, 0, $headerSize), $m);
sleep((int) ($m[1] ?? 1));
return request($url, $headers, $attempt + 1);
}
return [$status, json_decode(substr($raw, $headerSize), true)];
}import time
import requests
def request(method, url, attempt=0, **kwargs):
res = requests.request(method, url, **kwargs)
if res.status_code == 429 and attempt < 5:
time.sleep(int(res.headers.get("Retry-After", "1")))
return request(method, url, attempt + 1, **kwargs)
return resStaying under the limit
- Use
limit=250when paging through large lists. - Sync changes with
updated_sinceinstead of re-reading everything — see incremental sync. - Prefer webhooks over polling.
- Fetch stock for many variants at once with
GET /inventory-levels?variant_ids=….