Error Handling#
Understanding and handling errors in the AuxCORE API.
Overview#
The AuxCORE API uses conventional HTTP response codes to indicate the success or failure of an API request. Errors include helpful information to help you diagnose and resolve issues.
HTTP Status Codes#
| Code | Status | Meaning |
|---|---|---|
| 200 | OK | Request succeeded (also: a payment accepted but not yet settled, or one that needs a 3DS / hosted redirect) |
| 201 | Created | Resource created successfully |
| 400 | Bad Request | Invalid request parameters (VALIDATION_ERROR, PCI_SCOPE_VIOLATION, …) |
| 401 | Unauthorized | Missing or invalid API key or token |
| 402 | Payment Required | POST /api/v1/public/transaction was declined |
| 403 | Forbidden | Insufficient permissions, an SDK key used where it is not allowed, or a service not in the tenant's subscription (SERVICE_UNAVAILABLE) |
| 404 | Not Found | Resource or tenant doesn't exist, or the feature is turned off for the tenant/merchant |
| 409 | Conflict | Resource conflict, or an idempotent request still in progress |
| 422 | Unprocessable Entity | Idempotency key reused with a different body |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server error, or a downstream service unreachable |
| 502 | Bad Gateway | Upstream processor or service error |
| 503 | Service Unavailable | A downstream service is temporarily down (SERVICE_UNAVAILABLE) |
| 504 | Gateway Timeout | A downstream service timed out (GATEWAY_TIMEOUT) |
Error Response Structure#
Services return errors in this format:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message"
}
}Fields#
| Field | Type | Description |
|---|---|---|
success |
boolean | Always false for errors |
error.code |
string | Machine-readable error code |
error.message |
string | Human-readable description. Validation messages name the offending field |
Two Public API exceptions to plan for:
- API key failures on
/api/v1/public/*returnerroras a string plus amessage, e.g.{ "success": false, "error": "API key validation failed", "message": "..." }. See Authentication. - Payment results from
POST /api/v1/public/transactionuse a legacy envelope:{ "success", "status": "success" | "pending" | "declined", "status_code", "message", "data": { "Status", "ResponseCode", ... } }.
Branch on error.code when error is an object, and on the HTTP status otherwise.
Common Error Codes#
Authentication Errors#
UNAUTHORIZED#
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication required. Provide a Bearer token, auth cookie, or x-api-key header."
}
}Solution: Send a valid JWT or API key. Also returned for wrong login credentials (Invalid credentials).
TOKEN_EXPIRED#
{
"success": false,
"error": {
"code": "TOKEN_EXPIRED",
"message": "Access token has expired. Please refresh your token."
}
}Solution: Call POST /api/v1/public/refreshToken (or POST /api/v1/auth/refresh) and retry with the new token.
INVALID_TOKEN / INVALID_API_KEY#
Returned with 401 for a malformed or forged JWT (INVALID_TOKEN) or an unknown/deactivated API key on an Admin API route (INVALID_API_KEY).
Solution: Re-authenticate, or check the key on /settings/developer/api-keys.
TENANT_REQUIRED#
{
"success": false,
"error": {
"code": "TENANT_REQUIRED",
"message": "Tenant identification required. Provide X-Tenant-ID header or authenticate with a valid JWT."
}
}Solution: Sent with 400 when a request carries neither a JWT, an API key, nor a resolvable tenant. Include X-Tenant-ID, or authenticate — API-key calls resolve the tenant from the key.
TENANT_NOT_FOUND#
{
"success": false,
"error": {
"code": "TENANT_NOT_FOUND",
"message": "Tenant acme-prod not found"
}
}Solution: Verify the X-Tenant-ID you send (404). With an API key you can omit the header entirely.
Validation Errors#
VALIDATION_ERROR#
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Email and password are required"
}
}Solution: Fix the request and retry (400).
PCI_SCOPE_VIOLATION#
{
"success": false,
"error": {
"code": "PCI_SCOPE_VIOLATION",
"message": "PublicAPI.transaction: raw card data is not permitted in this environment. Collect card data via the PCI iframe/SDK and submit a pciToken only."
}
}Solution: Send a pciToken (pci_tok_…) from the PCI SDK or POST /api/v1/public/tokenize-card instead of CardNumber / CVV.
Resource Errors#
NOT_FOUND#
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Transaction with id 550e8400-e29b-41d4-a716-446655440020 not found"
}
}Solution: Verify the resource ID exists and belongs to your merchant.
CONFLICT#
{
"success": false,
"error": {
"code": "CONFLICT",
"message": "Transaction already voided"
}
}Solution: 409 — check the resource state before retrying.
Payment Errors#
Declined payment#
A decline from POST /api/v1/public/transaction is HTTP 402:
{
"success": false,
"status": "declined",
"status_code": 402,
"message": "Insufficient funds",
"data": {
"TransactionId": "550e8400-e29b-41d4-a716-446655440021",
"Status": "9",
"Amount": 50.00,
"ResponseCode": "202",
"ResponseMessage": "Insufficient funds",
"AvsResult": "N",
"CvvResult": "M"
}
}ResponseCode uses the NMI convention: 100 approved, 2xx issuer declines (e.g. 201 do not honor, 202 insufficient funds, 223 expired card, 225 invalid CVV), 300 rejected by the gateway, 400 processor error. Do not retry a decline with the same card.
A 200 with "status": "pending" is not a failure: either the payment was accepted and awaits settlement (ACH), or requires_action is true and the payer must be sent to data.RedirectUrl.
GATEWAY_ERROR#
{
"success": false,
"error": {
"code": "GATEWAY_ERROR",
"message": "CoinVoyage did not return a pay order"
}
}Solution: Returned with 502 when an upstream processor fails. Retry with exponential backoff.
Rate Limiting#
RATE_LIMITED#
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests from this IP. Try again later."
}
}Solution: 429 — wait for the number of seconds in the Retry-After header. The general API limiter answers 429 with a plain-text body (Too many requests, please try again later), so check the status code, not just the JSON. See Rate Limits.
Permission Errors#
FORBIDDEN#
{
"success": false,
"error": {
"code": "FORBIDDEN",
"message": "API key is not authorized for the requested merchant."
}
}Solution: 403 — a merchant-scoped key named another merchant, an SDK key was used on an Admin API route, or the user lacks the permission.
Idempotency Errors#
| Code | Status | Meaning |
|---|---|---|
INVALID_IDEMPOTENCY_KEY |
400 | Key shorter than 16 or longer than 255 characters |
IDEMPOTENCY_KEY_REQUIRED |
400 | Hosted-checkout / WooCommerce payment sent without a key |
IDEMPOTENCY_KEY_REUSED |
422 | Same key, different request body |
IDEMPOTENCY_REQUEST_IN_PROGRESS |
409 | The first request with this key has not finished yet |
IDEMPOTENCY_UNAVAILABLE |
503 | Hosted-checkout payment: the idempotency store is unreachable, so the payment is refused — retry |
Handling Errors in Code#
JavaScript/Node.js#
const response = await fetch('https://app.auxcore.net/api/v1/public/transaction', {
method: 'POST',
headers: {
'x-api-key': apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify(transactionData)
});
if (response.status === 429) {
const retryAfter = Number(response.headers.get('retry-after') || 60);
await sleep(retryAfter * 1000);
return retryRequest();
}
const result = await response.json();
if (response.status === 402 || result.status === 'declined') {
// Card declined — show result.message to the payer, do not retry
return { declined: true, code: result.data?.ResponseCode, message: result.message };
}
if (!response.ok) {
// error is an object on most routes, a string on API-key failures
const code = typeof result.error === 'string' ? result.error : result.error?.code;
const message = result.message || result.error?.message || 'An error occurred';
switch (code) {
case 'TOKEN_EXPIRED':
await refreshToken();
return retryRequest();
case 'VALIDATION_ERROR':
case 'PCI_SCOPE_VIOLATION':
console.error(`Fix the request: ${message}`);
break;
case 'GATEWAY_ERROR':
return retryWithBackoff();
default:
console.error('API Error:', code, message);
}
throw new Error(message);
}
if (result.status === 'pending' && result.requires_action) {
window.location.href = result.data.RedirectUrl;
}
return result.data;Python#
import requests
import time
def process_payment(transaction_data, api_key):
url = 'https://app.auxcore.net/api/v1/public/transaction'
headers = {
'x-api-key': api_key,
'Content-Type': 'application/json'
}
response = requests.post(url, json=transaction_data, headers=headers)
if response.status_code == 429:
time.sleep(int(response.headers.get('Retry-After', 60)))
return process_payment(transaction_data, api_key)
result = response.json()
if response.status_code == 402 or result.get('status') == 'declined':
print(f"Transaction declined: {result.get('message')}")
return None
if not response.ok:
error = result.get('error')
code = error if isinstance(error, str) else (error or {}).get('code', 'UNKNOWN_ERROR')
message = result.get('message') or (error or {}).get('message', 'An error occurred')
if code == 'GATEWAY_ERROR':
return retry_with_backoff(transaction_data, api_key)
raise Exception(f"{code}: {message}")
return result.get('data')Retry Logic#
When to Retry#
✅ DO retry these errors:
GATEWAY_ERROR- Upstream processor failureRATE_LIMITED/ any429- Wait forRetry-After500/503- Service temporarily down- Network timeouts — with an
Idempotency-Keywhere supported
❌ DON'T retry these errors:
VALIDATION_ERROR/PCI_SCOPE_VIOLATION- Fix the request firstUNAUTHORIZED/TOKEN_EXPIRED- Re-authenticate or refresh firstNOT_FOUND- Resource doesn't existFORBIDDEN- Insufficient permissions- Declined transactions (
402) - Don't retry declined cards
Exponential Backoff#
async function retryWithBackoff(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
// Don't retry non-retryable errors
if (!isRetryable(error)) throw error;
// Wait with exponential backoff
const delay = Math.min(1000 * Math.pow(2, i), 10000); // Max 10s
await sleep(delay);
}
}
}
function isRetryable(error) {
const retryableCodes = [
'GATEWAY_ERROR',
'SERVICE_UNAVAILABLE',
'RATE_LIMITED'
];
return retryableCodes.includes(error.code) || error.status >= 500;
}Idempotency#
The Admin API payment routes — POST /api/v1/transactions/sale, /auth, /token-sale, /ach-sale and /crypto-sale — accept an Idempotency-Key header (16–255 characters). A repeat with the same key and body within 24 hours returns the stored response instead of charging again.
curl -X POST https://app.auxcore.net/api/v1/transactions/sale \
-H "Authorization: Bearer TOKEN" \
-H "Idempotency-Key: 6f1c2e9a-4b7d-4e2a-9c1f-0a8b3d5e7f21" \
-H "Content-Type: application/json" \
-d '{"merchantId":"550e8400-e29b-41d4-a716-446655440000","amount":100.00,"paymentMethod":"card","pciToken":"pci_tok_..."}'Note:
POST /api/v1/public/transactiondoes not currently passIdempotency-Keythrough, so the header has no effect on the Public API. Look up the transaction (GET /api/v1/public/transaction-list) before retrying a Public API sale after a timeout.
Logging & Monitoring#
What to Log#
✅ DO log:
- Request IDs for tracking
- Error codes and messages
- User actions that caused errors
- Timestamps
- Retry attempts
❌ DON'T log:
- Full card numbers
- CVV codes
- Raw API tokens
- Customer passwords
Example Log Entry#
{
"timestamp": "2026-01-28T12:34:56Z",
"level": "error",
"requestId": "req_abc123",
"errorCode": "VALIDATION_ERROR",
"errorMessage": "Invalid card number",
"field": "card.number",
"userId": "user_123",
"action": "process_payment",
"retryCount": 0
}Testing Errors#
Trigger Specific Errors#
| Input | Result |
|---|---|
pciToken for card 4000000000000002 (Sandbox) |
Declined — HTTP 402 (may vary by processor) |
No x-api-key header |
401 API key required |
Raw CardNumber where raw card data is not allowed |
400 PCI_SCOPE_VIOLATION |
| Expired JWT on an Admin API route | 401 TOKEN_EXPIRED |
Specific decline reasons (expired card, invalid CVV, fraud) depend on the sandbox gateway configured for the merchant.
Best Practices#
✅ DO:#
- Handle all error cases gracefully
- Show user-friendly messages to customers
- Log errors for debugging
- Implement retry logic for transient errors
- Use idempotency keys for critical operations
- Monitor error rates to detect issues early
- Validate data before sending to API
❌ DON'T:#
- Don't expose raw errors to end users
- Don't retry indefinitely - set max attempts
- Don't ignore errors - always handle them
- Don't retry validation errors without fixing them
- Don't log sensitive data
Complete Error Code Reference#
Next Steps#
Need help? Contact support@auxcore.net