Create a candidate
Create a candidate from your ATS record, optionally sending the WhatsApp opener.
Headers
X-Api-Keyonb_live_<key_id>_<secret>required- Your issued Partner API key — see Authentication.
Idempotency-Key<your own record id>required- Matches on the key alone, per account. 1–255 characters.
Request fields
first_namestringrequired- Up to 200 characters.
last_namestringrequired- Up to 200 characters.
emailstringrequired- A syntactically valid address.
mobile_numberstringrequired- Include a country code (
+447700900321) where possible — a bare national number is accepted but assumes GB. role_typestring or nulloptional- Up to 200 characters.
documentsstring[]optional- Any name is accepted — there is no fixed catalogue (see Document names). Up to 50 entries, each up to 200 characters. Omit to seed the default five-document checklist.
references{ organisation, role, startDate?, endDate?, refType?, referee? }[]optional- Each entry's keys are camelCase (
startDate,endDate,refType) — the one exception to this endpoint's otherwise snake_case body.refTypeisprofessional(the default) orcharacter.refereeis optional —{ name, jobTitle?, email, phone?, relationship? }for a referee you already know.jobTitleis the referee's own job title; the entry'sroleis the candidate's. It is stored on the reference and never emailed at creation. While the candidate is being asked to agree on WhatsApp, a send for it (the dashboard's Send, orPATCH /api/v1/references/{id}) is refused and the reference waits for their answer; a candidate who has never been messaged on WhatsApp is not held. When you send it,refereemust be an object whose fields are strings (nullcounts as absent) — anything else is a 400 — andnameandemailare both required, and a professional referee needs a work address: Gmail, Hotmail, Outlook, Yahoo, iCloud and other personal addresses are a 400. A character referee may use a personal address. Up to 20 entries, each text field up to 200 characters; only the first 5 are seeded and asked for. An entry missingorganisationorroleis silently dropped, not rejected — unless it has areferee: then a professional entry is a 400 and a character entry is kept. A 400 for a refused entry says which entry it was. opt_inbooleanunless your account has a configured default opt-in source- Must be
true.false(or omitted with no account default) is a 400. opt_in_sourcestringunless your account has a configured defaultapplication_form,recruiter_attested, orats_import.opt_in_atstring (ISO-8601)unless your account has a configured default- When consent was captured.
opt_in_evidence_refstringoptional- Up to 500 characters — a pointer to your own consent record.
docs_chase_attemptsinteger or nulloptional- 1–10. Omit or send
nullfor the account default. reference_chase_attemptsinteger or nulloptional- 0–10 (
0disables referee chasing for this candidate). Omit or sendnullfor the account default. startbooleanoptional- Must be the literal
trueto send the WhatsApp opener. Defaults tofalse— every other truthy value still means "do not send". qualificationsobject[]optional- Qualification checks are switched on; if they are ever switched off, the field is ignored like any unknown field. The same items as Add qualifications (that endpoint lists the item keys and limits). They are validated before anything is created, so a bad item is a 400 that creates nothing and uses up no Idempotency-Key. Each qualification adds its own document slot ("CSCS Card", then "Second CSCS Card") after the documents above, or after the default checklist when
documentsis omitted. The 201 data then carriesqualifications_saved(and per-itemqualificationsresults). Ifqualifications_savedisfalsethe candidate exists but nothing was sent, even withstart: true: call Add qualifications again, then Start a candidate. application_form_sectionsstring[]optional- Optional. Which parts of the candidate's application form are shown, as a list of any of: address, eligibility, dbs, referees, cv_upload, documents, date_of_birth, email, declarations. Full name and phone number are always shown. Leave the field out to show every part. An empty list
[]shows only name and phone number;["cv_upload"]asks name, phone number and an optional CV. A part you leave out is not asked. If you leave outdeclarations, the candidate signs nothing and no signed declaration is kept: the compliance pack lists the declarations as not recorded. The consent is recorded in the declarations, so a list that includesdbsordocumentsmust includedeclarationstoo (a 400 otherwise);cv_uploaddoes not need it. A list has at most 18 entries. An unknown value is a 400 naming the allowed values; duplicates are ignored. The WhatsApp agreement for a candidate created withstart_after_application_formis always shown. start_after_application_formbooleanoptional- Must be the literal
trueto start the WhatsApp conversation only after the candidate submits their application form: Onboardly sends the first WhatsApp message a few minutes after they submit it, and the form asks the candidate to agree to WhatsApp messages. The candidate must tick the WhatsApp agreement on the form for anything to be sent, and the message goes to the phone number on file for the candidate when the form is submitted. Nothing is sent at creation. Cannot be combined withstart: true(400); a non-boolean value is a 400. You can still call Start a candidate yourself at any time.
Status codes
2012xx- Candidate created.
2002xx- This Idempotency-Key already created a candidate — the original result is returned, nothing new is written.
4004xx- Malformed body, a field failed its own validation, or the Idempotency-Key header was missing/too long.
An Idempotency-Key header is required (max 255 characters).Invalid mobile numbermobile_number must include a country code (e.g. +447700900123)mobile_number is not a valid number for its country codeConfirm the candidate opted in first 4014xx- Missing, malformed, unknown, revoked or expired key. Every authentication failure looks the same — see Authentication.
4034xx- The key’s user has the
viewerteam role, or their account could not be read just then (retry) — or the key was created Read only (code: "key_read_only", refused before anything else). Checked before the body or any id — see Authentication.Your team role doesn't allow this. Ask your team's admin or owner.This API key is read-only. Create a full-access key to make changes. 4024xx- Reserved for plan limits — over your account’s seat or message quota.
metacarries the figures. 4094xx- A duplicate phone or email already in your roster, a mobile number this API cannot create a candidate for right now, or another request with this Idempotency-Key still in flight. Nothing was created; fix the input or wait for the in-flight request.
A candidate with this mobile number already existsA candidate with this email already exists 4294xx- Per-key or per-account rate limit exceeded. Retry after the
Retry-Afterheader.Too many requests — please try again shortly. 5005xx- Creation failed and was not retried automatically. Retry with the SAME Idempotency-Key.
Failed to create candidate 5035xxstart: truewas sent but WhatsApp sending is not available on the account right now. The candidate may still have been created — read it back before retrying, and contact support if it persists.
`start` must be the literal `true`
Any other value — "yes", 1, omitted — sends nothing. This is deliberate: an ATS sync that silently messaged every imported record would be an expensive surprise.
Attaching a CV needs a multipart body, not the JSON shape above
Send multipart/form-data instead, with every field above as a plain-string form field (documents/references as their JSON-encoded strings) plus a cv_file file field — accepted types: application/pdf, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document, up to about 4.4 MB (the platform rejects a larger request body before it reaches the API). Idempotency-Key is still required. start is silently ignored on a multipart body — this route only reads it from a JSON-parsed clone of the request; call Start a candidate afterwards.
- A body that is not valid JSON may be answered with a 500 rather than a 400. Fix the body and retry with the same Idempotency-Key — the claim is released on failure.
- Documents accept any name — there is no fixed catalogue to validate against.
- References beyond the first 5 are collected in your ATS but neither seeded nor asked for.
docs_chase_attemptsmaps to the same per-candidate cap the dashboard’s outreach panel callschase_attempts_override; the wire name differs by endpoint (see Update outreach settings).- Undocumented in the design outline but present on the wire:
opt_in_at,opt_in_evidence_ref, andopt_in_sourceacceptingapplication_form/recruiter_attested/ats_import.
curl -X POST \ "https://rca-onboardly.com/api/v1/candidates" \ -H "X-Api-Key: $ONB_KEY" \ -H "Idempotency-Key: <your own record id>" \ -H "Content-Type: application/json" \ -d '{ "first_name": "Jordan", "last_name": "Ellis", "email": "jordan.ellis@example.com", "mobile_number": "+447700900321", "role_type": "Support Worker", "documents": [ "DBS", "Right to Work" ], "references": [ { "organisation": "Acme Ltd", "role": "Support Worker", "startDate": "2022-01", "endDate": "2023-06" } ], "opt_in": true, "opt_in_source": "ats_import", "opt_in_at": "2026-09-01T09:00:00Z", "start": false}'{ "first_name": "Jordan", "last_name": "Ellis", "email": "jordan.ellis@example.com", "mobile_number": "+447700900321", "role_type": "Support Worker", "documents": [ "DBS", "Right to Work" ], "references": [ { "organisation": "Acme Ltd", "role": "Support Worker", "startDate": "2022-01", "endDate": "2023-06" } ], "opt_in": true, "opt_in_source": "ats_import", "opt_in_at": "2026-09-01T09:00:00Z", "start": false}{ "id": "3f9c1a2e-8b7d-4c1f-9a3e-2d5f8c1b4e70", "checklistSeeded": true, "documentsSeeded": 2, "referencesSeeded": 1, "openerMirrored": false, "cvStored": false}{ "id": "3f9c1a2e-8b7d-4c1f-9a3e-2d5f8c1b4e70", "replayed": true}POST /api/v1/candidates
curl -X POST \ "https://rca-onboardly.com/api/v1/candidates" \ -H "X-Api-Key: $ONB_KEY" \ -H "Idempotency-Key: <your own record id>" \ -H "Content-Type: application/json" \ -d '{ "first_name": "Jordan", "last_name": "Ellis", "email": "jordan.ellis@example.com", "mobile_number": "+447700900321", "role_type": "Support Worker", "documents": [ "DBS", "Right to Work" ], "references": [ { "organisation": "Acme Ltd", "role": "Support Worker", "startDate": "2022-01", "endDate": "2023-06" } ], "opt_in": true, "opt_in_source": "ats_import", "opt_in_at": "2026-09-01T09:00:00Z", "start": false}'{ "first_name": "Jordan", "last_name": "Ellis", "email": "jordan.ellis@example.com", "mobile_number": "+447700900321", "role_type": "Support Worker", "documents": [ "DBS", "Right to Work" ], "references": [ { "organisation": "Acme Ltd", "role": "Support Worker", "startDate": "2022-01", "endDate": "2023-06" } ], "opt_in": true, "opt_in_source": "ats_import", "opt_in_at": "2026-09-01T09:00:00Z", "start": false}{ "id": "3f9c1a2e-8b7d-4c1f-9a3e-2d5f8c1b4e70", "checklistSeeded": true, "documentsSeeded": 2, "referencesSeeded": 1, "openerMirrored": false, "cvStored": false}{ "id": "3f9c1a2e-8b7d-4c1f-9a3e-2d5f8c1b4e70", "replayed": true}