Errors
Problem-detail shape
Every failure returns an application/problem+json body shaped per RFC 9457 · Problem Details for HTTP APIs. The five standard fields are always present; specific categories add extension fields (e.g. errors on validation failures, retryAfterSeconds on rate-limit responses).
{ "type": "https://quantdata.us/errors/<category>", "title": "Human-readable category name", "status": <HTTP status code>, "detail": "Specific human-readable reason", "instance": "/v1/options/tool/<endpoint>" }
Branch on the type URI when programmatically classifying failures. It is the stable identifier for the category. title and detail are human-readable summaries that may be refined over time. instance is the path of the failing request, so a log can include a single entry per response without separately recording the endpoint.
- Branch on
type, nottitleordetail. The URI is stable; the strings can change for clarity without notice. - For
400 · validation, theerrors[]array tells you exactly which field is the problem. Surface those messages directly to whoever wrote the request. - For
429 · rate-limit-exceeded, sleepRetry-Afterseconds (or readretryAfterSecondsfrom the body) before retrying. See Rate Limiting for the full contract. - For
500 · internal, retry with exponential backoff. The body intentionally omits diagnostic detail; treat a persistent500on a previously-working request as worth reporting.
Validation · 400
type: https://quantdata.us/errors/validation. Returned when the request body parsed but one or more fields fail validation. The errors extension property is an array of { field, message } records, one per failure.
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
"type": "https://quantdata.us/errors/validation",
"title": "Validation Failed",
"status": 400,
"detail": "Request validation failed",
"instance": "/v1/options/tool/gainers-losers",
"errors": [
{
"field": "filter.tickers",
"message": "'filter.tickers' contains an empty value."
},
{
"field": "filterExpression.operation",
"message": "Unrecognized operation 'BETWEEN'. Valid operations: EQUALS, =, ==, EQ, EQUAL, DOES_NOT_EQUAL, !=, ..."
}
]
}The field path is dotted from the top of the request body: filter.tickers, filterExpression.filters[0].operation, timeRange.endTime, and so on. Every individual failure is reported in a single response, so a request with two bad fields produces a single response with both entries.
Bad Request · 400
type: https://quantdata.us/errors/bad-request. Returned when the request body could not be parsed at all: malformed JSON, an unparseable type for a typed field (e.g. a non-date string for sessionDate), or a missing Content-Type header on a non-empty body.
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
"type": "https://quantdata.us/errors/bad-request",
"title": "Bad Request",
"status": 400,
"detail": "Could not parse request body. Please ensure the request is valid JSON.",
"instance": "/v1/options/tool/gainers-losers"
}Distinct from validation: a bad-request response has no errors[] array because the body could not be parsed at all. Fix the body shape (or the missing header) and retry; if instead you get a validation response, fix the individual field.
Authentication · 401
type: https://quantdata.us/errors/authentication. Returned when the API key is missing, malformed, unknown, revoked, or attached to an inactive user account. See the Authentication page for the five concrete detail strings and what each one indicates.
HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json
{
"type": "https://quantdata.us/errors/authentication",
"title": "Authentication Failed",
"status": 401,
"detail": "Invalid API key.",
"instance": "/v1/options/tool/gainers-losers"
}Authorization · 403
type: https://quantdata.us/errors/authorization. Returned when the API key authenticated successfully but the account does not have an active API subscription. Distinct from 401. The request was authenticated, just not authorized. Renew or upgrade from your dashboard's billing panel.
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
{
"type": "https://quantdata.us/errors/authorization",
"title": "API Subscription Required",
"status": 403,
"detail": "An active API subscription is required to access this endpoint.",
"instance": "/v1/options/tool/gainers-losers"
}OPRA Agreement · 403
type: https://quantdata.us/errors/opra-agreement-required. Returned when the key authenticated and the account has an active API subscription, but the user has not yet signed the OPRA market-data subscriber agreement that the Options Price Reporting Authority requires before any OPRA-sourced quotes can be returned. The body carries an agreementUrl extension property with the signing URL, so programmatic clients can branch on type and read the link directly. See the Authentication page for the full handling.
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
{
"type": "https://quantdata.us/errors/opra-agreement-required",
"title": "OPRA Agreement Required",
"status": 403,
"detail": "You must sign the OPRA market-data agreement before using this API. Sign at: https://v3.quantdata.us/legal/agreement",
"instance": "/v1/options/tool/gainers-losers",
"agreementUrl": "https://v3.quantdata.us/legal/agreement"
}Rate Limit · 429
type: https://quantdata.us/errors/rate-limit-exceeded. Returned when the per-user request quota for the configured window has been exhausted. The response carries Retry-After (per RFC 7231) plus three X-RateLimit-* headers and three extension fields in the body so callers can pick whichever spelling fits their HTTP client.
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
X-RateLimit-Limit: 240
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 47
Retry-After: 3
{
"type": "https://quantdata.us/errors/rate-limit-exceeded",
"title": "Rate Limit Exceeded",
"status": 429,
"detail": "Rate limit of 240 requests per 60 seconds exceeded. Retry in 3 seconds.",
"instance": "/v1/options/tool/gainers-losers",
"limit": 240,
"windowSeconds": 60,
"retryAfterSeconds": 3
}Sleep retryAfterSeconds (or Retry-After) seconds before sending the next request. See Rate Limiting for the full contract, including the headers that ride on every response, not just the rejected ones.
Internal · 500
type: https://quantdata.us/errors/internal. Returned for any unhandled server-side failure. The detail field is intentionally generic; diagnostic detail stays server-side.
HTTP/1.1 500 Internal Server Error
Content-Type: application/problem+json
{
"type": "https://quantdata.us/errors/internal",
"title": "Internal Server Error",
"status": 500,
"detail": "An unexpected error occurred. Please try again later.",
"instance": "/v1/options/tool/gainers-losers"
}Retry with exponential backoff. A persistent 500 on a previously-working request is worth reporting via the contact form linked in the footer.
Where to go next
AND, OR, and nested terminals.