Skip to main content
This page walks through the eight steps of an integration and the rules attached to each. Full request and response schemas are in the API reference; errors, rate limits and retries are on their own page. RFC 2119 keywords apply.

Conventions

  • Base URL: written here as {BASE_URL}. Every path is relative to {BASE_URL}/v1; POST /runs means POST {BASE_URL}/v1/runs. The sandbox base URL is on the Introduction; the production base URL is issued at go-live.
  • Server-to-server only. Every request comes from your backend. A browser MUST NOT call this API and MUST NOT ever hold a vpk_… key or vot_… token.
  • JSON only. Send Content-Type: application/json with a UTF-8 body on every POST. Responses are application/json; charset=utf-8.
  • Timestamps are ISO 8601 in UTC, for example 2026-10-04T09:12:44Z.
  • Ids. run_id, job_id, user_id, organization_id are UUID strings. candidate_id is an integer (it can exceed 2^31; use a 64-bit type). external_id values are your own ids, as strings, unique per customer organization for users and jobs, and unique per partner for organizations.
  • Unknown fields. Every response and webhook body MAY gain new fields at any time. Your client MUST ignore fields it does not know. Do not use strict deserializers that fail on extra properties.
  • Language. Notice title/body and error message follow x-vort-lang. Requirement and job validation issues always carry both _he and _en texts.

The 8 steps

1

Connect the customer

Vort issues the customer a one-time activation code (VORT-XXXX-XXXX, valid 14 days by default). A customer admin pastes it into your “Connect Vort” screen. Your server redeems it with POST /activations/redeem and receives an org token (vot_…). Store it server-side, encrypted, keyed to that customer. It is shown to you once.Details: Authentication · Redeem activation code
2

Register users

For each recruiter who will use Vort, call POST /users with your own id for them (external_id). A user is a field on later calls, never a credential: recruiters never receive a Vort key or token.Details: Register a recruiter
3

Create the job

POST /jobs with your job’s external_id, title, description (plain text, strip HTML) and location. Send it again whenever the job changes in your ATS: a repeat call updates title, description_text and location_text and returns the same job_id. Runs already started keep the description they started with. On 422 job_rejected, show each rejected[] message next to its field (title_missing, description_missing, location_missing).Details: Create or update a job
4

Start a run

POST /runs with the job, the acting recruiter, a target count and, optionally, the recruiter’s requirements (each must or nice, at most 3 musts). You get 202 {run_id} immediately, or 422 with Vort’s messages if a requirement is certainly wrong. A cold run typically shows its first confirmed candidates within 10 to 17 seconds and keeps working for several minutes.See Requirement rules below. Details: Start a run
5

Show progress and results

Poll GET /runs/{run_id}. While the run is running, show progress.confirmed / progress.target. Render every entry in notices[] in its slot (see Notices). Show candidates with their verdict and per-requirement evidence. Show the run_id.See Run status below. Details: Get a run
6

Reveal contact details

When a recruiter wants contact details, show the price from reveal_price_credits before the click, then call POST /runs/{run_id}/candidates/{candidate_id}/reveal.See Reveal rules below. Details: Reveal contact details
7

Report decisions

Every shortlist, rejection (with a reason from the fixed list), contact, interview, hire and note goes to POST /runs/{run_id}/decisions, together with the notice codes the recruiter was shown (notices_shown).See Decisions and reject reasons below. Details: Report decisions
8

Receive webhooks

Vort posts run_completed, run_failed and candidate_revealed (one per revealed candidate) to your webhook URL, signed with x-vort-signature. Verify the signature on the raw body before parsing, and de-duplicate retries by event_id.Details: Webhooks

Requirement rules

Requirements are validated before the run starts.
  • kind is must or nice. At most 3 must requirements (too_many_musts). Surface this cap in your editor before submit (see Screen 2).
  • Vort rejects only what is certainly wrong and warns on what is probably wrong. It never silently changes or drops a requirement: the run uses your requirements in the same order and count, trimmed and with whitespace collapsed.
  • When requirements is omitted, Vort derives the requirements from the job description.
  • Requirement indexes (index) are 0-based positions in the array you sent. They are the same indexes used by candidates[].requirements[].index and by notices with ref.requirement_index.
