Help center
https://api.certo.bio/v1openapi.json
Invite candidates
POST
/v1/invitationsscope hiring:inviteInvites up to 500 people to get verified and share their passport, on terms set once for the list. Each new invitation is one credit, checked for the whole list before anybody is invited (credit.none_left: nobody is); an address with an open invitation is answered already and costs nothing; each row answers sent, already or refused. The e-mails go out from certo. Requires an Idempotency-Key; try it first with ?dry_run=true.
As an MCP tool: invite_candidates
Query parameters
| Name | Type | Description |
|---|---|---|
| dry_run | boolean | true: the same checks and the same answer per row, and the credits it would take; nothing is written, sent or spent. |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
| people | array of object | Yes | Who to invite, 1 to 500. |
| people[].email | string | Yes | The candidate's address. |
| people[].name | string | Their name, for your own reference: not stored or sent (their passport names them). | |
| people[].custom_data | object or null | Your own data on this invitation: any JSON object (an id from your ATS or HR system, a requisition number), at most 4096 bytes. certo stores it and answers it back on the reads and in your webhook events; it never reads it, and the candidate never sees it. Its keys go over the list's custom_data. An address with an open invitation keeps its own, or takes this one if it had none. | |
| position_id | uuid or null | An open position of the organisation; the e-mail names it. | |
| note | string or null | A note the candidate reads, at most 280 characters. | |
| scope_types | array of one of employment, education, certification, role, language, right_to_work or null | What the candidate is asked to share: record types, or null (or every one) for all of them. | |
| include_contact | boolean | Ask for their contact details too. | |
| include_birth_date | boolean | Ask for their date of birth too (with birth_date_reason). | |
| birth_date_reason | string or null | Why the date of birth is needed, 3 to 280 characters; required with include_birth_date. The candidate reads it. | |
| requirements | object or null | What the candidate must have ready, docs/request-requirements.md: {"v": 1, "work_years", "education_min_level", "certifications", "right_to_work", "contact"}; null for the defaults. | |
| custom_data | object or null | Your own data on every invitation in the list: any JSON object (an id from your ATS or HR system, a requisition number), at most 4096 bytes. certo stores it and answers it back on the reads and in your webhook events; it never reads it, and the candidate never sees it. A row's own custom_data goes over it. |
Response fields
- dry_run
- boolean. Whether this was a dry run.
- results
- array of object. One per row, in order.
- results[].row
- integer. The row's number in
people, from 1. - results[].email
- string. The address, as certo keeps it (lower case).
- results[].status
- one of sent, would_send, already, refused. sent (now), would_send (a dry run), already (an open invitation from before: nothing sent, nothing spent) or refused.
- results[].invitation_id
- uuid or null. The invitation, for sent; for already when certo names it.
- results[].code
- string or null. Why the row was refused, a code with its sentence under errors (account.email_not_allowed, invite.email_invalid, invite.duplicate_row, ...).
- sent
- integer. Rows sent (would be sent, in a dry run).
- already
- integer. Rows with an open invitation from before.
- refused
- integer. Rows refused.
- credits
- object. The credits.
- credits.needed
- integer. Credits the rows to send take: one per new invitation (or graduate), none for already.
- credits.spent
- integer. Credits spent now (0 in a dry run).
- credits.balance
- integer. The balance after the call (before it, in a dry run).
Errors
- 402 credit.none_left
- 409 organisation.profile_incomplete
- 400 company_request.requirements_invalid
- 409 position.closed_or_missing
- 400 invite.birth_date_reason_required
- 400 invite.note_too_long
- 413 issuer.batch_too_large
- 400 request.field_required
- 400 request.unknown_field
- 400 api.idempotency_key_required
- 409 api.idempotency_mismatch
- 409 api.idempotency_in_progress
- 400 api.credential_in_url
- 401 api.key_invalid
- 401 api.key_expired
- 401 api.key_revoked
- 401 api.key_suspended
- 403 api.scope_missing
- 429 api.rate_limited