Custom ATS integration
This integration is in beta. Ask the Jack & Jill team to turn it on for your account before you connect.
Overview
Use this when your ATS is not one Jack & Jill already connects to. Your team hosts a small HTTPS API. Jack & Jill reads jobs, stages, and applications from it, and posts a command when you save, move, archive, or hire a candidate in your pipeline.
What it does
- Creates candidates in your ATS when they enter your pipeline, including their resume
- Moves candidates through the stages you define as they progress
- Reads changes back, so a hire, an archive, or a stage move in your ATS shows up in Jack & Jill
- Matches existing people by email, so someone already in your ATS is reused
Prerequisites
- Someone on your team who can deploy an HTTPS API
- A Jack & Jill account with at least one active role
- Custom ATS turned on for that account. Ask the Jack & Jill team if you do not see it under Integrations.
Getting started
1. Host the API
Your team hosts the endpoints in The API. The base URL is HTTPS and ends in /jack-jill/v1, for example https://ats.example.com/jack-jill/v1.
Give Jack & Jill a bearer token and a signing secret. The token authenticates every call Jack & Jill makes. The signing secret signs the commands Jack & Jill posts. Check that signature before you create or move anyone.
2. Connect it in Jack & Jill
Open Integrations → Custom ATS and follow the wizard. Enter the base URL, the bearer token, and the signing secret. Jack & Jill calls GET /jobs to confirm it can reach you.
Candidates Jack & Jill creates are attributed as Jack & Jill.
3. Choose which actions sync
During setup you choose which pipeline actions create or move someone in your ATS.
| Event | What it does in your ATS |
|---|---|
| Shortlist | Creates the candidate and application on the stage whose slot is lead |
| Pass on a lead | Archives the application |
| Request intro | Moves the candidate to the stage whose slot is application_review, when you have one |
| Complete intro | Moves the candidate to the stage whose slot is interview, when you have one |
| Hire | Moves the candidate to the stage whose slot is hired |
Jill archives a candidate only while their current stage slot is lead. Put lead on the stage a new candidate sits in if you want a pass in Jill to archive them in your ATS. Removing a candidate from the shortlist leaves their application where it is.
4. Link a role to a job
On the integration’s roles page, map each Jack & Jill role to a job from GET /jobs. Once a role is linked, actions on that role sync to that job.
How syncing works
Jack & Jill reads your applications about every 15 minutes. That read is what updates Jill. After you connect, Jill also shows a wake URL. POST it when you want the next read to happen now. The body can be empty. If you never call it, the poll still runs.
When you take an action in Jill, Jack & Jill posts one command to POST /commands. The Idempotency-Key header is the identity of that command. Send the same key again and return the original record. Do not create a second one.
If your server is down, Jack & Jill retries after 1 second, 5 seconds, and 30 seconds, then stops that command. A 401 or 403 stops immediately and marks the token invalid. Update the token from the Custom ATS integration page.
The API
Paths below are relative to your base URL. This is the contract your engineering team implements.
Authentication
Reads and commands use Authorization: Bearer <token>.
Commands also send Jack-Signature: sha256=<hex HMAC-SHA256 of the raw body>, signed with the outbound signing secret you enter in Jill.
The wake uses the same header, signed with the separate secret Jill shows after you connect. That secret is not the bearer token.
Reads
| Method and path | What it returns |
|---|---|
GET /jobs |
Jobs a role can link to. |
GET /jobs/{external_job_id}/stages |
That job’s pipeline, in your order. |
GET /applications?external_job_id=&cursor=&limit= |
One page of applications on that job. |
GET /applications/{external_application_id} |
One application. 404 means it is gone. |
GET /candidates?email= |
Candidates with that email. |
GET /candidates/{external_candidate_id} |
One candidate, including whether a resume is already stored. |
GET /jobs returns:
{
"jobs": [
{
"external_id": "job_1",
"title": "Engineer",
"status": "open",
"is_linkable": true,
"department": "Engineering",
"location": "London"
}
]
}is_linkable is true when the job can receive candidates.
GET /jobs/{external_job_id}/stages returns:
{
"stages": [
{ "external_id": "stage_lead", "title": "New lead", "slot": "lead", "order": 0 }
]
}slot is optional. Use lead, application_review, interview, offer, hired, or archived. A stage with no slot is left in place when Jill would otherwise move someone there.
GET /applications returns a page:
{
"applications": [],
"next_cursor": null,
"complete": true
}A full read can span pages. complete: true belongs on the last page only. It means this page plus the earlier pages are the whole job. next_cursor on a full read is a page token. Omit the cursor when you always return the full list.
A delta is optional. When you send one, complete is false and next_cursor is the cursor Jack & Jill sends next time.
Each application looks like this:
{
"external_application_id": "app_1",
"external_candidate_id": "cand_1",
"external_job_id": "job_1",
"candidate": {
"name": "Ada Lovelace",
"primary_email": "ada@example.com",
"linkedin_url": "https://www.linkedin.com/in/ada"
},
"stage": { "external_id": "stage_1", "title": "Application review", "slot": "application_review" },
"outcome": "pending",
"source": "Jack & Jill"
}outcome is pending, hired, or declined. primary_email is required. linkedin_url may be empty. source is optional. Set it to Jack & Jill when Jack & Jill created the candidate, and leave it empty or omit it otherwise. Jack & Jill uses that string to recognise its own candidates on a later read.
GET /candidates?email= returns:
{
"candidates": [
{ "external_id": "cand_1", "primary_email": "ada@example.com", "name": "Ada Lovelace" }
]
}Zero matches means the person is new. One match is reused. Two or more matches make Jack & Jill stop and not create a duplicate.
GET /candidates/{external_candidate_id} returns:
{ "external_id": "cand_1", "has_resume": false }has_resume is required. Jack & Jill uploads a file only when it is false. A missing field, or a 404, is not treated as “no resume”.
An application missing from a full pull stays as Jill last saw it. Jill moves the candidate when a later pull includes that application with a new stage, or with outcome hired or declined.
Commands
POST /commands returns:
{ "external_id": "cand_1", "adopted": false }adopted: true means you matched an existing record instead of creating one.
type |
Fields |
|---|---|
create_candidate |
name, primary_email, linkedin_url, source |
create_application |
external_candidate_id, external_job_id, stage_id |
change_application_stage |
external_application_id, stage_id |
archive_application |
external_application_id |
upload_resume |
external_candidate_id, filename, content_type, content_base64 |
create_candidate_note |
external_candidate_id, note |
create_candidate_tag |
tag_title |
add_candidate_tag |
external_candidate_id, tag_id |
remove_candidate_tag |
external_candidate_id, tag_id |
source on create_candidate is the string Jack & Jill. stage_id on create_application may be null, which means your default stage.
content_base64 is the resume file. Store that file once for a given Idempotency-Key. Jack & Jill sends this only when it created the candidate (adopted is false) and has_resume is false.
create_candidate_note posts one note. The same Idempotency-Key posts that note once. create_candidate_tag ensures a tag title exists and returns its external_id. The same title returns the same id. A response without external_id is retried. add_candidate_tag and remove_candidate_tag change whether that tag is on the candidate. Adding a tag that is already there, and removing one that is not, changes nothing. An empty note or an empty tag title is rejected and not retried.
Errors
| Response | What Jack & Jill does |
|---|---|
401, 403 |
Stops and marks the token invalid. |
429 with Retry-After |
Waits and does not count the wait as a failed attempt. |
409, 422 |
Stops that command. |
Timeout, connection failure, 5xx |
Retries after 1 second, 5 seconds, and 30 seconds, then stops that command. |
404 on one application |
Treats that application as not found. |
Wake URL
After you connect, Jill shows a URL and a signing secret. POST that URL when an application changes. Sign the raw bytes with the wake secret as Jack-Signature. Jill then reads your API.
FAQ
Will this create duplicate candidates? Jack & Jill looks the person up by email first. No match means they are new. One match is reused. Two or more matches stop the create, so Jill does not pick one.
What if an application is missing from a later read? Jill keeps the last snapshot. Jill moves the candidate when a later read includes that application with a new stage, or with outcome hired or declined. A shorter list does not archive anyone and does not delete them.
What happens if I disconnect and reconnect? Your ATS data stays as it is. Reconnecting starts syncing again from that point. The wake URL and its signing secret stay the same.
Can I change which events sync after setup? Yes. Open Integrations → Custom ATS and toggle them.
Do I have to call the wake URL? No. Jill still reads your API on the regular poll. The wake only asks for that read now.
What if I add a new role after setup? Link it to a job from your API. Candidates on that role sync after the link exists.
Support
This integration is in beta. Ask the Jack & Jill team to turn it on for your account before you connect. Email christian@jackandjill.ai.