Developer dashboard

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:

json
{
  "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/* return error as a string plus a message, e.g. { "success": false, "error": "API key validation failed", "message": "..." }. See Authentication.
  • Payment results from POST /api/v1/public/transaction use 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#

json
{
  "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#

json
{
  "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#

json
{
  "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#

json
{
  "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#

json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Email and password are required"
  }
}

Solution: Fix the request and retry (400).

PCI_SCOPE_VIOLATION#

json
{
  "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#

json
{
  "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#

json
{
  "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:

json
{
  "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#

json
{
  "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#

json
{
  "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#

json
{
  "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#

javascript
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#

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 failure
  • RATE_LIMITED / any 429 - Wait for Retry-After
  • 500 / 503 - Service temporarily down
  • Network timeouts — with an Idempotency-Key where supported

❌ DON'T retry these errors:

  • VALIDATION_ERROR / PCI_SCOPE_VIOLATION - Fix the request first
  • UNAUTHORIZED / TOKEN_EXPIRED - Re-authenticate or refresh first
  • NOT_FOUND - Resource doesn't exist
  • FORBIDDEN - Insufficient permissions
  • Declined transactions (402) - Don't retry declined cards

Exponential Backoff#

javascript
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.

bash
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/transaction does not currently pass Idempotency-Key through, 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#

json
{
  "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#

View all error codes →


Next Steps#


Need help? Contact support@auxcore.net