Show each rejected item’s message and, when not null, its suggestion, next to the requirement at index, verbatim, and let the recruiter fix and resubmit. Warnings do not block the run; show them next to the requirement without blocking. Example payloads for every code are in Requirement validation examples.
POST /runs is not idempotent. Do not retry it automatically on a timeout or 5xx: show the recruiter an error with a manual “Try again”.

Run status

Treat any other status value as running, but stop polling a run that has been running for more than 30 minutes and show the recruiter the run_id with a “contact support” hint.
object

Reveal rules

  • Before calling reveal, your UI MUST have shown the recruiter the price from that candidate’s reveal_price_credits in the latest GET /runs/{run_id} response. Never hard-code or cache a price across runs; prices come from Vort’s price table at request time.
  • email and phone can each be null: not every candidate has both. Render a missing value as “not found”, never as an empty field.
  • Show credits_charged and wallet_balance_after after the reveal. Do not assume credits_charged equals the quoted price; display what the response says.
  • After a reveal, the candidate’s contact_state becomes revealed in later GET /runs/{run_id} responses. A repeat reveal of the same candidate is not charged again.
  • On 402 insufficient_credits (with balance and required), show message with both numbers and do not retry.

Decisions and reject reasons

Reporting decisions is a condition of the partnership, not optional telemetry. Every recruiter decision in your UI about a Vort candidate MUST be sent to POST /runs/{run_id}/decisions, and every notice rendered to a recruiter MUST be reported in notices_shown.
Decision values: shortlisted, rejected, contacted, interviewing, hired.

Reject reasons

Only with decision: "rejected". Your reject UI MUST offer exactly this list, with these labels.
  • A rejection MAY have reject_reason: null only when the recruiter did not pick a reason; put any free text in note. Never infer a reason code from free text.
  • reject_reason on any decision other than rejected is 422 invalid_decision.
  • Free notes: a recruiter’s note travels in note on the decision it belongs to. v1 has no standalone note without a decision.
  • occurred_at is when the recruiter acted in your UI, not when you sent it. You MAY batch decisions, but SHOULD send them promptly (Vort recommends within 5 minutes of the action).
  • notices_shown lists the notice codes rendered to a recruiter for this run, including codes you did not recognize. Send each code at least once per run; repeats are harmless. If the recruiter makes no decision, still send decisions: [] with notices_shown.
  • The response is {"accepted": n, "duplicates": n}. Treat any 422 as “nothing from this request was stored”: fix the batch and resend all of it. Items already stored come back as duplicates.
Request body

Objects

integer
The candidate_id for reveal and decisions.
string
Render as an opaque string (masked in the sandbox).
string | null
Current title.
string | null
Current employer.
string | null
Location.
string
strong | good | weak
object[]
One entry per run requirement.
string
locked | revealed
integer
Price of a reveal, in credits, at the time of this response.
string
Stable identifier. New codes appear without notice; render them generically. See Notices.
string
info | warning | blocking
string
above_list | on_candidate | on_requirement | empty_state
string
Short heading. Render verbatim.
string
Full sentence(s). Render verbatim.
object
Optional. requirement_index (for on_requirement) and/or candidate_id (for on_candidate).
string
required
The requirement as the recruiter wrote it.
string
required
must | nice
Used in rejected[] and warnings[] of POST /runs.
integer
0-based position in the submitted requirements array.
string
A rejection or warning code (see Requirement rules). New codes may be added.
string
What is wrong. Show verbatim.
string | null
How to fix it. Show verbatim when not null.
string
The request field at fault, for example location_text.
string
title_missing | description_missing | location_missing (new codes may be added).
string
Show verbatim.
integer
required
A candidate of this run.
string
required
shortlisted | rejected | contacted | interviewing | hired
string | null
Only with rejected. One of the seven reject reasons.
string | null
The recruiter’s free text.
string (date-time)
required
When the recruiter acted.