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.
| Limit | Default |
|---|---|
| Queries per minute | 60 |
| Queries per day | 10,000 |
| Rows returned per day | 500,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: 1735689660X-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
400from 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 particular429also has no rate-limit state behind it, unlike every other429.
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:
| Code | Meaning |
|---|---|
mcp_rate_minute | Per-minute query limit exceeded. |
mcp_rate_day | Per-day query limit exceeded. |
mcp_daily_rows | Per-day row quota exceeded (you've read too much data today, even if individual calls stayed under the query limits). |
mcp_access_blocked | Your 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 atX-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.
401 is the one exception — authentication failures use DRF's stock
error shape instead of the envelope above:
{ "detail": "..." }This applies to every authentication failure (missing header, malformed key,
unknown key, revoked key) — see
Authentication for why they're all
indistinguishable by design. Every other error status (400, 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 in
Authentication.
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.
| Code | HTTP status | When you'll see it |
|---|---|---|
invalid_argument | 400 | General 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_field | 400 | A search filters entry referenced a field outside the resource's curated vocabulary (see Filters). |
unsupported_column | 400 | A search columns entry referenced a field outside the resource's curated vocabulary. |
query_timeout | 408 | The 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/429responses for each endpoint.
Filters & search
The curated filter vocabulary, operator semantics, and option lookups
POSTSearch companies
Screen mining companies by a curated set of filterable fields — commodities, geography, exchange listings, market cap, and more. Returns matching companies with the requested columns, or a default column set if none are requested.