Get started

Errors

Errors use real HTTP status codes and one JSON envelope with a stable code. Match on the code, never on the message.

The envelope

Every error response with a JSON body looks like this:

Error body
{
  "error": {
    "code": "quote_expired",
    "message": "The quote has expired; ask for a new one.",
    "request_id": "req_01JB8X2Q4M7Z3V9K5T1N6R0P2W"
  }
}
  • code is stable and machine-readable. Branch on it.
  • message explains the error to a person. It may change at any time, so don't parse it.
  • request_id is also in the MK-Request-Id header of every response, successful or not. Log it, and quote it when you contact us.

A 2xx status means success; anything else carries this envelope. GET /v1/giftcards/{id}/pdf answers application/pdf on success and JSON on errors.

Error codes

StatusCodeMeaningWhat to do
400invalid_requestThe request is malformed: bad JSON, an unknown or invalid field, or a missing header.Fix the request. The message names the field.
401invalid_signatureThe MK-* headers are missing or malformed, the key id is unknown, or the signature doesn't match.Check the key id and the canonical string; sign the exact bytes you send.
401key_expiredThe key has passed its expiry date.Create a new key in the webapp.
401key_revokedThe key was revoked.Create a new key in the webapp.
401timestamp_out_of_rangeMK-Timestamp is more than 30 seconds from the server clock.Sync your clock (server_time in GET /v1/status) and sign again.
401nonce_reusedThis key already used this MK-Nonce.Sign again with a fresh random nonce.
403insufficient_scopeThe key doesn't have the scope this endpoint needs.Use a key with the scope. Scopes can't be added to an existing key.
403ip_not_allowedThe request came from an address outside the key's IP allowlist.Call from an allowed address, or create a key with a different allowlist.
403account_restrictedThe account can't use the API right now (for example locked, inactive, or onboarding not finished).Check the account in the webapp, or contact us.
403api_access_disabledAPI access is switched off.Wait until API access is switched on; GET /v1/status shows it.
403forbiddenThis action is switched off for the API right now.Check GET /v1/status; contact us if you need it switched on.
404not_foundNo such resource.Check the id or asset symbol.
405method_not_allowedThe path exists, but not with this method.Use the method the reference lists for this path.
409idempotency_conflictThis Idempotency-Key was used with a different request body.Use a new Idempotency-Key for a different request.
409idempotency_in_progressA request with this Idempotency-Key is still being processed.Wait a moment, then retry with the same Idempotency-Key.
409quote_already_usedThe quote was already accepted.Look up the trade: GET /v1/quotes/{id} has its trade_id.
413payload_too_largeThe request body is larger than 8 KB.Keep request bodies under 8 KB.
415unsupported_media_typeRequest bodies must be application/json.Send Content-Type: application/json.
422insufficient_fundsThe available balance doesn't cover the trade, gift card or withdrawal.Deposit, or ask for a smaller amount.
422amount_below_minimumThe amount is below the minimum.Raise the amount; GET /v1/assets lists the minimum.
422amount_above_maximumThe amount is above the maximum.Lower the amount, or split it over time.
422limit_exceededA daily or 30-day limit would be exceeded.Wait for the daily or 30-day window; GET /v1/limits shows your use.
422key_limit_reachedThe account already has as many API keys as it can (5): revoke one in Settings → API first.Revoke a key you no longer use in Settings → API, then make a new connect code.
422quote_expiredThe quote has expired.Create a new quote and accept it within its lifetime.
422price_movedThe market moved too far from the quoted price.Create a new quote.
422asset_not_tradableThe asset can't be traded right now.Check buy_enabled and sell_enabled in GET /v1/assets.
422asset_not_withdrawableThe asset can't be withdrawn on this network right now.Check the asset and network, or withdraw in the app.
429rate_limitedToo many requests. Wait for Retry-After seconds.Wait Retry-After seconds, then slow down.
429too_many_concurrent_requestsToo many requests in flight for this key or account.Wait for a request in flight to finish; Retry-After is 1.
429key_suspendedThe key is suspended, for example after sustained rate limiting.Stop the client and fix the loop. The suspension lifts after 15 minutes.
500internal_errorSomething went wrong on our side.Retry later. If it persists, contact us with the request_id.
502bad_gatewayAn upstream service answered unexpectedly.Retry with backoff; a POST only with the same Idempotency-Key.
503trading_haltedTrading is paused: switched off, or paused for this asset for a short while after a fast price move.Retry later; GET /v1/status shows trading_enabled.
503maintenanceThe API is read-only for maintenance.Reads still work. Retry writes later.
503overloadedThe service is overloaded. Retry after Retry-After seconds.Wait Retry-After seconds, then retry.
503service_unavailableThe service is temporarily unavailable.Retry with backoff; a POST only with the same Idempotency-Key.
504gateway_timeoutAn upstream service didn't answer in time. A POST may still complete; retry with the same Idempotency-Key.The request may have gone through. Retry with the same Idempotency-Key.

Each endpoint's reference page lists the codes it can return.

Handling errors

The SDKs turn every error into an ApiError with status, code, message and requestId (request_id in Python), plus retryAfter (retry_after) when the response had a Retry-After header. Subclasses group the statuses:

ClassStatus
AuthenticationError401
PermissionError403
NotFoundError404
ConflictError409
ValidationError400, 422
RateLimitError429
ServiceError5xx
ConnectionErrorno response (network failure or timeout)
import { ApiError, Myntkaup, RateLimitError } from "@myntkaup/api";

const client = new Myntkaup();

try {
  const trade = await client.buy({ asset: "BTC", quoteAmount: "25.00" });
  console.log(trade.status);
} catch (error) {
  if (error instanceof RateLimitError) {
    console.warn(`Slow down; retry in ${error.retryAfter} s (${error.requestId})`);
  } else if (error instanceof ApiError && ["price_moved", "quote_expired"].includes(error.code)) {
    // The market moved: decide whether to try again with a new quote.
  } else {
    throw error;
  }
}
import myntkaup
from myntkaup import Myntkaup

client = Myntkaup()

try:
    trade = client.buy(asset="BTC", quote_amount="25.00")
    print(trade["status"])
except myntkaup.RateLimitError as error:
    print(f"Slow down; retry in {error.retry_after} s ({error.request_id})")
except myntkaup.ApiError as error:
    if error.code not in ("price_moved", "quote_expired"):
        raise
    # The market moved: decide whether to try again with a new quote.

Which errors to retry

  • Retry after waiting: 429 rate_limited, 429 too_many_concurrent_requests, 503 overloaded and 503 service_unavailable (honour Retry-After), and 502 and 504.
  • Retry a POST only with the same Idempotency-Key. After a 502, 504 or a dropped connection the request may have gone through; the same key returns the first result instead of doing it twice. See Idempotency.
  • Wait, then retry with the same key: 409 idempotency_in_progress.
  • Don't retry as is: every other 4xx. Change the request (or the key, or the quote) first.
  • Never retry: 429 key_suspended. Stop and fix the client.

The SDKs handle the first two cases for you: a GET is retried on 429 (except key_suspended), 502, 503, 504 and network errors, and a POST only when it carries an Idempotency-Key. They retry at most twice by default, with exponential backoff and jitter, and honour Retry-After.