Developers

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.