- Live API Reference
- Reference Essentials
- Error Responses
Error Responses
Status codes, error body shapes and error codes returned by the Lab API, with examples.
Most errors return JSON with a machine-readable error field and a human-readable message. A 401 has an empty body, and validation errors raised before the endpoint runs use the RFC 7807 problem-details format, so check the status code before parsing the body.
Error body shapes
| Shape | When | Content-Type |
|---|---|---|
| Empty (0 bytes) | 401 (missing or invalid key, except the Risk Engine), 405 (method not allowed), 404 for a route that does not exist | none |
| RFC 7807 problem details | A query value of the wrong type, a required value the framework checks (for example historical at), or a POST body that cannot be parsed. Also the 404 of an Alerts id that does not exist | application/problem+json |
{error, message} | Most endpoint errors: 400, 404, 422, 503 | application/json |
{status: "ERROR", ...} | 403, 429, the Risk Engine, and validation_error from the Screener and Alerts | application/json |
On a few routes error is a sentence rather than a code, returned as is: No option quote found., Invalid expiry date format. Use yyyy-MM-dd., Invalid type. Use C, P, Call, or Put. (all on /optionquote) and Quota exceeded (429).
401 Unauthorized
A missing or invalid X-Api-Key returns 401 with an empty body: no JSON, no Content-Type, no WWW-Authenticate. The Risk Engine is the exception: it returns {"status": "ERROR", "error": "missing_api_key", ...} or invalid_api_key.
HTTP/1.1 401 Unauthorized
Content-Length: 0400 Bad Request (RFC 7807)
A query value of the wrong type (for example spot=abc) or a missing required value that the framework checks returns a problem-details body. errors names the parameter.
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": { "spot": ["The value 'abc' is not valid."] },
"traceId": "00-...",
"endpoint_version": "2026.09.30"
}Other 400 responses use {error, message} with a code such as invalid_expiry. Each endpoint page lists its codes under Errors.
404 Not Found
{
"error": "symbol_not_found",
"message": "No data for ZZZZQ.",
"endpoint_version": "2026.09.30"
}Most 404 responses use no_data or symbol_not_found. Some routes use their own code: no_option_data, no_coverage, no_snapshot, index_not_found, insufficient_history, expiry_not_found, research_pack_not_available, research_artifact_not_found, and No option quote found. on /optionquote.
403 Forbidden (Tier Restricted)
{
"status": "ERROR",
"endpoint_version": "2026.09.30",
"error": "tier_restricted",
"message": "This endpoint requires the Growth plan or higher.",
"current_plan": "Basic",
"required_plan": "Growth"
}On the Free plan, a stock outside the ~250-symbol tracked universe (/v1/universe) gets 403 with symbol_not_in_free_universe on GEX, Key Levels and Stock Quote. Basic and higher can query any US-listed symbol. The request still counts toward the daily quota.
{
"status": "ERROR",
"endpoint_version": "2026.09.30",
"error": "symbol_not_in_free_universe",
"message": "The Free plan only covers ~250 tracked symbols. KSS isn't one of them. Upgrade to Basic or higher for on-demand access to any US-listed symbol.",
"current_plan": "Free",
"required_plan": "Basic"
}429 Too Many Requests
{
"status": "ERROR",
"endpoint_version": "2026.09.30",
"error": "Quota exceeded",
"message": "You have exceeded your daily API quota of 5 requests on the Free plan. Please upgrade to Basic for a higher limit. Your quota resets at 2026-10-31 00:00:00 UTC.",
"current_plan": "Free",
"limit": 5,
"upgrade_to": "Basic",
"reset_at": "2026-10-31T00:00:00.0000000Z"
}A 429 carries Retry-After (seconds until the quota resets at 00:00 UTC), X-RateLimit-Limit, X-RateLimit-Remaining: 0 and X-RateLimit-Reset (Unix time in seconds). limit is your plan's daily quota. Requests rejected with 403 still count toward the daily quota.
Other status codes
| Status | Error | When |
|---|---|---|
400 | invalid_*, missing_*, or RFC 7807 | A parameter is missing or invalid. Codes are listed on each endpoint page. |
404 | no_data, symbol_not_found, ... | No data for the request. See the 404 codes above. |
405 | - | The route does not accept this HTTP method. Empty body. |
422 | iv_not_found, unsupported_symbol | The value cannot be computed (for example an option price outside the arbitrage-free range), or the symbol is not supported by that operation. |
503 | historical_source_unhealthy, ... | Data is still loading, a source is disabled (the body names a reason), or /health reports a failing dependency. |
504 | - | Gateway timeout on a slow request. The body is an HTML page and Retry-After: 120 is set; retry later. |
Handling errors in code
Branch on the status code before you parse the body. The samples reuse the BASE and HEADERS setup from Authentication.
# One request: print the body, then the HTTP status
curl -s -w "\nStatus: %{http_code}\n" \
-H "X-Api-Key: YOUR_API_KEY" \
"https://lab.flashalpha.com/v1/exposure/levels/AAPL"resp = requests.get(f"{BASE}/v1/exposure/levels/AAPL", headers=HEADERS)
if resp.status_code == 200:
data = resp.json()
elif resp.status_code == 401:
print("Bad API key - check your X-Api-Key header")
elif resp.status_code == 403:
err = resp.json()
print(f"Tier restricted: {err['message']}")
print(f"Current plan: {err['current_plan']}, required: {err['required_plan']}")
elif resp.status_code == 404:
err = resp.json()
print(f"Not found: {err['message']}")
elif resp.status_code == 429:
err = resp.json()
print(f"Quota exceeded on {err['current_plan']} plan")
print(f"Resets at: {err['reset_at']}")
else:
print(f"Unexpected error: {resp.status_code}")const resp = await fetch(`${BASE}/v1/exposure/levels/AAPL`, { headers: HEADERS });
switch (resp.status) {
case 200:
const data = await resp.json();
break;
case 401:
console.error("Bad API key - check your X-Api-Key header");
break;
case 403:
const err403 = await resp.json();
console.error(`Tier restricted: ${err403.message}`);
console.error(`Current: ${err403.current_plan}, required: ${err403.required_plan}`);
break;
case 404:
const err404 = await resp.json();
console.error(`Not found: ${err404.message}`);
break;
case 429:
const err429 = await resp.json();
console.error(`Quota exceeded on ${err429.current_plan} plan`);
console.error(`Resets at: ${err429.reset_at}`);
break;
default:
console.error(`Unexpected error: ${resp.status}`);
}var resp = await client.GetAsync($"{baseUrl}/v1/exposure/levels/AAPL");
var body = await resp.Content.ReadAsStringAsync();
switch ((int)resp.StatusCode)
{
case 200:
var data = JsonDocument.Parse(body).RootElement;
break;
case 401:
Console.WriteLine("Bad API key - check your X-Api-Key header");
break;
case 403:
var err403 = JsonDocument.Parse(body).RootElement;
Console.WriteLine($"Tier restricted: {err403.GetProperty("message")}");
Console.WriteLine($"Current: {err403.GetProperty("current_plan")}, required: {err403.GetProperty("required_plan")}");
break;
case 404:
var err404 = JsonDocument.Parse(body).RootElement;
Console.WriteLine($"Not found: {err404.GetProperty("message")}");
break;
case 429:
var err429 = JsonDocument.Parse(body).RootElement;
Console.WriteLine($"Quota exceeded on {err429.GetProperty("current_plan")} plan");
Console.WriteLine($"Resets at: {err429.GetProperty("reset_at")}");
break;
default:
Console.WriteLine($"Unexpected error: {(int)resp.StatusCode}");
break;
}HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(BASE + "/v1/exposure/levels/AAPL"))
.header("X-Api-Key", API_KEY).build();
HttpResponse<String> resp = client.send(req, BodyHandlers.ofString());
switch (resp.statusCode()) {
case 200:
JsonObject data = JsonParser.parseString(resp.body()).getAsJsonObject();
break;
case 401:
System.out.println("Bad API key - check your X-Api-Key header");
break;
case 403:
JsonObject err403 = JsonParser.parseString(resp.body()).getAsJsonObject();
System.out.println("Tier restricted: " + err403.get("message"));
break;
case 404:
JsonObject err404 = JsonParser.parseString(resp.body()).getAsJsonObject();
System.out.println("Not found: " + err404.get("message"));
break;
case 429:
JsonObject err429 = JsonParser.parseString(resp.body()).getAsJsonObject();
System.out.println("Quota exceeded on " + err429.get("current_plan") + " plan");
break;
default:
System.out.println("Unexpected error: " + resp.statusCode());
}req, _ := http.NewRequest("GET", base+"/v1/exposure/levels/AAPL", nil)
req.Header.Set("X-Api-Key", apiKey)
resp, _ := http.DefaultClient.Do(req)
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
switch resp.StatusCode {
case 200:
var data map[string]interface{}
json.Unmarshal(body, &data)
case 401:
fmt.Println("Bad API key - check your X-Api-Key header")
case 403:
var err map[string]interface{}
json.Unmarshal(body, &err)
fmt.Printf("Tier restricted: %v\n", err["message"])
case 404:
var err map[string]interface{}
json.Unmarshal(body, &err)
fmt.Printf("Not found: %v\n", err["message"])
case 429:
var err map[string]interface{}
json.Unmarshal(body, &err)
fmt.Printf("Quota exceeded on %v plan\n", err["current_plan"])
default:
fmt.Printf("Unexpected error: %d\n", resp.StatusCode)
}Unlock DEX, VEX and CHEX with Basic
Everything in Free, plus:
- DEX, VEX and CHEX exposure
- Full max pain
- ETF & index symbols SPY, QQQ, SPX, VIX
Go full-chain with Growth
Everything in Basic, plus:
- Full-chain GEX Every expiration
- 0DTE analytics
- Exposure summary & AI narrative
- CME futures & live screener
Add history and vol surfaces with Alpha
Everything in Growth, plus:
- Historical API replay Since 2017
- SVI vol surfaces
- VRP analytics
- Greeks surfaces Vanna, charm, volga and speed grids