API reference.
Read and add candidates, jobs, clients and placements from your own systems, and hear about what happens in Helixon through signed webhooks.
Authentication
Make a key in your workspace under Settings › Integrations. A key acts as the person who made it, sees everything they can see, and can be revoked at any time. Send it as a bearer token (or an X-API-Key header) over HTTPS:
curl https://www.helixon.co.uk/api/v1/candidates?stage=Shortlisted \ -H "Authorization: Bearer hx_your_key"
Each key can make 1,000 requests an hour; past that you get a 429. Responses carry a Helixon-API-Version header (currently 2026-10-01), and field names are kept stable within a version.
Requests and responses
Request and response bodies are JSON with camelCase field names. Lists come back as data with paging details; use page and pageSize (up to 100) to move through them.
{
"data": [
{
"id": "6f1c…",
"name": "Ana Ruiz",
"email": "ana@example.com",
"stage": "Shortlisted",
"matchScore": 82,
"job": { "id": "a3b9…", "title": "Ward Sister", "client": "St Thomas'" },
"tags": [],
"createdAt": "2026-10-19T09:12:00.000Z"
}
],
"page": 1,
"pageSize": 25,
"total": 1,
"totalPages": 1
}Errors have an HTTP status and a message saying what to fix:
{ "error": { "status": 400, "message": "name is required." } }401: no key or a revoked one. 402: the workspace's subscription has lapsed. 404: not found in your workspace. 429: over the hourly limit.
Endpoints
GET /api/v1
Checks your key works: the workspace it belongs to, who it acts as, and the endpoints and webhook events available.
GET /api/v1/candidates
Candidates, newest first, paged.
Query parameters
- search
- Words to find in names, titles, companies and CVs. AND, OR, NOT, quotes and brackets work as a boolean search.
- stage
- Screened, Shortlisted, Interview, Offer, Placed, Rejected
- jobId
- Only candidates for this job.
- scoreBand
- 80+, 60-79 or <60
- near, radius
- A town or postcode, and 5, 10, 25, 50 or 100 miles around it.
- tags
- Tag ids, separated by commas.
- pool
- 1 for talent-pool candidates only.
- updatedSince
- An ISO date-time: only candidates with activity since then - for syncing.
- page, pageSize
- Paging (pageSize up to 100, default 25).
POST /api/v1/candidates
Adds a candidate. Someone already on file - the same email or LinkedIn profile, or the same name and phone number - is returned with duplicate: true instead of being added twice. Someone who asked to be erased can't be added back (409).
JSON body
- name
- Required.
- email, phone, linkedin, location
- Optional.
- currentTitle, currentCompany
- Optional.
- jobId
- Put them on this job.
- stage
- Screened, Shortlisted, Interview, Offer, Placed, Rejected
- source
- referral, job_board, linkedin, direct_sourcing, agency_database, careers_page or other
- skills
- A list of strings.
- note
- Added as a note on their record.
GET /api/v1/candidates/{id}
One candidate.
PATCH /api/v1/candidates/{id}
Updates a candidate. Moving stage is logged and sends the candidate.stage_changed webhook.
JSON body
- stage
- Screened, Shortlisted, Interview, Offer, Placed, Rejected
- email, phone, location, currentTitle, currentCompany
- null clears one.
- note
- Adds a note.
GET /api/v1/jobs
Jobs, newest first, paged.
Query parameters
- status
- open or closed
- page, pageSize
- Paging.
POST /api/v1/jobs
Adds an open job. A client name that's new is added to your clients.
JSON body
- title
- Required.
- client, location, salaryRange, employmentType, seniority
- Optional.
- requiredSkills
- A list of strings.
- description
- The full job description, used when screening.
GET /api/v1/jobs/{id}
One job, with how many candidates it has and how many are at each stage.
GET /api/v1/clients
Clients, by name, paged.
POST /api/v1/clients
Adds a client. One with the same name is returned with duplicate: true.
JSON body
- name
- Required.
- website, industry, address, status
- Optional.
- feePercent, paymentTermsDays, rebateDays
- Numbers.
GET /api/v1/placements
Offers and placements, newest first, paged.
Query parameters
- status
- offered, accepted, declined, started, fell_through, completed
- createdSince
- An ISO date-time.
- page, pageSize
- Paging.
Adding a candidate:
curl -X POST https://www.helixon.co.uk/api/v1/candidates \
-H "Authorization: Bearer hx_your_key" \
-H "Content-Type: application/json" \
-d '{"name": "Ana Ruiz", "email": "ana@example.com", "stage": "Screened"}'Webhooks
Add an endpoint under Settings › Integrations and choose its events (or all of them). When one happens, Helixon POSTs JSON to it: { id, event, createdAt, data }, with Helixon-Event, Helixon-Delivery and Helixon-Signature headers.
Events
- candidate.created
- A candidate is added (screened, applied, imported by API or the LinkedIn extension)
- candidate.stage_changed
- A candidate moves stage
- application.received
- Someone applies through your jobs page or a job's application email
- interview.scheduled
- An interview is scheduled
- placement.created
- An offer is recorded
- placement.updated
- An offer or placement changes status
- invoice.created
- An invoice is raised
- invoice.paid
- An invoice is marked paid
- invoice.credited
- A credit note is raised against an invoice
- reference.received
- A referee answers
- signature.signed
- A document sent for e-signature is signed
- signature.declined
- Someone declines to sign a document
- interview.booked
- A candidate books an interview time from a booking link
- candidate.self_updated
- A candidate updates their details on their private link
Answer with any 2xx within 8 seconds. A timeout, a dropped connection, a 429 or a 5xx is tried twice more (after 1 and 5 seconds) with the same Helixon-Delivery id, so you can ignore repeats. An endpoint that fails 20 times in a row is paused; resume it in Settings.
Check every delivery is really from Helixon: Helixon-Signature is t=<unix time>,v1=<hex>, where v1 is the HMAC-SHA256 of `${t}.${body}` with your endpoint's secret.
import crypto from "node:crypto";
// body: the raw request body, exactly as received
function verify(body, signatureHeader, secret) {
const parts = Object.fromEntries(signatureHeader.split(",").map((p) => p.split("=")));
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${body}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Help
Questions about the API, or something you'd like it to do? Email hello@helixon.co.uk. For Zapier and Make, the help article has a walkthrough.