Error Codes
All errors return a consistent JSON structure with a machine-readablecode field for programmatic handling and a human-readable message field for debugging.
{
"error": {
"type": "invalid_request_error",
"message": "Amount must be between 0.01 and 10000",
"param": "amount",
"code": "amount_out_of_range"
}
}
Error Types
| Type | Description |
|---|---|
invalid_request_error | The request was malformed or missing required parameters. |
authentication_error | Invalid, expired, or missing API key. |
authorization_error | Valid API key but insufficient permissions for the requested resource. |
not_found_error | The requested resource does not exist. |
conflict_error | The request conflicts with existing state (e.g., duplicate idempotency key, cancelling a confirmed intent). |
rate_limit_error | Too many requests. Back off and retry. |
api_error | An internal error on our side. Retry with exponential backoff. |
Payment Intent Errors
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
amount_out_of_range | 400 | Amount is below 0.01orabove10,000. | Adjust the amount to within the allowed range. |
invalid_currency | 400 | Currency is not usd. | Set currency to "usd". |
invalid_merchant | 400 | Merchant ID is malformed. | Check the merchant ID format (mer_*). |
merchant_not_found | 404 | Merchant does not exist or is not accessible to this API key. | Verify the merchant ID and API key permissions. |
merchant_not_active | 422 | Merchant account is suspended or deactivated. | Contact support or check merchant status in the dashboard. |
intent_not_cancellable | 409 | Payment intent is not in awaiting_payment status. | Only intents with status awaiting_payment can be cancelled. |
idempotency_key_conflict | 409 | The idempotency key was used with different parameters. | Use a unique idempotency key for each distinct request. |
ttl_out_of_range | 400 | TTL is below 60 or above 3600 seconds. | Set TTL between 60 and 3600. |
Authentication Errors
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
missing_api_key | 401 | No Authorization header provided. | Include Authorization: Bearer sk_* in request headers. |
invalid_api_key | 401 | API key is malformed or does not exist. | Check that the key is correct and hasn’t been revoked. |
revoked_api_key | 401 | API key has been revoked. | Generate a new API key from the dashboard. |
environment_mismatch | 403 | Using a test key against production resources or vice versa. | Use sk_test_* for sandbox and sk_live_* for production. |
Webhook Errors
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
invalid_webhook_url | 400 | Webhook URL is not a valid HTTPS URL. | Provide a valid HTTPS URL. |
webhook_unreachable | 422 | Could not reach the webhook URL during registration. | Ensure your endpoint is publicly accessible and returns 200. |
Rate Limit Errors
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
rate_limit_exceeded | 429 | Too many requests in the current window. | Wait and retry. Check X-RateLimit-Reset header for when the window resets. |
Handling Errors
async function createPaymentIntent(amount, merchantId) {
const response = await fetch('https://api.stablegenius.co/v1/payment-intents', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ amount, currency: 'usd', merchant_id: merchantId }),
});
if (!response.ok) {
const { error } = await response.json();
switch (error.type) {
case 'authentication_error':
// API key issue — check configuration
throw new Error(`Auth failed: ${error.message}`);
case 'rate_limit_error':
// Back off and retry
const resetAt = response.headers.get('X-RateLimit-Reset');
await sleep(resetAt * 1000 - Date.now());
return createPaymentIntent(amount, merchantId); // Retry
case 'api_error':
// Our issue — retry with backoff
await sleep(1000);
return createPaymentIntent(amount, merchantId);
default:
throw new Error(`API error: ${error.code} — ${error.message}`);
}
}
return response.json();
}
import requests
import time
def create_payment_intent(amount, merchant_id):
response = requests.post(
'https://api.stablegenius.co/v1/payment-intents',
headers={
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json',
},
json={'amount': amount, 'currency': 'usd', 'merchant_id': merchant_id},
)
if not response.ok:
error = response.json().get('error', {})
error_type = error.get('type')
if error_type == 'rate_limit_error':
reset_at = int(response.headers.get('X-RateLimit-Reset', 0))
time.sleep(max(reset_at - time.time(), 1))
return create_payment_intent(amount, merchant_id)
if error_type == 'api_error':
time.sleep(1)
return create_payment_intent(amount, merchant_id)
raise Exception(f"API error: {error.get('code')} — {error.get('message')}")
return response.json()
Best Practices
- Always check
error.codefor programmatic handling, noterror.message(messages may change). - Retry on
429and500with exponential backoff. Use theX-RateLimit-Resetheader for rate limits. - Never retry on
400,401,403,404— these indicate issues with your request, not transient failures. - Log the full error response for debugging. Include the
error.type,error.code, anderror.paramfields.

