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

Standard fields
{
  "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.

How to handle errors
  • Branch on type, not title or detail. The URI is stable; the strings can change for clarity without notice.
  • For 400 · validation, the errors[] array tells you exactly which field is the problem. Surface those messages directly to whoever wrote the request.
  • For 429 · rate-limit-exceeded, sleep Retry-After seconds (or read retryAfterSeconds from 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 persistent 500 on 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.

Response · validation
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.

Response · bad request
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.

Response · authentication
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.

Response · authorization
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.

Response · OPRA agreement required
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.

Response · rate limit
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.

Response · internal error
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