Skip to main content
HayInsights uses a weighted, sliding-window rate limit. Instead of counting requests, each call consumes a weight from your plan’s per-minute budget. Heavier endpoints (large aggregates) cost more than light ones.

Headers

Every response — success or error — carries the current quota state:
HeaderMeaning
X-RateLimit-LimitYour plan’s total weight budget per 1-minute window.
X-RateLimit-RemainingWeight remaining in the current window.
X-RateLimit-ResetUnix timestamp (seconds) when the window resets.
X-RateLimit-Weight-UsedWeight consumed by this request.
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 997
X-RateLimit-Reset: 1781857979
X-RateLimit-Weight-Used: 3

Endpoint weights

DomainFeatureWeight per call
Macromacro_data2
Real Estaterealestate_data2
Commoditiescommodities_data3
Cryptocrypto_data3
FXfx_data3 (summary, table) · 6 (correlation, em-stress)
ETFetf_data8
Example: on a plan with a 1,000 weight/minute budget you can make roughly 333 macro calls (weight 2) or 125 ETF calls (weight 8) per minute. Mixing endpoints draws from the same shared budget.

Exceeding the limit

When you exhaust your budget the API returns HTTP 429 with the RATE_LIMIT_EXCEEDED code and a Retry-After header (seconds to wait):
{
  "success": false,
  "statusCode": 429,
  "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Rate limit exceeded" },
  "meta": { "timestamp": "2026-06-19T08:34:09.000Z" }
}

Handling 429 gracefully

Read the headers and back off until the window resets.
async function get(url: string, key: string): Promise<Response> {
  const res = await fetch(url, { headers: { "X-API-Key": key } });
  if (res.status === 429) {
    const wait = Number(res.headers.get("Retry-After") ?? 1);
    await new Promise((r) => setTimeout(r, wait * 1000));
    return get(url, key);
  }
  return res;
}
Watch X-RateLimit-Remaining and slow down before you hit zero, rather than reacting to 429s after the fact.