Error Codes

When an error occurs, the Vexutopia API returns a consistent error response format with details to help you understand and handle the issue.

Error Response Format

All errors follow this structure:

JSON
{
  "error": "Validation failed: amount: Required",
  "code": "VALIDATION_ERROR",
  "details": {
    "fieldErrors": { "amount": ["Required"] },
    "formErrors": []
  }
}
Error Response Fields
ParameterTypeDescription
errorstringA human-readable description of the error
codestringA machine-readable error code (UPPERCASE_SNAKE_CASE)
detailsobjectOptional. Present on VALIDATION_ERROR — contains zod fieldErrors and formErrors.

HTTP Status Codes

The API uses standard HTTP status codes to indicate success or failure:

200
OK

Request succeeded

201
Created

Resource created successfully

400
Bad Request

Invalid request parameters or body

401
Unauthorized

Missing or invalid API key

403
Forbidden

API key lacks required permissions

404
Not Found

Requested resource does not exist

422
Unprocessable Entity

Request was well-formed but failed validation or business rules (e.g. unsupported currency, no provider available, amount below minimum).

429
Too Many Requests

Rate limit exceeded. Slow down requests.

500
Internal Server Error

Something went wrong on our end

Error Codes

Common error codes you may encounter:

CodeDescription
MISSING_API_KEYNo X-API-Key header was provided. 401.
INVALID_API_KEYAPI key does not exist. 401.
REVOKED_API_KEYAPI key has been revoked from the dashboard. 401.
EXPIRED_API_KEYAPI key expiry date has passed. 401.
ACCOUNT_SUSPENDEDOwning account is suspended. 403.
INSUFFICIENT_SCOPEAPI key lacks the scope required for this endpoint. 403.
INVALID_BODYRequest body could not be parsed as JSON. 400.
VALIDATION_ERRORBody parsed but failed schema validation. The details field contains per-field errors. 422.
RATE_LIMITEDPer-API-key rate limit exceeded. Honour the Retry-After header. 429.
NO_PROVIDER_AVAILABLENo payment provider can serve the requested currency / country / payment_method combination. 422. Not returned when the only reason is maintenance — that is PAYMENT_METHOD_MAINTENANCE.
PAYMENT_METHOD_MAINTENANCEThe payment method that would have taken this payment is temporarily in maintenance. methods lists which ones, in the same spelling as payment_method on webhooks (for example ["gift_card"]), and the message includes our maintenance note when there is one. Retrying later can succeed; in the meantime you can create the payment for another method. Returned only when maintenance is the actual reason — if the method could not have taken the payment anyway, you get NO_PROVIDER_AVAILABLE. 503.
PROVIDER_ERRORA downstream provider returned an unrecoverable error while creating the payment. 502.
PROVIDER_UPSTREAM_UNAVAILABLEThe selected provider timed out or is currently unreachable. Safe to retry. 503.
WALLET_NOT_CONFIGUREDCrypto payment requested but the merchant has no payout wallet configured for the requested currency. 422.
DIRECT_MODE_UNSUPPORTED_CURRENCYdirect: true was passed with a currency outside the supported set (BRL, ARS, RUB). 422.
CHINA_PAY_REQUIRES_CNYpayment_method=ALIPAY or WXPAY was passed without currency=CNY.
CHINA_PAY_AMOUNT_TOO_LOWOrder amount is below the 1 CNY per-order minimum for Alipay/WeChat Pay.
CHINA_PAY_AMOUNT_TOO_HIGHOrder amount exceeds the 1,000 CNY (~$138 USD) per-order maximum for Alipay/WeChat Pay. Split the order or use a different rail.
AMOUNT_OUT_OF_RANGEIDR or PHP amount is outside the rail window: 10,000–10,000,000 IDR for QRIS, 100–50,000 PHP for GCash or Maya. 422.
CUSTOMER_PHONE_REQUIREDcurrency=IDR or currency=PHP was sent without a valid customer_phone. The payment page cannot be generated without it. Use a local mobile: Indonesian in 628… form (08… and +62… are accepted), Philippine in 639… form (09… and +63… are accepted). If you do not collect phone numbers, create the payment in USD instead — the hosted checkout asks the customer for it. 422.
CUSTOMER_EMAIL_REQUIREDcurrency=PHP (GCash or Maya) was sent without customer_email. The payment page cannot be generated without it. If you do not collect email addresses, create the payment in USD instead — the hosted checkout asks the customer for it. 422.
COUNTRY_NOT_SUPPORTEDMastercard is not available for the country you passed. The full list is under Mastercard in the payments docs. Offer the customer another payment method. 422.
AMOUNT_BELOW_MINIMUMThe order is below the Mastercard per-order minimum (EUR 2). 422.
AMOUNT_ABOVE_MAXIMUMThe order is above the Mastercard per-order ceiling — about EUR 500, and it can shift slightly over time. The error message states the current limit. 422.
MASTERCARD_MAINTENANCEReturned for mastercard: true while Mastercard is temporarily in maintenance. Retrying later can succeed. 503.
CARD_DIRECT_MAINTENANCEReturned for card_direct: true while direct card payments are temporarily in maintenance. Retrying later can succeed. 503.
MASTERCARD_NOT_ENABLEDThis site is not approved for Mastercard yet. Request it per site under Settings → Payment Methods; we review the site and enable it. 403.
MASTERCARD_DISABLEDThe site is approved, but Mastercard is switched off for your account. Turn it back on under Settings → Payment Methods. Note this is the method switch, not “Show Mastercard on your main checkout” — hiding it there never blocks mastercard: true. 403.
INVALID_IDEMPOTENCY_KEYIdempotency-Key header was provided but does not match the allowed format (1–255 chars from A-Z a-z 0-9 _ -).
IDEMPOTENCY_KEY_REUSEDAn Idempotency-Key was reused within 24h with a different request body. Use a fresh key for new requests.
GIFT_CARD_AMOUNT_UNSUPPORTEDgift_card_amount is not one of 5, 10, 15, 20, 25, 30, 40, 50, 75, 100, 250, 500. 422.
GIFT_CARD_AMOUNT_MISMATCHInvoice is outside that gift-card face's window (90% of face up to face). An $8 product cannot use 5 or 10. 422.
GIFT_CARD_AMOUNT_USD_ONLYgift_card_amount was sent with a non-USD currency. 422.
CASHAPP_US_ONLYCash App checkout was started from a non-US connection. Test checkouts skip this. 403.
INTERNAL_ERRORAn unexpected error occurred. 500.
INVALID_ALLOWED_METHODSallowed_methods contains a name we don’t recognise, or is an empty list. The message lists every accepted name. To allow every method, omit the parameter. 422.
ALLOWED_METHODS_CONFLICTallowed_methods contradicts the rest of the request: it was combined with crypto, direct, card_direct, card_payment or payment_method (which already choose one method); the payment is not in USD (only mastercard: true may also use EUR, and must then list mastercard); or gift_card_amount / preferred_method names a method the list excludes. 422.
NO_ALLOWED_METHOD_ENABLEDEvery method in allowed_methods is switched off for your account, so the checkout would have nothing to offer. Turn one on under Settings → Payment Methods, or change the list. 422.
METHOD_NOT_ALLOWEDReturned by the hosted checkout, not by your API call: the customer tried to start a method that allowed_methods excludes for that payment. You normally never see it — excluded methods are not shown — but it is what a customer gets if they try anyway. 403.

