https://api.certo.bio/v1openapi.json

Invite candidates

POST/v1/invitationsscope hiring:invite

Invites 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

NameTypeDescription
dry_runbooleantrue: the same checks and the same answer per row, and the credits it would take; nothing is written, sent or spent.

Request body

NameTypeRequiredDescription
peoplearray of objectYesWho to invite, 1 to 500.
people[].emailstringYesThe candidate's address.
people[].namestringTheir name, for your own reference: not stored or sent (their passport names them).
people[].custom_dataobject or nullYour 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_iduuid or nullAn open position of the organisation; the e-mail names it.
notestring or nullA note the candidate reads, at most 280 characters.
scope_typesarray of one of employment, education, certification, role, language, right_to_work or nullWhat the candidate is asked to share: record types, or null (or every one) for all of them.
include_contactbooleanAsk for their contact details too.
include_birth_datebooleanAsk for their date of birth too (with birth_date_reason).
birth_date_reasonstring or nullWhy the date of birth is needed, 3 to 280 characters; required with include_birth_date. The candidate reads it.
requirementsobject or nullWhat 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_dataobject or nullYour 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