Alfa API

Connect your own ATS

Connect your company's own applicant tracking system (ATS) to Alfa with a company key. Your developer builds two pieces: a job sync that sends your jobs to Alfa, and a webhook receiver that adds the candidates Alfa screens or sources to your ATS.

What moves where

The integration is a one-way push in each direction. Alfa never reads from your ATS or writes to it; your ATS calls Alfa, and Alfa calls your webhook endpoint.

DataDirectionHow
JobsYour ATS to AlfaYour ATS sends each job, and every change to it, with PUT /v1/integration/ats-jobs/{externalJobId}.
Candidates Alfa screens or sourcesAlfa to your ATSAlfa sends a webhook for each candidate on a linked job, and for each later status change.
Applicants who apply in your ATSStay in your ATSAlfa does not receive or screen them.
Stage changes in your ATSStay in your ATSAlfa does not receive them. A hire or rejection in your ATS does not change the candidate in Alfa.

An Alfa job holds far more than your ATS job: the screening requirements, questions and job advert. Either a recruiter creates the Alfa job and links it to one of your ATS jobs in the Alfa job form, or your ATS creates the Alfa job already linked to its job, either live at once or as a draft that a recruiter reviews and publishes in Alfa.

Before you start

  • Ask Alfa to switch the Alfa API on for your company. Until then, requests answer 403 api_not_enabled.
  • Your company can have one ATS integration. If your company already connected an ATS directly in Alfa, sending an ATS job answers 409 ats_integration_conflict; contact Alfa to switch.
  • An org admin or owner generates a key in Alfa, in Settings › Developers, with the default scopes (ats_jobs:write, jobs:read and candidates:read) and webhooks:manage. Start with a test key on the development API (authentication).

To try every call by hand first, follow the quickstart. This guide covers what your integration must do in production.

1. Send your jobs

Send a job when it is created in your ATS and again whenever it changes, under your own job id. The call creates or updates the job, so it is safe to repeat.

Create or update an ATS job
# Platform keys only: add -H "Alfa-Company-Id: org_2abcDEF123"
curl -X PUT "https://dev-api.welovealfa.com/v1/integration/ats-jobs/job_8841" \
  -H "Authorization: Bearer $ALFA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Senior Backend Engineer","status":"open"}'
  • title and status are required. location, department and url (a link to the job in your ATS) are optional.
  • A detail you leave out keeps its value, and null clears it.
  • open jobs appear in the ATS job picker. Send closed when the job closes in your ATS: it leaves the picker, and Alfa jobs already linked to it stay linked. There is no delete.
  • To check what Alfa holds, list your jobs with GET /v1/integration/ats-jobs/page.

Optional: create the Alfa job from your ATS

With a key that has the jobs:write scope, send the job's title and description to create an Alfa job draft already linked to your job id, so no recruiter has to link it by hand. The description becomes the draft's advert, and Alfa writes the scoring criteria from it. Send publish: true to put the job live at once, ready for sourcing; otherwise it stays a draft until a recruiter publishes it in Alfa or you call POST /v1/integration/jobs/{jobId}/publish.

Create a job linked to an ATS job
# Platform keys only: add -H "Alfa-Company-Id: org_2abcDEF123"
curl -X POST "https://dev-api.welovealfa.com/v1/integration/jobs" \
  -H "Authorization: Bearer $ALFA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"externalJobId":"job_8841","title":"Senior Backend Engineer","description":"We are hiring a Senior Backend Engineer for our London office. You will design and run…"}'
  • Send an Idempotency-Key header, so that a retry returns the same job instead of creating a second draft.
  • If you have not sent the ATS job yet, Alfa records it as open with this title. Keep sending changes to it as above.
  • Change a draft with PATCH /v1/integration/jobs/{jobId} before it is published. Once published, its advert has gone out across Alfa's advertising network and cannot be changed: close the job and create a new one.
  • When the job is filled or closes in your ATS, call POST /v1/integration/jobs/{jobId}/close. The job stops taking applications and its job adverts are taken down. Closing the ATS job alone does not close the Alfa job.

