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:
{
"error": "Validation failed: amount: Required",
"code": "VALIDATION_ERROR",
"details": {
"fieldErrors": { "amount": ["Required"] },
"formErrors": []
}
}| Parameter | Type | Description |
|---|---|---|
error | string | A human-readable description of the error |
code | string | A machine-readable error code (UPPERCASE_SNAKE_CASE) |
details | object | Optional. Present on VALIDATION_ERROR — contains zod fieldErrors and formErrors. |
HTTP Status Codes
The API uses standard HTTP status codes to indicate success or failure:
Request succeeded
Resource created successfully
Invalid request parameters or body
Missing or invalid API key
API key lacks required permissions
Requested resource does not exist
Request was well-formed but failed validation or business rules (e.g. unsupported currency, no provider available, amount below minimum).
Rate limit exceeded. Slow down requests.
Something went wrong on our end
Error Codes
Common error codes you may encounter:
| Code | Description |
|---|---|
MISSING_API_KEY | No X-API-Key header was provided. 401. |
INVALID_API_KEY | API key does not exist. 401. |
REVOKED_API_KEY | API key has been revoked from the dashboard. 401. |
EXPIRED_API_KEY | API key expiry date has passed. 401. |
ACCOUNT_SUSPENDED | Owning account is suspended. 403. |
INSUFFICIENT_SCOPE | API key lacks the scope required for this endpoint. 403. |
INVALID_BODY | Request body could not be parsed as JSON. 400. |
VALIDATION_ERROR | Body parsed but failed schema validation. The details field contains per-field errors. 422. |
RATE_LIMITED | Per-API-key rate limit exceeded. Honour the Retry-After header. 429. |
NO_PROVIDER_AVAILABLE | No 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_MAINTENANCE | The 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_ERROR | A downstream provider returned an unrecoverable error while creating the payment. 502. |
PROVIDER_UPSTREAM_UNAVAILABLE | The selected provider timed out or is currently unreachable. Safe to retry. 503. |
WALLET_NOT_CONFIGURED | Crypto payment requested but the merchant has no payout wallet configured for the requested currency. 422. |
DIRECT_MODE_UNSUPPORTED_CURRENCY | direct: true was passed with a currency outside the supported set (BRL, ARS, RUB). 422. |
CHINA_PAY_REQUIRES_CNY | payment_method=ALIPAY or WXPAY was passed without currency=CNY. |
CHINA_PAY_AMOUNT_TOO_LOW | Order amount is below the 1 CNY per-order minimum for Alipay/WeChat Pay. |
CHINA_PAY_AMOUNT_TOO_HIGH | Order 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_RANGE | IDR 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_REQUIRED | currency=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_REQUIRED | currency=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_SUPPORTED | Mastercard 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_MINIMUM | The order is below the Mastercard per-order minimum (EUR 2). 422. |
AMOUNT_ABOVE_MAXIMUM | The 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_MAINTENANCE | Returned for mastercard: true while Mastercard is temporarily in maintenance. Retrying later can succeed. 503. |
CARD_DIRECT_MAINTENANCE | Returned for card_direct: true while direct card payments are temporarily in maintenance. Retrying later can succeed. 503. |
MASTERCARD_NOT_ENABLED | This site is not approved for Mastercard yet. Request it per site under Settings → Payment Methods; we review the site and enable it. 403. |
MASTERCARD_DISABLED | The 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_KEY | Idempotency-Key header was provided but does not match the allowed format (1–255 chars from A-Z a-z 0-9 _ -). |
IDEMPOTENCY_KEY_REUSED | An Idempotency-Key was reused within 24h with a different request body. Use a fresh key for new requests. |
GIFT_CARD_AMOUNT_UNSUPPORTED | gift_card_amount is not one of 5, 10, 15, 20, 25, 30, 40, 50, 75, 100, 250, 500. 422. |
GIFT_CARD_AMOUNT_MISMATCH | Invoice 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_ONLY | gift_card_amount was sent with a non-USD currency. 422. |
CASHAPP_US_ONLY | Cash App checkout was started from a non-US connection. Test checkouts skip this. 403. |
INTERNAL_ERROR | An unexpected error occurred. 500. |
INVALID_ALLOWED_METHODS | allowed_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_CONFLICT | allowed_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_ENABLED | Every 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_ALLOWED | Returned 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:
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.