Limits, retries & errors
Rate limits, safe retries with Idempotency-Key, and what every error looks like.
Rate limits#
Only reads are rate-limited. Buys, sells, batches, launches and every other write are not throttled — the spending guardrails are the key's own scopes and caps (maxSolPerTrade, dailySolCap, the wallet list), not a per-minute bucket.
Reads are counted per minute, per key and per account. Many keys on one account share the account's limit.
| Kind of call | Per key | Per account |
|---|---|---|
Reads (GET) | 300 | 600 |
| Writes and launches | no limit | no limit |
A read answer carries the key's bucket in three headers (writes carry none — there is no bucket):
| Header | |
|---|---|
X-RateLimit-Limit | Reads per minute the key allows |
X-RateLimit-Remaining | Reads left in the current minute |
X-RateLimit-Reset | When the bucket is full again, as a Unix time in seconds |
Over the limit you get 429 with a Retry-After header (seconds), and the same number in the body:
{ "code": "rate_limited", "error": "rate limit reached — slow down", "retryAfterSeconds": 12, "traceId": "…" }Wait that long before trying again. Too many calls with a wrong or revoked key from one address are refused with 429 and code: "auth_rate_limited" for a minute.
Retry safely#
Networks drop answers. To retry a buy, sell, batch or launch without doing it twice, send an Idempotency-Key header: a unique string you pick per operation, 8–128 characters of letters, digits, -, _ or :.
curl -X POST https://evm.vortexdeployer.com/evm/api/v1/buy \
-H "Authorization: Bearer vd_…" \
-H "Idempotency-Key: buy-2026-09-15-0001" \
-H "Content-Type: application/json" \
-d '{"mint":"7xKX…pump","walletId":"812","amountSol":0.05}'| You send again | You get |
|---|---|
| Same key, same body | The first call's answer. Nothing is sent twice |
| Same key, same body, first call still running | 409 with code: "operation_pending". Ask again shortly |
| Same key, different body | 409 with code: "idempotency_conflict". Use a new key for a new operation |
| A key outside 8–128 characters or with other characters | 400 with code: "invalid_idempotency_key" |
Without Idempotency-Key, a retry is a new trade.
Errors#
Every error is JSON with three fixed fields:
{ "code": "insufficient_scope", "error": "this API key does not have the 'trade' scope", "traceId": "9f1c…" }| Field | |
|---|---|
code | A stable, machine-readable name for the refusal (table below). Switch on this |
error | What went wrong, for people. The wording may change; the code won't |
traceId | Quote it in a support ticket |
Some codes add a field naming the limit that refused you: over_trade_cap sends maxSolPerTrade, over_daily_cap sends dailySolCap, wallet_not_allowed sends walletId, rate_limited sends retryAfterSeconds.
A malformed body is validation_failed and lists each problem with its path:
{
"code": "validation_failed",
"error": "validation failed",
"details": [{ "path": "amountSol", "message": "Too small: expected number to be >0" }],
"traceId": "…"
}Codes#
| Code | Status | Means |
|---|---|---|
validation_failed | 400 | The body or query is malformed. details lists every problem. Fix it; retrying won't help |
bad_request | 400 | Understood but refused, e.g. a delayed sell batch, or buys on an EVM launchpad |
invalid_idempotency_key | 400 | The Idempotency-Key header is malformed |
invalid_api_key | 401 | Missing, wrong or revoked key |
insufficient_scope | 403 | The key doesn't have the scope this route needs |
ip_not_allowed | 403 | The call came from outside the key's IP allowlist |
key_restricted | 403 | A key with limits tried something only an unlimited key may do (tasks, a real launch) |
sandbox_key | 403 | A sandbox key sent a real (dryRun: false) call |
wallet_not_allowed | 403 | The wallet isn't yours, or isn't on the key's wallet list |
private_key_not_allowed | 403 | A wallet-list key sent a privateKey |
over_trade_cap | 403 | A buy above the key's Max per buy |
over_daily_cap | 403 | The buy would pass the key's Daily cap |
not_found | 404 | No such route, or the task, project or wallet isn't yours |
idempotency_conflict | 409 | Same Idempotency-Key, different body |
operation_pending | 409 | Same Idempotency-Key, first call still running. Ask again shortly |
conflict | 409 | The change clashes with the current state |
payload_too_large | 413 | The body is over the size limit |
unprocessable | 422 | Understood but impossible, e.g. selling a token the wallet doesn't hold |
rate_limited | 429 | Over the rate limit. Honour Retry-After |
auth_rate_limited | 429 | Too many bad keys from this address. Wait a minute |
internal_error | 500 | Our side. Retry with the same Idempotency-Key, and quote the traceId |
service_unavailable | 503 | Briefly unavailable, or an idempotent operation is being reconciled. Retry with the same Idempotency-Key |
Every status and code, per route, is in the OpenAPI document.
Request an API feature#
API → Requests files a feature request. Replies arrive on the ticket and in the bell.