API Error Responses: Status Codes and Error Bodies | FlashAlpha Lab API
  1. 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 existnone
RFC 7807 problem detailsA 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 existapplication/problem+json
{error, message}Most endpoint errors: 400, 404, 422, 503application/json
{status: "ERROR", ...}403, 429, the Risk Engine, and validation_error from the Screener and Alertsapplication/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.

Response
HTTP/1.1 401 Unauthorized
Content-Length: 0

400 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.

Response
{
  "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

Response
{
  "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)

Response
{
  "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.

Response
{
  "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

Response
{
  "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
400invalid_*, missing_*, or RFC 7807A parameter is missing or invalid. Codes are listed on each endpoint page.
404no_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.
422iv_not_found, unsupported_symbolThe value cannot be computed (for example an option price outside the arbitrage-free range), or the symbol is not supported by that operation.
503historical_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)
}

Ready to build with FlashAlpha?

Get your free API key and start pulling live options data in 60 seconds.

No credit card required Setup in 60 seconds Secure & reliable