Comprehensive guide to error codes, debugging, and troubleshooting under the APIM-first security model.
Overview#
BasaltSurge 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://surge.basalthq.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 BasaltSurge 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://surge.basalthq.com/api/inventory" # Correct (with APIM key) curl -X GET "https://surge.basalthq.com/api/inventory" \ -H "Ocp-Apim-Subscription-Key: $APIM_SUBSCRIPTION_KEY"
For admin-only operations (BasaltSurge 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 BasaltSurge 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://surge.basalthq.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
).failureActionStripe failures can additionally include
markup
(the original structured Stripe code) and providerErrorCodemarkup
(a providerRequestIdmarkup
support reference). These are nullable and are derived from signed events or server observations tied to the receipt's Stripe session. req_...markup
remains the branded merchant code. Unknown causes use a generic merchant code without inventing a provider error. All failure fields and provider references are failureCodemarkup
for successful/nonfailure statuses. Browser checkout telemetry alone does not trigger a canonical failure webhook. See webhook status and failure semantics.null1. 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 | Stripe returned markup (or the legacy markup code). The specific blocking reason may not be disclosed. | Do not retry the transaction. Contact support with the receipt/session and provider request references. Do not assume fraud or request additional identity verification solely from this code. |
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: