Pulse Intelligence

Rate limits and errors

Quota defaults, rate-limit headers, and the error envelope shapes you'll encounter

Every /v1/ request is metered against your organization's quota. Whether a response carries rate-limit headers depends on whether the request reached the quota check at all — most do, but a request that fails before that point doesn't (see Rate-limit headers below for the cases). This guide covers the defaults, the headers, what a 429 looks like, and the error shapes used across the API.

Quota defaults

These are the platform defaults applied to every organization. Your organization's limits may differ — contact Pulse if you need higher limits.

LimitDefault
Queries per minute60
Queries per day10,000
Rows returned per day500,000

An org's effective quota is the default merged with any per-org override on file — any override field that's set wins over the matching default. A quota can also be blocked outright (a hard kill switch, independent of whether you have headroom left) — a blocked org's keys get a 429 on every call until access is restored.

Rate-limit headers

Rate-limit headers appear on any response where your request reached the quota check — successful calls, filter/column validation errors, and ordinary rate-limit 429s all include them.

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1735689660
  • X-RateLimit-Limit — your queries-per-minute limit for the current window.
  • X-RateLimit-Remaining — how many requests you have left in the current minute window.
  • X-RateLimit-Reset — Unix timestamp (seconds) when the current minute window resets.

These headers reflect the minute window specifically — the day-level query and row quotas aren't exposed as headers, only as the 429 you'll get once you exceed them (see below).

A request that fails before that point carries no rate-limit headers. This list isn't exhaustive — anything that short-circuits ahead of the quota check falls in the same bucket — but the cases you're most likely to run into are:

  • A 401 — authentication runs first, so a bad or missing key never reaches the quota check.
  • A 400 from a malformed JSON body — the request body is parsed before the quota check runs, so a body that fails to parse is rejected during parsing, ahead of it.
  • The blocked-organization 429 (mcp_access_blocked, below) — the access-blocked check runs before the rate counters are consulted, so this particular 429 also has no rate-limit state behind it, unlike every other 429.

When you're rate limited

HTTP 429

Once you exceed a quota, the API returns 429 with a body identifying which limit you hit:

{
  "error": {
    "code": "mcp_rate_minute",
    "message": "MCP request rate exceeded (60/minute)."
  }
}

The code tells you which specific limit was exceeded:

CodeMeaning
mcp_rate_minutePer-minute query limit exceeded.
mcp_rate_dayPer-day query limit exceeded.
mcp_daily_rowsPer-day row quota exceeded (you've read too much data today, even if individual calls stayed under the query limits).
mcp_access_blockedYour organization's API access is blocked outright — contact Pulse.

The code values above carry the mcp_ prefix from the shared internal throttle these limits are implemented on top of — the same mechanism used by Pulse's chat and MCP surfaces. It's not public-API-specific naming, but it's exactly what you'll see on the wire, so match against these strings exactly rather than assuming a public_api_-style prefix.

Retry guidance

  • Always read the rate-limit headers rather than hardcoding your own assumption of the limit — your organization's effective quota can differ from the defaults above.
  • On a 429, back off before retrying. A minute-window limit (mcp_rate_minute) clears at X-RateLimit-Reset; a daily limit (mcp_rate_day, mcp_daily_rows) doesn't clear until the next UTC day.
  • Don't tight-loop retries on 429 — you'll keep hitting the same limit until its window resets.

The error envelope

Most error responses share one shape:

{
  "error": {
    "code": "...",
    "message": "..."
  }
}

Some errors carry additional fields alongside code and message — for example, a rejected filter field also returns which field and resource triggered it. Treat code and message as the guaranteed fields and anything else as extra context.

Two exceptions use DRF's stock error shape instead of the envelope above:

{ "detail": "..." }
  • Every 401 (missing header, malformed key, unknown key, revoked key) — see Authentication for why presented-key failures are indistinguishable by design.
  • The 400 for a request body that isn't valid JSON — the body is rejected during parsing, before the API's own error handling is reached, so it comes back as { "detail": "JSON parse error ..." }.

Every other 400 and every 429 uses the nested {"error": {...}} envelope.

There are no resource-level 404s on this API: a well-formed request to a documented path never 404s. An unknown company or asset id, an unsupported resource, or an unrecognized options endpoint all come back as a 400 invalid_argument rather than a 404 — "not found" is treated as a bad argument, not a missing route. A wrong or slash-less path is a different story and does 404 — see the trailing-slash note under Endpoints.

Common error codes

These are the codes you can actually trigger by calling the API correctly but with bad input — not an exhaustive list of every internal error type.

CodeHTTP statusWhen you'll see it
invalid_argument400General bad input — an unknown company/asset id, an unsupported resource on /v1/filters/{resource}/, an unrecognized options_path, a non-integer limit, a bad logic value, or similar.
unsupported_filter_field400A search filters entry referenced a field outside the resource's curated vocabulary (see Filters).
unsupported_column400A search columns entry referenced a field outside the resource's curated vocabulary.
unsupported_sort_field400A search sort value isn't a sortable key in the resource's curated vocabulary (or isn't a string). See Pagination and enumeration.
query_timeout408The query took too long to execute — reachable on the full-text search endpoints (POST /v1/people/search/, POST /v1/publications/search/) under a broad enough query. Narrow your search term (or add a company/date filter on publications) and retry.

Example 400 for a filter field outside the curated vocabulary:

{
  "error": {
    "code": "unsupported_filter_field",
    "message": "Unknown or unsupported filter field for the public API: 'internal_score'.",
    "field": "internal_score",
    "resource": "companies"
  }
}

Go deeper

  • Filters covers the curated filter vocabulary that drives most 400s on the search endpoints.
  • The API Reference documents the exact 400/401/429 responses for each endpoint.

On this page