2. Receive candidates

Set one HTTPS webhook endpoint with PUT /v1/integration/webhook-endpoint and store the signing secret from the response. Alfa then sends an event for each candidate on a linked job:

  • candidate.screened: the first time the candidate has a status. Create the candidate in your ATS against the event's externalJobId.
  • candidate.status_changed: every later change, for example when a recruiter shortlists them. Move the candidate in your ATS.
  • candidate.interview_completed: on a job that screens the resume first and then interviews, once the candidate finishes the AI Interview and it is scored. Store the recording, transcript and updated score and summary. On a job that interviews applicants, candidate.screened carries the interview instead.
  • candidate.opted_out: a person Alfa sourced asked Alfa to stop contacting them. Mark them as not to be contacted.

Your receiver must:

  • verify the Alfa-Signature header against the raw body before it trusts an event;
  • answer with a 2xx status within 10 seconds, and do slow work afterwards;
  • ignore an event id it has already processed, because events can repeat;
  • keep the newest statusUpdatedAt for each candidate, because events can arrive out of order;
  • acknowledge event types it does not handle with a 2xx status.

Failed deliveries are retried for 24 hours, and you can send any event again from the delivery log. Read about webhooks.

3. Store each candidate

  • Key the candidate by candidateId: app_… for someone who applied on Alfa, src_… for someone Alfa sourced.
  • Store the score (0 to 100), the summary and the profileUrl, which opens the candidate in Alfa for signed-in recruiters.
  • Download the resume when the event arrives with a resumeUrl. The link works for 24 hours; GET /v1/integration/candidates/{candidateId}/resume returns a fresh one.
  • Store the candidate without a resume when resumeUrl is null. Alfa holds no resume for sourced candidates, so the resume call answers 404 resume_not_available for them.

4. Map statuses to your stages

Alfa sends its own status, and your integration decides which stage of your ATS each one moves the candidate to. Alfa holds no stage mapping for API integrations. For example:

Alfa statusExample stage in your ATS
shortlistedShortlisted, or ready for hiring manager review
suitableScreened, suitable
not_suitableScreened, rejected
sourced_potentialSourced, or prospect

shortlisted outranks the screening outcome. Moving a candidate in your ATS does not change their status in Alfa.

5. Start sourcing (optional)

To have Alfa find candidates for a live linked job, call POST /v1/integration/jobs/{jobId}/sourcing with a key that has the sourcing:write scope, the credits to spend and a unique requestId. The campaign uses your company's sourcing credits, as when a recruiter boosts the job in Alfa, and Alfa chooses where they go: job ads, a search of professional profiles, or both. People Alfa finds by searching arrive as src_… candidates with status sourced_potential; people who apply through a job ad arrive as applicants. GET /v1/integration/jobs/{jobId}/sourcing shows the job's campaigns, the credits each charged and how many sourced candidates Alfa has reported. GET /v1/integration/jobs/{jobId}/postings lists the job ads, with each one's status and link, and counts the job's applicants by the channel they came through.

If you keep a do-not-contact list, set a contact check endpoint with PUT /v1/integration/contact-check-endpoint. Before Alfa first emails someone it found for your jobs, it sends your endpoint their email, phone and LinkedIn URL, and emails them only if you answer contactable: true within 5 seconds. Any other answer, or none, means no email. Read about the contact check.

Going live

Check each step on the development API with a test key:

  1. Send an ATS job with a test key, and see it in the ATS job picker of an Alfa job form.
  2. Set a webhook endpoint, send a test event and verify its signature.
  3. Link an Alfa job to the ATS job, apply to it as a test candidate, and check that candidate.screened creates the candidate in your ATS with the resume stored.
  4. Shortlist that candidate in Alfa, and check that candidate.status_changed moves them to your shortlisted stage.
  5. Close the ATS job, and check that it leaves the picker.

Then generate a live key, set the webhook endpoint on the production API, and send your open jobs. Recruiters can link Alfa jobs to them straight away.

Reference

The API reference lists every field, and the errors page lists every error code and rate limit.