Errors

All non-2xx responses use the same envelope. Inspect error.type to decide how to recover.

Envelope

{
  "error": {
    "type": "validation_error",
    "code": "missing_field",
    "message": "from is required",
    "param": "from"
  }
}
FieldTypeNotes
typestringHigh-level category. Drives your retry strategy.
codestringShort machine code. Stable; safe to switch on.
messagestringHuman-readable explanation. May change wording over time.
paramstringPresent only on validation_error. The request field that's invalid.
retry_after_msnumberPresent on rate_limit_error. How long to wait before retrying.
upstream_codenumberPresent on upstream_error when the upstream returned a numeric code.

Every response also has an X-Request-Id header. Log it. Pass it back to GhostSwap support when escalating — we use it to find your request in our logs.

Error types

These are the values you'll see in error.type. Switch on these to drive recovery — they're stable.

TypeHTTPRecoverable?What to do
validation_error400No (without changing input)Surface to the user; fix the input. Check error.param for the bad field.
authentication_error401No (without new credentials)Check the Authorization header is present and well-formed. Code may be unauthenticated or revoked_credential.
authorization_error403SometimesOrg isn't active yet, or you don't have access to this resource. Code is usually forbidden or org_not_active.
not_found404NoWrong id or it doesn't belong to your org
conflict409YesMost commonly idempotency_key_mismatch — same Idempotency-Key reused with a different body. Change one.
unprocessable422Yes (with different input)Request was well-formed but rejected by a business rule. See error.code.
rate_limit_error429YesWait error.retry_after_ms (or the Retry-After header), then retry with the same Idempotency-Key.
upstream_error502 / 503Yes (transient)Backoff and retry. Often resolves in seconds.
internal_error500SometimesBackoff and retry with the same idempotency key. Escalate with X-Request-Id if it persists.

Common codes

error.code is the short machine-readable classifier under each type. These are stable and safe to switch on. Common ones you'll see:

CodeTypeMeaning
invalid_requestvalidation_errorGeneric bad-shape body (see error.param)
missing_fieldvalidation_errorA required body/query field was absent
invalid_currencyvalidation_errorUnknown ticker or currency is temporarily disabled
amount_below_minvalidation_erroramountFrom is below the pair's minimum
amount_above_maxvalidation_erroramountFrom exceeds the pair's maximum
invalid_addressvalidation_errorThe destination address failed validation
idempotency_key_mismatchconflictSame Idempotency-Key, different body
pending_request_existsconflictA previous request of this kind is still in flight (e.g. payouts)
unauthenticatedauthentication_errorMissing, malformed, or expired bearer credential
revoked_credentialauthentication_errorCredential was revoked or its parent org was suspended
forbiddenauthorization_errorGeneric access denied
org_not_activeauthorization_errorThe credential's org is pending_review / suspended / rejected
below_thresholdunprocessablePayout amount below the $100 threshold
insufficient_balanceunprocessablePayout amount exceeds available balance
rate_limitedrate_limit_errorPer-credential RPS exceeded — see Retry-After
upstream_rate_limitedupstream_errorGhostSwap's upstream liquidity cap hit (rare; we shield against this)
upstream_not_configuredupstream_errorServer-side configuration issue. Email support
upstream_empty_quoteupstream_errorNo quote returned — usually means pair is unavailable
upstream_bad_responseupstream_errorLiquidity provider returned non-JSON. Auto-retried once internally before surfacing; if you see this, it survived the retry. Retry your request with the same Idempotency-Key
upstream_unreachableupstream_errorNetwork failure reaching the liquidity layer. Auto-retried once internally
provider_credential_pendingupstream_error (503)Your dedicated liquidity key is still being activated by our liquidity team (1–3 business days from approval). Reads work; swap creation will be enabled once activation completes. No action needed by you

Recovery patterns

Retry on transient

async function retryable(fn) {
  for (let i = 0; i < 3; i++) {
    const res = await fn();
    if (res.ok) return res.json();
    const body = await res.json().catch(() => ({}));
    if (body?.error?.type !== 'upstream_error' && body?.error?.type !== 'rate_limit_error') {
      throw new Error(`${body?.error?.code}: ${body?.error?.message}`);
    }
    const retryAfter = Number(res.headers.get('Retry-After')) || 2 ** i;
    await new Promise((r) => setTimeout(r, retryAfter * 1000));
  }
  throw new Error('exhausted retries');
}

Surface validation cleanly

const body = await res.json();
if (body.error?.type === 'validation_error') {
  showFieldError(body.error.param, body.error.message);
}

Distinguish from upstream

if (body.error?.type === 'upstream_error') {
  // Show a generic "exchange provider is unavailable" message;
  // your user didn't do anything wrong.
}

See also