List candidates

Enumerate a recruiter’s own candidates, keyset-paginated, with status and updated_since filters.

GET/api/v1/candidates#

Discover which candidates exist, keyset-paginated, without already holding an id.

Headers

X-Api-Keyonb_live_<key_id>_<secret>required
Your issued Partner API key — see Authentication.

Request fields

limitintegeroptional
25 if omitted, up to 100. Never clamped — an out-of-range value is a 400, not silently reduced.
cursorstringoptional
Opaque. Pass back the previous page’s next_cursor verbatim; omit for the first page.
statusstringoptional
One of awaiting_consent, consent_denied, in_progress, verified, flagged, revoked — the same stage slug The candidate object’s own status field uses. Omit for every stage.
updated_sincestring (ISO-8601)optional
ISO-8601 timestamp with a timezone (2026-09-01T00:00:00Z) — anything else is a 400. Best-effort — see the note below.

Status codes

2002xx
A page of candidates — may hold fewer than limit rows, including zero. See the pagination note below.
4004xx
An unrecognised or out-of-range query parameter, or a malformed/tampered cursor.
Invalid query parameter: {param}Invalid or expired cursor
4014xx
Missing, malformed, unknown or revoked key. Every authentication failure looks the same — see Authentication.
4294xx
Per-key or per-account rate limit exceeded. Retry after the Retry-After header.
Too many requests — please try again shortly.
5035xx
A record this page depends on could not be read right now. Never a confident empty page — retry.
Some of this candidate’s records could not be read right now. Try again shortly.

These are query-string parameters, not a request body

This is a GET with no body — every field in the table above is sent on the URL (?limit=25&status=in_progress), not as JSON. The generated curl/Node/Python samples on this page show the bare endpoint with no query string; append the parameters you need yourself, as in the examples below.

Example query strings
GET /api/v1/candidates?limit=50
GET /api/v1/candidates?status=verified&limit=25
GET /api/v1/candidates?cursor=<next_cursor from the previous page>
GET /api/v1/candidates?updated_since=2026-08-20T00:00:00Z

Iterate until `next_cursor` is `null` — never until `candidates` is empty

next_cursor is derived from the underlying scan alone, independent of ?status. A ?status filter can legitimately return candidates: [] on a page while next_cursor stays non-null — the scan had more rows beyond this page, none of them happened to match. Stopping at an empty candidates array will leave real matches unread.

status is refined in two passes: a database-level filter, then the exact same deriveStage computation The candidate object’s own status field uses, run again after the read with the same inputs — so a status value here and a status value from a direct GET /candidates/{id} call, taken from the same underlying row, always agree.

updated_since is best-effort: stamped by the compliance engine’s own writers, and — as of this release — by PATCH /candidates/{id} and PATCH /candidates/{id}/outreach. An edit made directly from the Onboardly dashboard does not stamp it — do not rely on updated_since alone to catch every change; poll the full roster periodically as a backstop.

  • cv is the same V1CandidateCv shape The candidate object’s own cv field uses: null when no CV is on file or none is currently servable, otherwise an object with file_name, content_type, size, and content_url.
  • A list row is intentionally thinner than The candidate object: no documents, references, reference_requirements, compliance_pack, or chase. Fetch GET /candidates/{id} for the full record.
cURL
curl \
"https://rca-onboardly.com/api/v1/candidates" \
-H "X-Api-Key: $ONB_KEY"
200 · Response
{
"candidates": [
{
"id": "3f9c1a2e-8b7d-4c1f-9a3e-2d5f8c1b4e70",
"external_id": "ats-record-4471",
"status": "in_progress",
"consent": true,
"candidate": {
"name": "Jordan Ellis",
"mobile_number": "+447700900321",
"email": "jordan.ellis@example.com",
"job_title": "Support Worker"
},
"cv": null,
"created_at": "2026-08-20T10:15:00.000Z",
"updated_at": "2026-09-01T09:02:11.000Z"
}
],
"next_cursor": "MjAyNi0wOC0yMFQxMDoxNTowMC4wMDBafDNmOWMxYTJlLThiN2QtNGMxZi05YTNlLTJkNWY4YzFiNGU3MA"
}