Conventions and errors
What every Prospex surface shares: dates, ids and versions, pagination, parameters, headers, idempotency and the refusal vocabulary.
These rules hold on every surface. Each surface's own page covers only what differs.
Dates, ids and versions
- Every timestamp is ISO 8601 in UTC, and every date is
YYYY-MM-DD. - Ids come in two flavours. A Prospex company id is an integer and stable forever. Outreach ids are opaque strings such as
prospect_4812, and the API takes them exactly as it gave them. - The API is versioned in its path, at
/api/v1/outreach/. Feeds carry aversionorschema_versionin the body instead, bumped when a field changes meaning or leaves. - A new field does not bump a version. Unknown response fields should be ignored.
Pagination
Each paginated feed hands you a next_url that is absolute and carries every parameter you sent. Following it until it comes back empty is the whole of pagination. Cursors are opaque.
A built list is a ranking, and its order changes only when a build refreshes it. Its cursor carries the build it was issued under. A cursor from an older build answers 400 invalid_parameter and asks you to start again.
A list's signals are ordered by detection time. Its keyset cursor keeps page turns stable when newer signals arrive during a pull.
A project grows continuously, so its feed uses a keyset in one of two modes. Without updated_since it walks primary keys, which never move, so a page turn cannot skip a row that changed underneath you. With updated_since it walks oldest change first, and a row updated mid-walk is served again. Re-reading a row costs you a comparison. Skipping one costs you a company.
Page sizes: up to 500 rows on a list page, up to 200 on a project page.
Parameters
The list signal feed, the project feed and the API refuse a parameter they do not know, with a 400 that names it and lists what would have worked.
The list feed reads three parameters — tab, limit and cursor — and ignores anything else.
Headers
| Header | Where | Why |
|---|---|---|
| Retry-After | On a 429 | Seconds until the limit resets: midnight for a daily limit, midnight on the 1st for monthly_row_limit. Sleep on this rather than on a guess. |
| X-Request-Id | Every API response | Also in the body as request_id, and recorded in the project's audit history. Quote it when something looks wrong. |
| X-RateLimit-* | Every API response | Limit, Remaining and Reset for the day's request allowance. |
| Idempotency-Key | Sent by you, on every API mutation | Required. |
Idempotency
Every mutation carries an idempotency key you choose.
- The same key with the same request returns the original response byte for byte, with
Idempotency-Replayed: true, and spends nothing a second time. - The same key with a different request answers 422
idempotency_conflict. - Keys are scoped to the credential that used them, so two services in one account can both send
nightly-2026-08-21without colliding.