Rate limits
v1 status: rate limiting is being implemented. The 30 RPS per-credential limit described below is the design target — until the middleware ships, requests are not actively throttled. Build to the limit anyway: the moment we turn it on, partners exceeding 30 RPS will start receiving 429s. We'll announce a date in the changelog.
Two limits apply, in this order:
| Layer | Limit | Scope |
|---|---|---|
| Per-credential | 30 requests/second | Each gspk_live_* credential separately |
| Global upstream | 10 requests/second | Aggregate across all GhostSwap partners (we absorb this) |
Once enforcement is live, hitting the per-credential limit returns HTTP 429 with a Retry-After header. The global cap is mostly invisible to you — we queue and serialize internally.
Recognizing 429s
HTTP/1.1 429 Too Many Requests
Retry-After: 1
Content-Type: application/json
X-Request-Id: 7b3c1e9f-...
{
"error": {
"type": "rate_limit_error",
"code": "rate_limited",
"message": "Per-credential rate limit exceeded",
"retry_after_ms": 1000
}
}Backing off
Read Retry-After (in seconds) and sleep at least that long before retrying. Use the same Idempotency-Key if you're retrying a POST /v1/swaps — that way the retry is safe even if the original request succeeded server-side and we return the cached response.
async function withBackoff(fn, attempts = 5) {
for (let i = 0; i < attempts; i++) {
const res = await fn();
if (res.status !== 429) return res;
const retryAfter = Number(res.headers.get('Retry-After')) || 1;
await new Promise((r) => setTimeout(r, retryAfter * 1000));
}
throw new Error('Rate limit retries exhausted');
}Avoiding rate limits
- Cache
GET /v1/currenciesfor ~10 minutes. The list rarely changes minute-to-minute. - Quote once per user attempt. Don't re-quote on every keystroke; debounce by ~500ms.
- Poll
GET /v1/swaps/:idevery 30 seconds while a swap is non-terminal. There's no benefit to faster — our worker only updates the upstream every 30s. - Stop polling at terminal states.
finished,failed,refunded,overdue,expired— never poll these again.
Need higher limits?
Email support@ghostswap.io with the credential id and your expected sustained RPS. We adjust the per-credential bucket on a case-by-case basis.
How GhostSwap absorbs the global cap
Our liquidity layer enforces a 10 RPS aggregate cap shared across all GhostSwap partners. We handle this for you:
- Read endpoints (currencies, pairs, address validation, quotes, status) are queued internally so partners don't see those 429s.
- Writes (swap creation) are serialized to fit under the cap.
If we ever need to surface a global 429 to you, the error type is upstream_error with code upstream_rate_limited — distinct from your per-credential rate_limited.