Comprehensive guide to error codes, debugging, and troubleshooting under the APIM-first security model.
Overview#
Data-opt APIs use standard HTTP status codes and structured error responses. Developer-facing endpoints require an APIM subscription key in the header
markup
when called via the APIM custom domain.Ocp-Apim-Subscription-KeyBase API URL for clients: https://pay.data-opt.com
- Health check path: markup(no subscription required)
GET /healthz - All API routes: markup(APIM rewrites to backend
/api/*markup)/api/*
Admin-only operations in the Data-opt web app use JWT cookies (
markup
) with CSRF and role checks.cb_auth_tokenHTTP Status Codes#
| Code | Name | Description |
|---|---|---|
| 200 | OK | Request successful |
| 201 | Created | Resource created successfully |
| 400 | Bad Request | Invalid request (check parameters) |
| 401 | Unauthorized | Missing or invalid APIM subscription key (developer APIs) or missing/invalid admin session (JWT) |
| 403 | Forbidden | Insufficient scope/permissions or business precondition not met |
| 404 | Not Found | Resource not found |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server error (retry after delay) |
Error Response Format#
All errors follow this structure:
json{ "error": "error_code", "message": "Human-readable error description" }
Additional fields may be included depending on the error type (for example
markup
for rate limiting).resetAtCommon Error Codes#
Authentication & Authorization Errors#
markupunauthorized
#
unauthorized- HTTP: 401
- Message: "Missing or invalid subscription key" (developer APIs)
- Cause: Missing/invalid markupheader
Ocp-Apim-Subscription-Key - Solution: Include a valid APIM subscription key on every developer API request
cURL:
bash# Wrong (no key) curl -X GET "https://pay.data-opt.com/api/inventory" # Correct (with APIM key) curl -X GET "https://pay.data-opt.com/api/inventory" \ -H "Ocp-Apim-Subscription-Key: $APIM_SUBSCRIPTION_KEY"
For admin-only operations (Data-opt UI):
- HTTP: 401
- Message: "JWT authentication failed"
- Cause: Missing/expired admin session cookie markup
cb_auth_token - Solution: Re-authenticate through the web interface
markupforbidden
#
forbidden- HTTP: 403
- Message: "Insufficient scope or not allowed"
- Causes:
- APIM subscription does not include the required scope (e.g., markup,
orders:createmarkup)inventory:write - Attempt to access or mutate resources without proper role/ownership (admin-JWT paths)
- APIM subscription does not include the required scope (e.g.,
- Solutions:
- Ensure your APIM product/subscription grants the required scopes
- For admin routes, confirm you are logged in with appropriate roles
Note: If using Azure Front Door (AFD) as an optional fallback path, APIM also permits requests carrying the AFD-injected internal header
markup
. Clients should not send this header themselves.x-edge-secretBusiness Logic Errors#
markupsplit_required
#
split_required- HTTP: 403
- Message: "Split contract not configured for this merchant"
- Cause: Creating orders without configuring split first
- Solution: Configure your split in the Data-opt Admin UI (Settings → Payments → Split), then retry
markupinventory_item_not_found
#
inventory_item_not_found- HTTP: 400
- Message: "Product not found in inventory"
- Cause: Referencing a SKU or ID that doesn't exist
- Solution: Verify SKU/ID exists in your inventory; list inventory and confirm before ordering
typescript// Check inventory first const items = await listProducts(); const exists = items.some(item => item.sku === 'ITEM-001'); if (!exists) throw new Error('inventory_item_not_found'); // Then create order await createOrder([{ sku: 'ITEM-001', qty: 1 }]);
markupitems_required
#
items_required- HTTP: 400
- Message: "At least one item is required"
- Cause: Creating an order with an empty items array
- Solution: Include at least one item in the order
Validation Errors#
markupinvalid_input
#
invalid_input- HTTP: 400
- Message: "Invalid request parameters"
- Cause: Missing required fields or invalid data types
- Solution: Review endpoint docs for required parameters; validate types and value ranges
Common causes:
- Missing required fields (sku, name, price, etc.)
- Invalid data types (string instead of number)
- Out of range values (negative prices, invalid stock quantity)
typescript// Wrong { "sku": "ITEM-001", // Missing name "priceUsd": "invalid", // Should be number "stockQty": -10 // Should be >= -1 } // Correct { "sku": "ITEM-001", "name": "Product Name", "priceUsd": 25.00, "stockQty": 100 }
Rate Limiting Errors#
markuprate_limited
#
rate_limited- HTTP: 429
- Message: "Rate limit exceeded"
- Response: Includes markuptimestamp (Unix ms)
resetAt - Cause: Too many requests in the time window
- Solution: Implement backoff and retry after markup
resetAt
Example payload:
json{ "error": "rate_limited", "message": "Rate limit exceeded", "resetAt": 1698765432000 }
Implementation:
typescriptasync function makeRequestWithRetry(fn: () => Promise<any>, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (err: any) { if (err?.error === 'rate_limited') { const resetAt = err.resetAt || Date.now() + 60_000; const waitMs = Math.max(0, resetAt - Date.now()); if (i < maxRetries - 1) { await new Promise(r => setTimeout(r, waitMs)); continue; } } throw err; } } }
Rate limit headers (if enabled at gateway):
- markup
X-RateLimit-Limit - markup
X-RateLimit-Remaining - markup
X-RateLimit-Reset
System / Degraded Mode#
markupcosmos_unavailable
#
cosmos_unavailable- HTTP: 200 (Degraded mode)
- Response: Includes markup
degraded: true - Cause: Database temporarily unavailable
- Solution: System operates in degraded mode; data will be persisted when database recovers
json{ "ok": true, "degraded": true, "reason": "cosmos_unavailable", "data": { "...": "..." } }
Handling:
typescriptconst response = await createOrder(items); if (response.degraded) { console.warn('System in degraded mode:', response.reason); // Inform user or queue for reconciliation }
markupplatform_recipient_not_configured
#
platform_recipient_not_configured- HTTP: 400
- Message: "Platform recipient address not set up"
- Cause: Server/platform configuration issue
- Solution: Contact support
Debugging Tips#
1. Use Correlation IDs#
Responses may include an
markup
header. Log this for faster support triage.X-Correlation-IdcURL:
bashcurl -i "https://pay.data-opt.com/api/orders" \ -H "Content-Type: application/json" \ -H "Ocp-Apim-Subscription-Key: $APIM_SUBSCRIPTION_KEY" \ -X POST -d '{...}' # Response headers may include: # X-Correlation-Id: 550e8400-e29b-41d4-a716-446655440000
2. Check Request Format#
typescriptconsole.log('Request:', { url, method: 'POST', headers, body: payload }); const response = await fetch(url, { method: 'POST', headers, body: JSON.stringify(payload) }); const data = await response.json(); if (!response.ok) { console.error('Error:', { status: response.status, error: data }); }
3. Validate Before Sending#
typescriptfunction validateOrder(items: any[]) { if (!Array.isArray(items) || items.length === 0) throw new Error('items_required'); for (const item of items) { if (!item.sku && !item.id) throw new Error('Item must have SKU or ID'); if (!item.qty || item.qty < 1) throw new Error('Invalid quantity'); } }
4. Monitor Your Usage#
Track call/error/rate-limit counts and alert on spikes:
typescriptconst metrics = { calls: 0, errors: 0, rateLimits: 0 }; async function tracked(fn: () => Promise<any>) { metrics.calls++; try { return await fn(); } catch (e: any) { metrics.errors++; if (e?.error === 'rate_limited') metrics.rateLimits++; throw e; } } setInterval(() => console.log('API Metrics:', metrics), 60_000);
Common Scenarios#
Scenario 1: Authentication Failure (Developer APIs)#
- Error: markup(401)
unauthorized - Steps:
- Ensure markupis present and valid
Ocp-Apim-Subscription-Key - Confirm your subscription is active
- Retry the request
- Ensure
Scenario 2: Missing Scope#
- Error: markup(403)
forbidden - Steps:
- Check required scope in the API docs (e.g., markup)
orders:create - Verify your APIM product/subscription includes that scope
- Request access or upgrade if necessary
- Check required scope in the API docs (e.g.,
Scenario 3: Split Not Configured#
- Error: markup(403)
split_required - Steps:
- In Admin UI, configure split (Settings → Payments → Split)
- Retry order creation
Scenario 4: Rate Limited#
- Error: markup(429)
rate_limited - Steps:
- Read markupheaders and
X-RateLimit-*markupresetAt - Back off until reset time
- Implement client-side throttling
- Read
Payment & Checkout Failure Codes (markupPORTAL_*
)#
PORTAL_*When inspecting receipt status (
markup
) or receiving status webhooks (GET /api/receipts/statusmarkup
), failed transactions include structured, branded failure codes (receipt.status_updatedmarkup
), categories (failureCodemarkup
), human-readable descriptions (failureCategorymarkup
), and merchant remediation actions (failureReasonmarkup
).failureAction1. Card & Bank Declines (markupcategory: "card_decline"
)#
category: "card_decline"| Custom Error Code | Description | Suggested Merchant Action |
|---|---|---|
markup | The card/account was declined due to insufficient available funds. | Ask the customer to retry with another card or alternate payment method. |
markup | The payment card was declined by the card issuer. | Ask the customer to contact their issuing bank to approve the transaction. |
markup | The payment card has expired. | Customer must enter an active card with a valid expiration date. |
markup | The 3- or 4-digit security code (CVC/CVV) is incorrect. | Customer must re-enter the correct security code from the card. |
markup | The card number is invalid or failed checksum validation. | Customer must re-enter a valid 16-digit card number. |
markup | The bank declined with a generic "Do Not Honor" code. | Customer must authorize crypto/online debit charges with their bank. |
markup | The charge was blocked by automated risk screening algorithms. | Advise customer to use a verified payment method or complete ID verification. |
markup | 3D Secure verification (OTP/bank challenge) failed or cancelled. | Customer should retry and approve the SMS/banking app prompt promptly. |
markup | Banking institution policy restricts digital asset purchases. | Customer should switch to a crypto-friendly financial institution. |
2. Compliance & Identity Verification (markupcategory: "compliance"
)#
category: "compliance"| Custom Error Code | Description | Suggested Merchant Action |
|---|---|---|
markup | Basic identity verification (Level 0 Name & Address) is required. | Customer must submit their legal name and residential address. |
markup | Level 1 identity step-up (Date of Birth & SSN/Tax ID) is required. | Customer must provide DOB and SSN/Tax ID to proceed. |
markup | Level 2 document verification (Photo ID & Selfie) is required. | Customer must complete document scan via Stripe verification modal. |
markup | Uploaded identity document photo was blurry, expired, or unreadable. | Prompt customer to re-scan their ID in good lighting. |
markup | Submitted date of birth does not match verified identity records. | Customer must ensure DOB matches official government ID. |
markup | Customer or IP matched restricted sanctions/AML lists. | Transaction cannot be processed under international compliance laws. |
markup | Customer does not meet the legal minimum age of 18. | User is ineligible to transact. |
markup | Customer is located in an unsupported jurisdiction (e.g. NY, HI). | Region is restricted under state licensing requirements. |
3. Purchase Limits (markupcategory: "limits"
)#
category: "limits"| Custom Error Code | Description | Suggested Merchant Action |
|---|---|---|
markup | Order total exceeds customer's current KYC tier limit. | Direct customer to complete identity verification to increase limit. |
markup | Order total exceeds single-transaction maximum. | Customer should split the order or pay via bank transfer (ACH). |
markup | Order total is below minimum processing threshold. | Order total must meet the minimum checkout amount. |
4. Blockchain & Web3 (markupcategory: "blockchain"
)#
category: "blockchain"| Custom Error Code | Description | Suggested Merchant Action |
|---|---|---|
markup | Customer wallet lacks required crypto or gas tokens. | Customer should top up wallet balance or switch to card. |
markup | Customer rejected signature prompt in Web3 wallet. | Customer may retry and approve the transaction in wallet. |
markup | Token exchange rate moved beyond slippage tolerance. | Refresh quote to obtain updated conversion rates. |
markup | Smart contract execution reverted on-chain. | Review transaction parameters or contact technical support. |
markup | Travel Rule wallet ownership challenge signature failed. | Customer must sign challenge with the exact destination wallet. |
5. Session & Abandonment (markupcategory: "session"
)#
category: "session"| Custom Error Code | Description | Suggested Merchant Action |
|---|---|---|
markup | Customer closed portal before completing checkout. | Send an abandoned checkout recovery email to customer. |
markup | Checkout session expired after remaining inactive. | Generate a new checkout session link. |
markup | Customer clicked cancel on the payment modal. | Customer may restart checkout when ready. |
Getting Help#
If you encounter persistent errors:
- Documentation: Review the API Reference
- Include Details in Reports:
- X-Correlation-Id (if present)
- Endpoint, method, headers (redact secrets)
- Full error payload and status code
- Steps to reproduce
- Contact Support with the above information
Next Steps: