Cloak API

Errors

Flat objects with a stable error code. Branch on the code, never on the message. Messages are written for humans and will change.

Error shape
{
  "success": false,
  "error":   "AMOUNT_BELOW_MIN",
  "message": "Minimum for this asset is 2 USDC.",
  "min":     "2000000",
  "max": "200000000000"
}

Codes

CodeHTTPWhat to do
AMOUNT_BELOW_MIN400Carries both min and max, so you can render a real bound.
AMOUNT_ABOVE_MAX400Carries both min and max.
QUOTE_FAILED400 / 503Unsupported pair, or pricing temporarily unavailable.
ASSET_NOT_SUPPORTED400Unknown asset, not enabled for your account, or a pair that cannot be swapped.
INVALID_ADDRESS400Carries field, so you can mark the right input.
PARTNER_FEE_TOO_HIGH400Carries maxBps. Clamp and retry.
FEE_RECIPIENT_NOT_REGISTERED400Address is valid but is not registered to your account. Add it from the portal's Fees page or choose an eligible wallet.
FEE_RECIPIENT_COOLING_OFF400Registered too recently. Resolves on its own; wait rather than reconfigure.
QUOTE_EXPIRED409Request a new quote.
QUOTE_MISMATCH409The echoed quote failed authentication. Send it back unmodified.
ORDER_IN_PROGRESS409An identical order is being created. Retry shortly.
IDEMPOTENCY_CONFLICT409Key reused with different terms. Use a fresh key.
ORDER_NOT_REFRESHABLE409Order is past PENDING. Poll status instead.
ORDER_NOT_FOUND404Unknown, or belongs to another account.
UNAUTHORIZED401Missing, unknown, revoked, or disabled key, deliberately indistinguishable.
LIMIT_EXCEEDED429Carries retryAfter in seconds.
SERVICE_UNAVAILABLE503Carries retryAfter. Back off; do not hammer.

Limit errors carry both bounds

AMOUNT_BELOW_MIN and AMOUNT_ABOVE_MAX both return min and max, so a single failed request always tells you the full usable range. You never need a second call to discover the other end.
Examples in

Handling codes

Handling codes
# Branch on .error, never on .message.
RES=$(curl -sS -X POST "https://api.veilo.network/cloak/v1/quote" -H "Authorization: Bearer $VEILO_KEY" \
        -H "Content-Type: application/json" -d "$BODY")

case "$(echo "$RES" | jq -r '.error // "OK"')" in
  OK)                   echo "$RES" | jq -r '.quote.amountOut' ;;
  AMOUNT_BELOW_MIN)     echo "minimum is $(echo "$RES" | jq -r .min)" ;;
  PARTNER_FEE_TOO_HIGH) echo "cap is $(echo "$RES" | jq -r .maxBps) bps" ;;
  LIMIT_EXCEEDED)       sleep "$(echo "$RES" | jq -r .retryAfter)" ;;
  *)                    echo "$RES" | jq -r '.error + ": " + .message' ;;
esac

Which are worth retrying

ClassRetry?Codes
TransientYes, after retryAfterLIMIT_EXCEEDED, SERVICE_UNAVAILABLE, ORDER_IN_PROGRESS
Stale stateRe-quote, then retryQUOTE_EXPIRED
Your bugNo. Fix the callIDEMPOTENCY_CONFLICT, QUOTE_MISMATCH, INVALID_ADDRESS
ConfigurationNo. Update the portal or contact supportFEE_RECIPIENT_NOT_REGISTERED, UNAUTHORIZED, ASSET_NOT_SUPPORTED
WaitingYes, but slowlyFEE_RECIPIENT_COOLING_OFF

Retrying a non-transient error simply reproduces it. For LIMIT_EXCEEDED in particular, retrying sooner than retryAfter extends the window. See Limits.