Handling Errors

Here's how to properly handle errors in your integration:

JavaScript
async function createPayment(amount, currency) {
  const response = await fetch('https://vexutopia.com/api/v1/payments', {
    method: 'POST',
    headers: {
      'X-API-Key': 'vex_test_your_api_key',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ amount, currency })
  });

  const data = await response.json();

  if (!response.ok) {
    // Handle the error based on the code
    switch (data.code) {
      case 'INVALID_API_KEY':
      case 'MISSING_API_KEY':
      case 'REVOKED_API_KEY':
      case 'EXPIRED_API_KEY':
        throw new Error('Check your API key configuration');
      case 'VALIDATION_ERROR':
        throw new Error(`Invalid request: ${data.error}`);
      case 'NO_PROVIDER_AVAILABLE':
        throw new Error('No provider available for this currency/country/method');
      case 'PAYMENT_METHOD_MAINTENANCE':
      case 'MASTERCARD_MAINTENANCE':
      case 'CARD_DIRECT_MAINTENANCE':
        // Temporary: retry later, or offer the customer another method.
        // data.methods lists what is down, e.g. ["gift_card"].
        throw new Error(data.error);
      case 'RATE_LIMITED': {
        // Honour Retry-After (in seconds) and retry once
        const retryAfter = Number(response.headers.get('Retry-After') ?? '1');
        await new Promise(r => setTimeout(r, retryAfter * 1000));
        return createPayment(amount, currency);
      }
      case 'PROVIDER_UPSTREAM_UNAVAILABLE':
        // Safe to retry with backoff
        throw new Error('Provider temporarily unavailable, retry shortly');
      default:
        throw new Error(data.error);
    }
  }

  return data;
}

Rate Limiting

The API enforces rate limits to ensure fair usage. When you exceed the limit, you'll receive a 429 response.

Default Limits

  • • 300 requests per minute, per API key (sliding window).
  • • Applies identically to test-mode and live-mode keys.

Retry-After Header

On a 429 response, the API returns a Retry-After header containing the number of seconds to wait before retrying. Honour this value rather than retrying immediately.

Need Help?

If you encounter persistent errors or need assistance, contact our support team at [email protected] with the timestamp of the failing request and the returned code value.