This recipe puts shortlisted candidates into Atlas (the recruitment CRM at recruitwithatlas.com): it creates the person, or finds the existing one, files a note with the screening outcome, optionally adds them to a project, and writes the Atlas id back to PlacementFlow.
Atlas has no Zapier app, so the Atlas side runs in a Code by Zapier (JavaScript) step that calls Atlas's API with your own Atlas API key. PlacementFlow never calls Atlas: the Zap does, with your key.
The Atlas endpoints below come from Atlas's public API reference (https://api.recruitwithatlas.com/api/v1/docs) as of September 2026. Check them, and the request and response field names, against that reference before you switch the Zap on; Atlas can change its API.
Atlas's POST /api/v1/people never merges into an existing record. When the person already exists it answers 409 Conflict with the existing personId. Atlas matches on, in order: source.system plus source.externalId, then email, then LinkedIn URL, then phone.
This recipe sends source.system = "placementflow" and source.externalId = <the PlacementFlow candidate_id>, so the same candidate always matches the same Atlas person. A 409 is the normal "already exists" path, not an error: take the personId from it and update the person with PATCH.
Trigger: PlacementFlow, Candidate Shortlisted. Connect your PlacementFlow account with the API key.
Filter: continue only if
test is false, andcandidate__representation_consent does not exactly match withdrawn.Action: Code by Zapier, Run JavaScript. Input data:
atlasKey: your Atlas API keycandidateId: candidate_idfirstName, lastName, email, linkedinUrl: candidate__first_name, candidate__last_name, candidate__email, candidate__linkedin_urlscore, summary, mustKnowFlags, pfUrl: score, summary, must_know_flags, candidate__pf_urlprojectId (optional): the Atlas project to add the candidate toCode:
const ATLAS = "https://api.recruitwithatlas.com/api/v1";
const headers = {
Authorization: `Bearer ${inputData.atlasKey}`,
"Content-Type": "application/json",
};
// Check these field names against Atlas's API reference.
const person = {
firstName: inputData.firstName,
lastName: inputData.lastName,
email: inputData.email,
linkedinUrl: inputData.linkedinUrl,
};
// 1. Create, or learn the existing personId from the 409.
const created = await fetch(`${ATLAS}/people`, {
method: "POST",
headers,
body: JSON.stringify({
...person,
source: { system: "placementflow", externalId: inputData.candidateId },
}),
});
const createdBody = await created.json();
let personId;
if (created.status === 409) {
personId = createdBody.personId;
// 2. Already exists: update it.
const patched = await fetch(`${ATLAS}/people/${personId}`, {
method: "PATCH",
headers,
body: JSON.stringify(person),
});
if (!patched.ok) throw new Error(`Atlas PATCH failed: ${patched.status}`);
} else if (created.ok) {
personId = createdBody.personId ?? createdBody.id;
} else {
throw new Error(`Atlas POST failed: ${created.status}`);
}
// 3. File the screening note.
const note = [
`PlacementFlow shortlist. Score: ${inputData.score || "n/a"}`,
inputData.summary,
inputData.mustKnowFlags ? `Must-know flags: ${inputData.mustKnowFlags}` : "",
inputData.pfUrl,
].filter(Boolean).join("\n\n");
const noted = await fetch(`${ATLAS}/people/notes`, {
method: "PUT",
headers,
body: JSON.stringify({ personId, note }),
});
if (!noted.ok) throw new Error(`Atlas note failed: ${noted.status}`);
// 4. Optional: add to a project (lands in its first stage by default).
if (inputData.projectId) {
await fetch(`${ATLAS}/projects/${inputData.projectId}/candidates`, {
method: "POST",
headers,
body: JSON.stringify({ personId }),
});
}
return { personId };Action: PlacementFlow, Set candidate external ID. Candidate: candidate_id. Source: atlas. External ID: personId from step 3.
Code steps have run-time limits that depend on your Zapier plan. This step makes three or four short calls; if Atlas is slow and the step times out, Zapier shows the run as errored and you can replay it (the 409 path makes a replay safe).
Add a second Zap so an erasure in PlacementFlow reaches Atlas:
test is false, and external_source exactly matches atlas.external_id. The event carries no name or email, only ids.candidate__external_source = atlas and candidate__external_id = the Atlas personId, so other Zaps can file notes straight onto the person.candidate.erased carries the Atlas id, so the deletion request points at the right person.