Retrieve messages

Read a candidate’s WhatsApp conversation history, newest first, plus a bot-health summary.

GET/api/v1/candidates/{id}/messages#

Headers

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

Request fields

limitintegeroptional
1–200, default 50. Out of range or non-integer is rejected outright, never clamped.
cursorstringoptional
Opaque — pass back the exact next_cursor value from a previous page.

Status codes

2002xx
A page of messages, newest first.
4004xx
?limit failed validation, or ?cursor is malformed/tampered/calendar-invalid.
Invalid query parameter: limitInvalid or expired cursor
4014xx
Missing, malformed, unknown or revoked key. Every authentication failure looks the same — see Authentication.
4044xx
Unknown, cross-tenant, sandbox-hidden, or a malformed {id} — one outcome, never a 403.
Not found
4294xx
Per-key or per-account rate limit exceeded. Retry after the Retry-After header.
Too many requests — please try again shortly.
5035xx
A read this payload depends on could not be completed. Never a confident empty messages: [] — retry.
Some of this candidate’s records could not be read right now. Try again shortly.

`limit`/`cursor` are query-string parameters, not a JSON body

A GET request has no body — append ?limit=/?cursor= to the URL. The sample on this page does not show them; add them yourself, e.g. ?limit=50.

  • Iterate by passing each page’s next_cursor back as ?cursor until it comes back null — that marks the end of the scan, not an empty messages array on any individual page.
  • author is candidate for anything inbound; for an outbound row, recruiter for a dashboard-sent manual message, bot otherwise.
  • body passes the stored message through unchanged for every message_type, including file — a file row’s body is the candidate’s own caption text, never a URL. This response carries no link to the uploaded file itself; see Retrieve document content for that.
  • delivery_status is the raw conversations.delivery_status value — never narrowed. delivery_error is a CLOSED set — opted_out, send_failed, or null — never the provider’s raw text or this engine’s own internal compliance state.
  • stage is the candidate’s raw conversation_stage, not resolved to a friendlier label the way retrieve-candidate’s status is.
  • bot_health is independent of ?cursor/?limit — it always reflects the conversation’s actual newest state, not whatever page you happen to be reading.
  • bot_health.status: crashed / transport_ok: false do NOT always mean the transport failed — this engine writes the identical delivery_status for a message it deliberately withheld (the candidate opted out, a GDPR pause, human takeover). Check that message’s delivery_error: opted_out means the candidate stopped the conversation, not that anything is broken.
cURL
curl \
"https://rca-onboardly.com/api/v1/candidates/<candidate id>/messages" \
-H "X-Api-Key: $ONB_KEY"
200 · Response
{
"messages": [
{
"id": "b2f4c1a0-7e3d-4a9c-8b1f-2d6e5a9c3f70",
"direction": "outbound",
"author": "bot",
"body": "Hi Marcus, we need a few documents to get you compliant. Do you have a valid DBS certificate?",
"message_type": "text",
"delivery_status": "delivered",
"delivery_error": null,
"sent_at": "2026-09-01T10:14:02Z"
},
{
"id": "1a3e7c9d-4b2f-4a6e-9c1d-8f5a2b7e3c40",
"direction": "inbound",
"author": "candidate",
"body": "yes I have one",
"message_type": "text",
"delivery_status": null,
"delivery_error": null,
"sent_at": "2026-09-01T10:16:40Z"
}
],
"next_cursor": "eyJjcmVhdGVkQXQiOiIyMDI2LTA5LTAxVDEwOjE0OjAyLjAwMFoiLCJpZCI6ImIyZjRjMWEwLTdlM2QtNGE5Yy04YjFmLTJkNmU1YTljM2Y3MCJ9",
"stage": "awaiting_upload",
"bot_health": {
"status": "healthy",
"reply_state": "bot_replied",
"transport_ok": true,
"last_delivery_status": "delivered"
}
}