Errors
Error handling for the AnySpend Platform API
The AnySpend Platform API uses a consistent, structured error format across all endpoints. Errors follow the Stripe convention -- a JSON object with an error key containing the type, code, human-readable message, and (where applicable) the parameter that caused the error.
Error response format
Every error response has this shape:
json{ "error": { "type": "invalid_request_error", "code": "missing_required_field", "message": "The 'name' field is required.", "param": "name" } }
| Field | Type | Description |
|---|---|---|
type | string | The category of error. Use this for broad error handling logic. |
code | string | A machine-readable error code. Use this for specific error handling. |
message | string | A human-readable description of what went wrong. Safe to display to developers (but not end users). |
param | string or absent | The request parameter that caused the error, if applicable. |
Error types
Error types group errors into broad categories. Use these for top-level error handling.
| Type | HTTP status | Description |
|---|---|---|
invalid_request_error | 400 | The request was malformed or contained invalid parameters. Check the param field for which field caused the issue. |
authentication_error | 401 | The API key is missing, invalid, expired, or revoked. |
permission_error | 403 | The API key is valid but lacks the required permission level for the requested operation. |
not_found_error | 404 | The requested resource does not exist, or does not belong to your organization. |
rate_limit_error | 429 | You have exceeded the rate limit for your authentication tier. |
idempotency_error | 409 | An idempotency key was reused with a different request body. |
api_error | 500 | An unexpected internal error occurred on our side. These are rare and are automatically reported. |
Error codes
Error codes provide specific, machine-readable identifiers for each error condition.
Request validation errors
| Code | Type | HTTP | Description |
|---|---|---|---|
missing_required_field | invalid_request_error | 400 | A required field was not included in the request body. The param field indicates which field is missing. |
invalid_field_value | invalid_request_error | 400 | A field was included but its value is invalid (wrong type, format, or out of range). |
invalid_address | invalid_request_error | 400 | An Ethereum address field is not a valid 0x + 40 hex character address. |
no_updates_provided | invalid_request_error | 400 | A PATCH request was sent with no valid fields to update. |
invalid_status_transition | invalid_request_error | 400 | The requested status change is not allowed (e.g., completing an already-expired session). |
duplicate_resource | invalid_request_error | 409 | A resource with a conflicting unique field already exists. |
Authentication and authorization errors
| Code | Type | HTTP | Description |
|---|---|---|---|
key_missing | authentication_error | 401 | No API key was found in the Authorization or X-API-Key header. |
key_invalid | authentication_error | 401 | The provided API key does not match any key in the system. |
key_expired | authentication_error | 401 | The API key has passed its expires_at timestamp. Create a new key or extend the expiration. |
key_revoked | authentication_error | 401 | The API key was explicitly revoked. Create a new key. |
insufficient_permissions | permission_error | 403 | The API key does not have the permission level required for this operation (e.g., a read key attempting a POST). |
Resource errors
| Code | Type | HTTP | Description |
|---|---|---|---|
resource_not_found | not_found_error | 404 | The requested resource does not exist, or it belongs to a different organization. |
Rate limiting and idempotency errors
| Code | Type | HTTP | Description |
|---|---|---|---|
rate_limit_exceeded | rate_limit_error | 429 | Too many requests in the current time window. The error message includes the retry delay. |
idempotency_conflict | idempotency_error | 409 | An Idempotency-Key header was reused with a different request body than the original request. |
Server errors
| Code | Type | HTTP | Description |
|---|---|---|---|
internal_error | api_error | 500 | An unexpected server error. These are automatically tracked. If this persists, contact support. |
HTTP status codes
| Status | Meaning |
|---|---|
200 | Success -- the resource was retrieved or updated |
201 | Created -- a new resource was successfully created |
400 | Bad Request -- the request was invalid |
401 | Unauthorized -- authentication failed |
403 | Forbidden -- the API key lacks the required permission |
404 | Not Found -- the resource does not exist |
409 | Conflict -- idempotency conflict or duplicate resource |
429 | Too Many Requests -- rate limit exceeded |
500 | Internal Server Error -- something went wrong on our end |
Example error responses
Missing required field
bashcurl -X POST https://platform-api.anyspend.com/api/v1/payment-links \ -H "Authorization: Bearer asp_live_abc123..." \ -H "Content-Type: application/json" \ -d '{ "amount": "10000000" }'
json{ "error": { "type": "invalid_request_error", "code": "missing_required_field", "message": "The 'name' field is required.", "param": "name" } }
Invalid Ethereum address
bashcurl -X POST https://platform-api.anyspend.com/api/v1/payment-links \ -H "Authorization: Bearer asp_live_abc123..." \ -H "Content-Type: application/json" \ -d '{ "name": "Test Link", "token_address": "not-an-address", "chain_id": 8453, "recipient_address": "0xabc123..." }'
json{ "error": { "type": "invalid_request_error", "code": "invalid_address", "message": "The 'token_address' field must be a valid Ethereum address (0x + 40 hex characters).", "param": "token_address" } }
Authentication failure
bashcurl https://platform-api.anyspend.com/api/v1/payment-links \ -H "Authorization: Bearer asp_live_expired_key..."
json{ "error": { "type": "authentication_error", "code": "key_expired", "message": "This API key has expired." } }
Insufficient permissions
bash# A read-only key trying to create a resource curl -X POST https://platform-api.anyspend.com/api/v1/payment-links \ -H "Authorization: Bearer asp_live_readonly_key..." \ -H "Content-Type: application/json" \ -d '{ "name": "Test" }'
json{ "error": { "type": "permission_error", "code": "insufficient_permissions", "message": "This API key does not have 'write' permission." } }
Resource not found
bashcurl https://platform-api.anyspend.com/api/v1/payment-links/pl_nonexistent \ -H "Authorization: Bearer asp_live_abc123..."
json{ "error": { "type": "not_found_error", "code": "resource_not_found", "message": "No payment_link found with id 'pl_nonexistent'." } }
Rate limit exceeded
json{ "error": { "type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded. Retry after 42 seconds." } }
Idempotency conflict
json{ "error": { "type": "idempotency_error", "code": "idempotency_conflict", "message": "An idempotency key was used with a different request body." } }
Handling errors in code
TypeScript / JavaScript
typescriptasync function createPaymentLink(data: PaymentLinkInput) { const response = await fetch( "https://platform-api.anyspend.com/api/v1/payment-links", { method: "POST", headers: { "Authorization": `Bearer ${process.env.ANYSPEND_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify(data), } ); const body = await response.json(); if (!response.ok) { const error = body.error; switch (error.type) { case "invalid_request_error": // Fix the request -- check error.param for the bad field console.error(`Invalid request: ${error.message} (field: ${error.param})`); break; case "authentication_error": // Check your API key -- it may be expired or revoked console.error(`Auth failed: ${error.message}`); break; case "permission_error": // Upgrade the API key permissions console.error(`Insufficient permissions: ${error.message}`); break; case "rate_limit_error": // Back off and retry console.warn(`Rate limited: ${error.message}`); await sleep(5000); return createPaymentLink(data); // retry break; case "not_found_error": console.error(`Not found: ${error.message}`); break; case "idempotency_error": // The idempotency key was reused with different data console.error(`Idempotency conflict: ${error.message}`); break; case "api_error": // Server error -- retry with exponential backoff console.error(`Server error: ${error.message}`); break; } throw new Error(`AnySpend API error: ${error.code} - ${error.message}`); } return body; }
Python
pythonimport requests import time def create_payment_link(data: dict) -> dict: response = requests.post( "https://platform-api.anyspend.com/api/v1/payment-links", headers={ "Authorization": f"Bearer {os.environ['ANYSPEND_API_KEY']}", "Content-Type": "application/json", }, json=data, ) body = response.json() if not response.ok: error = body["error"] error_type = error["type"] if error_type == "rate_limit_error": time.sleep(5) return create_payment_link(data) # retry if error_type == "authentication_error": raise PermissionError(f"Auth failed: {error['message']}") if error_type == "invalid_request_error": raise ValueError( f"Bad request on field '{error.get('param', 'unknown')}': " f"{error['message']}" ) raise Exception(f"AnySpend API error: {error['code']} - {error['message']}") return body
Best practices
Use error.type for broad control flow (e.g., retry on rate_limit_error, fail fast on authentication_error). Use error.code for specific handling within a type.
When error.param is present, you can map it directly to a form field to show inline validation errors in your UI.
rate_limit_error and api_error (500) are transient. Use exponential backoff when retrying:
typescriptasync function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> { for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await fn(); } catch (err: any) { const isRetryable = err.status === 429 || err.status === 500; if (!isRetryable || attempt === maxRetries) throw err; const delay = Math.min(1000 * Math.pow(2, attempt), 10000); await new Promise(resolve => setTimeout(resolve, delay)); } } throw new Error("Unreachable"); }
Client errors like 400, 401, 403, and 404 will not resolve on retry. Fix the request or credentials before retrying.
Always log error.type, error.code, error.message, and error.param together. This gives you the full picture when debugging production issues.