Ashby · Arazzo Workflow

Ashby Candidate Intake with Deduplication

Version 1.0.0

Search for a candidate by email, then update the match or create a new candidate.

1 workflow 1 source API 1 provider
View Spec View on GitHub ATSApplicant Tracking SystemRecruitingTalent AcquisitionSourcingCRMSchedulingAnalyticsHiringHR TechAIArazzoWorkflows

Provider

ashby-hq

Workflows

candidate-intake
Deduplicate on email, create or update the candidate, then note and verify.
Searches Ashby for an existing candidate with the supplied email, updates the match or creates a new candidate, records an intake note, and fetches the final candidate profile.
5 steps inputs: email, githubUrl, linkedInUrl, name, note, phoneNumber outputs: candidateId, candidateName, noteId, primaryEmail
1
findCandidate
candidateSearch
Search Ashby for an existing candidate with this email. Ashby matches on the candidate's email addresses, so this is the reliable dedupe key before any write.
2
updateExisting
candidateUpdate
Update the matched candidate with the supplied contact details. Only the supplied fields are changed; everything else on the profile is left intact.
3
createCandidate
candidateCreate
Create a new candidate when the email search returned no match.
4
recordIntakeNote
candidateCreateNote
Attach an intake note to the candidate so recruiters can see where the record came from. Runs against whichever candidate id the branch produced.
5
readCandidate
candidateInfo
Read the candidate back to confirm the write landed and to hand the caller a complete profile.

Source API Descriptions

Arazzo Workflow Specification

Raw ↑
arazzo: 1.0.1
info:
  title: Ashby Candidate Intake with Deduplication
  summary: Search for a candidate by email, then update the match or create a new candidate.
  description: >-
    The foundational Ashby integration pattern for any system that pushes people
    into the ATS. Sourcing tools, referral portals, and CRM syncs all need to
    avoid creating duplicate candidate records, so this workflow searches by
    email first and then branches: when a candidate already exists it updates the
    existing record, and when none is found it creates one. Either way the flow
    attaches an intake note and reads the candidate back so the caller ends with
    a confirmed profile. Every step spells out its request inline so the flow can
    be read and executed without opening the underlying OpenAPI description.
  version: 1.0.0
sourceDescriptions:
- name: ashbyApi
  url: ../openapi/ashby-openapi.yml
  type: openapi
workflows:
- workflowId: candidate-intake
  summary: Deduplicate on email, create or update the candidate, then note and verify.
  description: >-
    Searches Ashby for an existing candidate with the supplied email, updates the
    match or creates a new candidate, records an intake note, and fetches the
    final candidate profile.
  inputs:
    type: object
    required:
    - email
    - name
    properties:
      email:
        type: string
        description: Primary personal email of the candidate, used as the dedupe key.
      name:
        type: string
        description: The first and last name of the candidate (e.g. "Adam Hart").
      phoneNumber:
        type: string
        description: Primary personal phone number of the candidate.
      linkedInUrl:
        type: string
        description: Url to the candidate's LinkedIn profile. Must be a valid Url.
      githubUrl:
        type: string
        description: Url to the candidate's GitHub profile. Must be a valid Url.
      note:
        type: string
        description: Intake note to record on the candidate (e.g. where they came from).
  steps:
  - stepId: findCandidate
    description: >-
      Search Ashby for an existing candidate with this email. Ashby matches on the
      candidate's email addresses, so this is the reliable dedupe key before any
      write.
    operationId: candidateSearch
    requestBody:
      contentType: application/json
      payload:
        email: $inputs.email
    successCriteria:
    - condition: $statusCode == 200
    - condition: $response.body#/success == true
    outputs:
      matchedCandidateId: $response.body#/results/0/id
    onSuccess:
    - name: candidateExists
      type: goto
      stepId: updateExisting
      criteria:
      - context: $response.body
        condition: $.results.length > 0
        type: jsonpath
    - name: candidateMissing
      type: goto
      stepId: createCandidate
      criteria:
      - context: $response.body
        condition: $.results.length == 0
        type: jsonpath
  - stepId: updateExisting
    description: >-
      Update the matched candidate with the supplied contact details. Only the
      supplied fields are changed; everything else on the profile is left intact.
    operationId: candidateUpdate
    requestBody:
      contentType: application/json
      payload:
        candidateId: $steps.findCandidate.outputs.matchedCandidateId
        name: $inputs.name
        phoneNumber: $inputs.phoneNumber
        linkedInUrl: $inputs.linkedInUrl
        githubUrl: $inputs.githubUrl
    successCriteria:
    - condition: $statusCode == 200
    - condition: $response.body#/success == true
    outputs:
      candidateId: $response.body#/results/id
    onSuccess:
    - name: continueToNote
      type: goto
      stepId: recordIntakeNote
  - stepId: createCandidate
    description: >-
      Create a new candidate when the email search returned no match.
    operationId: candidateCreate
    requestBody:
      contentType: application/json
      payload:
        name: $inputs.name
        email: $inputs.email
        phoneNumber: $inputs.phoneNumber
        linkedInUrl: $inputs.linkedInUrl
        githubUrl: $inputs.githubUrl
    successCriteria:
    - condition: $statusCode == 200
    - condition: $response.body#/success == true
    outputs:
      candidateId: $response.body#/results/id
  - stepId: recordIntakeNote
    description: >-
      Attach an intake note to the candidate so recruiters can see where the
      record came from. Runs against whichever candidate id the branch produced.
    operationId: candidateCreateNote
    requestBody:
      contentType: application/json
      payload:
        candidateId: $steps.createCandidate.outputs.candidateId
        note: $inputs.note
        sendNotifications: false
    successCriteria:
    - condition: $statusCode == 200
    - condition: $response.body#/success == true
    outputs:
      noteId: $response.body#/results/id
  - stepId: readCandidate
    description: >-
      Read the candidate back to confirm the write landed and to hand the caller
      a complete profile.
    operationId: candidateInfo
    requestBody:
      contentType: application/json
      payload:
        id: $steps.createCandidate.outputs.candidateId
    successCriteria:
    - condition: $statusCode == 200
    - condition: $response.body#/success == true
    outputs:
      candidateId: $response.body#/results/id
      candidateName: $response.body#/results/name
      primaryEmail: $response.body#/results/primaryEmailAddress/value
  outputs:
    candidateId: $steps.readCandidate.outputs.candidateId
    candidateName: $steps.readCandidate.outputs.candidateName
    primaryEmail: $steps.readCandidate.outputs.primaryEmail
    noteId: $steps.recordIntakeNote.outputs.noteId