Errors & Troubleshooting
The WSO API returns structured JSON error responses with standard HTTP status codes, programmatic error codes, and descriptive messages.
Standard Error Envelope
Every non-2xx response returns an application/json error body following this schema:
{
"error": "invalid_parameter",
"message": "expiration must be in YYYY-MM-DD format",
"status": 400,
"timestamp": "2026-09-17T20:00:00Z",
"request_id": "c7a9b1c0-4f2e-4d1a-8c3b-5e6f7a8b9c0d"
}Error Codes Reference
Programmatic error codes returned by the API, generated directly from the OpenAPI specification error catalogue:
| Status | Error Code | Description |
|---|---|---|
| 403 | account_suspended | This account cannot use the API right now. Contact us for details. |
| 403 | endpoint_not_allowed | This endpoint is not available for your account. |
| 503 | endpoint_unavailable | This endpoint is temporarily unavailable. Retry later. |
| 503 | entitlement_unavailable | Account entitlement database or subscription cache is temporarily unreachable. |
| 500 | internal_error | An unexpected server error occurred while processing the request. Retry with exponential backoff. |
| 403 | ip_blocked | Requests from this network are not accepted. Contact us if you believe this is an error. |
| 401 | invalid_auth | Authorization header format is invalid. Must use the 'Bearer wso_live_...' scheme with a separating space. |
| 400 | invalid_body | Request body is malformed JSON, unreadable, contains unrecognized fields, or exceeds 1 MiB. |
| 401 | invalid_key | API key does not exist or was revoked. Generate a new key in the developer dashboard. |
| 400 | invalid_page | Pagination page parameter exceeds allowable bounds (e.g. page > 100). |
| 400 | invalid_parameter | Query or path parameter failed format, type, or constraint validation (e.g. malformed symbol or bad limit). |
| 400 | invalid_range | Numerical or date range constraint violated (e.g. min exceeds max, or date range exceeds allowable span). |
| 503 | maintenance | The API is undergoing maintenance. Retry after the Retry-After interval. |
| 405 | method_not_allowed | HTTP method is not supported on this endpoint. Consult Allow response header. |
| 401 | missing_auth | Authorization header is missing or empty. Provide an API key via Bearer authentication. |
| 400 | missing_symbol | Required equity symbol or option contract parameter was omitted. |
| 400 | missing_symbols | Required symbols parameter for batch request was omitted or empty. |
| 404 | not_found | Endpoint route does not exist or requested option contract was not found in active chain records. |
| 403 | origin_forbidden | Request rejected because origin authentication failed. All requests must route through the configured Cloudflare edge proxy. |
| 400 | out_of_range | Requested date window exceeds retention horizon (e.g. 7-day flow retention) or market-wide window cap. |
| 403 | plan_upgrade_required | The requested endpoint requires a higher tier plan than your current active plan. Response includes required_plan. |
| 503 | query_timeout | Database execution budget exceeded for complex query or statement timeout. Retry with narrower parameters. |
| 400 | query_too_broad | Market-wide query is insufficiently constrained; narrow search filters or specify symbol. |
| 429 | rate_limited | Account rate limit exceeded (or unauthenticated/public IP rate limit exceeded). Respect the Retry-After header before retrying. |
| 503 | service_unavailable | Underlying market data cache, database, or verifier is temporarily unavailable. |
| 403 | subscription_inactive | Account subscription is inactive, unpaid, or canceled. Update payment methods in the dashboard. |
| 403 | subscription_required | An active paid subscription plan is required to access data endpoints. Subscribe via the dashboard. |
| 404 | symbol_not_found | Underlying equity ticker symbol is unrecognized or has no options market data coverage. |
| 429 | too_many_auth_failures | Client IP temporarily throttled due to repeated authentication failures. |
| 429 | too_many_concurrent_requests | Account in-flight concurrency ceiling exceeded. Reduce concurrent calls or wait for active requests to complete. |
| 400 | too_many_filters | Screener request exceeds maximum permitted filter predicates count (up to 50 filters). |
| 400 | too_many_symbols | Batch request exceeds maximum permitted symbols limit (up to 20 symbols). |
| 403 | trial_ended | Your trial has ended. Subscribe to keep using the API. |
| 502 | upstream_error | Upstream market data provider returned an error and fallback stored data is unavailable. |
| 403 | wrong_credential_type | A Firebase ID token or dashboard session credential was sent to /v1; use an API key (wso_live_...) instead. Legacy wsk_ keys return invalid_key (401). |
Common Problems & Solutions
1. HTTP 401: Authentication Errors
Ensure your HTTP header contains Authorization: Bearer wso_live_.... Notice the space between Bearer and the key. Missing headers return missing_auth. Malformed headers return invalid_auth. Invalid or revoked keys (including legacy wsk_ keys) return invalid_key (401). If a Firebase ID token or dashboard credential is sent to /v1, the API returns wrong_credential_type (403).
2. HTTP 403: Forbidden Access & Plans
Data endpoints require an active plan or active trial. A key whose account has never had a plan gets HTTP 403 with code subscription_required; an account whose trial has expired receives trial_ended; an inactive or canceled plan returns subscription_inactive; and a request attempting to access endpoints outside your plan tier returns plan_upgrade_required. Direct requests bypassing the Cloudflare edge proxy return origin_forbidden. /v1/auth/verify and /v1/usage still answer with HTTP 200, so you can check your key status.
3. HTTP 429: Rate Limits & Backoff
Always inspect the Retry-After header on HTTP 429 responses. Wait the indicated number of seconds before retrying:
import time
import requests
def request_with_retry(url, headers, max_retries=3):
for _ in range(max_retries):
resp = requests.get(url, headers=headers)
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 1))
print(f"Rate limited (429). Retrying in {retry_after}s...")
time.sleep(retry_after)
continue
return resp
raise Exception("Max retries exceeded")4. HTTP 404: not_found vs symbol_not_found
The API returns not_found when an endpoint path doesn't exist or an individual option contract cannot be found. If the underlying equity ticker symbol itself is unknown or unsupported, the API returns symbol_not_found.