refactor(ashby): align tools, block, and triggers with Ashby API (#4288)

* refactor(ashby): align tools, block, and triggers with Ashby API

Audit-driven refactor to destructure rich fields per Ashby's API docs,
centralize output shapes via shared mappers in tools/ashby/utils.ts,
and align webhook provider handler with trigger IDs via a shared
action map. Removes stale block outputs left over from prior flat
response shapes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(ashby): remove stale noteId output and reject ping events

- Remove stale `noteId` output descriptor from block (create_note
  now returns `id` at the top level via the shared note mapper).
- Explicitly reject `ping` events in the webhook matchEvent before
  falling back to the generic triggerId check, so webhook records
  missing triggerId cannot execute workflows on Ashby ping probes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(ashby): trim optional ID params in create/update tools

Optional ID params in create_application, change_application_stage,
and update_candidate were passed through to the request body without
.trim(), unlike their required ID siblings. Normalize to prevent
copy-paste whitespace errors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(ashby): add subblock migration for removed filterCandidateId

Add SUBBLOCK_ID_MIGRATIONS entry so deployed workflows that previously
used the `filterCandidateId` subBlock on `list_applications` don't break
after the field was removed (Ashby's application.list doesn't filter by
candidateId). Also regenerate docs to sync noteId removal.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(ashby): final API alignment from parallel validation

- create_candidate: email is optional per Ashby docs (only name is
  required); tool, types, and block all made non-required.
- list_applications: guard NaN when createdAfter can't be parsed so we
  don't send a bad value to Ashby's API.
- webhook provider: replace createHmacVerifier with explicit
  fail-closed verifyAuth that 401s when secretToken, signature header,
  or signature match is missing (was previously fail-open on missing
  secret).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(ashby): preserve input.data path in webhook formatInput

Restore the explicit `data` key alongside the spread so deployed
workflows that reference `input.data.application.*`, `input.data.candidate.*`,
etc. keep working. The spread alone dropped those paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(ashby): drop legacy input.data key from webhook formatInput

Keep formatInput aligned with the advertised trigger outputs schema
(flat top-level entities) and drop the legacy input.data.* compat path.
Every field declared in each trigger's outputs is now populated 1:1 by
the data spread plus the explicit action key — no undeclared keys.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(ashby): trim remaining ID body params for parity

Add .trim() on sourceId (create_candidate), jobId (list_applications),
applicationId and interviewStageId (list_interviews) to match the
trim-on-IDs pattern used across the rest of the Ashby tools and guard
against copy-paste whitespace.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* update docs

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Waleed
2026-04-24 13:29:26 -07:00
committed by GitHub
co-authored by Claude Opus 4.7
parent efc868263a
commit f330fe22a2
37 changed files with 2854 additions and 1380 deletions
+431 -188
View File
@@ -38,7 +38,7 @@ Integrate Ashby into the workflow. Manage candidates (list, get, create, update,
### `ashby_add_candidate_tag`
Adds a tag to a candidate in Ashby.
Adds a tag to a candidate in Ashby and returns the updated candidate.
#### Input
@@ -52,7 +52,37 @@ Adds a tag to a candidate in Ashby.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the tag was successfully added |
| `candidates` | json | List of candidates with rich fields \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], linkedInUrl, githubUrl, profileUrl, position, company, school, timezone, location with locationComponents\[\], tags\[\], applicationIds\[\], customFields\[\], resumeFileHandle, fileHandles\[\], source with sourceType, creditedToUser, fraudStatus, createdAt, updatedAt\) |
| `jobs` | json | List of jobs \(id, title, confidential, status, employmentType, locationId, departmentId, defaultInterviewPlanId, interviewPlanIds\[\], customFields\[\], jobPostingIds\[\], customRequisitionId, brandId, hiringTeam\[\], author, createdAt, updatedAt, openedAt, closedAt, location with address, openings\[\] with latestVersion, compensation with compensationTiers\[\]\) |
| `applications` | json | List of applications \(id, status, customFields\[\], candidate summary, currentInterviewStage, source with sourceType, archiveReason with customFields\[\], archivedAt, job summary, creditedToUser, hiringTeam\[\], appliedViaJobPostingId, submitterClientIp, submitterUserAgent, createdAt, updatedAt\) |
| `notes` | json | List of notes \(id, content, author, isPrivate, createdAt\) |
| `offers` | json | List of offers \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion with id/startDate/salary/createdAt/openingId/customFields\[\]/fileHandles\[\]/author/approvalStatus\) |
| `archiveReasons` | json | List of archive reasons \(id, text, reasonType \[RejectedByCandidate/RejectedByOrg/Other\], isArchived\) |
| `sources` | json | List of sources \(id, title, isArchived, sourceType \{id, title, isArchived\}\) |
| `customFields` | json | List of custom field definitions \(id, title, isPrivate, fieldType, objectType, isArchived, isRequired, selectableValues\[\] \{label, value, isArchived\}\) |
| `departments` | json | List of departments \(id, name, externalName, isArchived, parentId, createdAt, updatedAt\) |
| `locations` | json | List of locations \(id, name, externalName, isArchived, isRemote, workplaceType, parentLocationId, type, address with addressCountry/Region/Locality/postalCode/streetAddress\) |
| `jobPostings` | json | List of job postings \(id, title, jobId, departmentName, teamName, locationName, locationIds, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensationTierSummary, shouldDisplayCompensationOnJobBoard, updatedAt\) |
| `openings` | json | List of openings \(id, openedAt, closedAt, isArchived, archivedAt, closeReasonId, openingState, latestVersion with identifier/description/authorId/createdAt/teamId/jobIds\[\]/targetHireDate/targetStartDate/isBackfill/employmentType/locationIds\[\]/hiringTeam\[\]/customFields\[\]\) |
| `users` | json | List of users \(id, firstName, lastName, email, globalRole, isEnabled, updatedAt, managerId\) |
| `interviewSchedules` | json | List of interview schedules \(id, applicationId, interviewStageId, interviewEvents\[\] with interviewerUserIds/startTime/endTime/feedbackLink/location/meetingLink/hasSubmittedFeedback, status, scheduledBy, createdAt, updatedAt\) |
| `tags` | json | List of candidate tags \(id, title, isArchived\) |
| `id` | string | Resource UUID |
| `name` | string | Resource name |
| `title` | string | Job title or job posting title |
| `status` | string | Status |
| `candidate` | json | Candidate details \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], customFields\[\], source, creditedToUser, createdAt, updatedAt\) |
| `job` | json | Job details \(id, title, status, employmentType, locationId, departmentId, hiringTeam\[\], author, location, openings\[\], compensation, createdAt, updatedAt\) |
| `application` | json | Application details \(id, status, customFields\[\], candidate, currentInterviewStage, source, archiveReason, job, hiringTeam\[\], createdAt, updatedAt\) |
| `offer` | json | Offer details \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion\) |
| `jobPosting` | json | Job posting details \(id, title, descriptionPlain, descriptionHtml, descriptionSocial, descriptionParts, departmentName, teamName, teamNameHierarchy\[\], jobId, locationName, locationIds, linkedData, address, isRemote, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensation, updatedAt\) |
| `content` | string | Note content |
| `author` | json | Note author \(id, firstName, lastName, email, globalRole, isEnabled\) |
| `isPrivate` | boolean | Whether the note is private |
| `createdAt` | string | ISO 8601 creation timestamp |
| `moreDataAvailable` | boolean | Whether more pages exist |
| `nextCursor` | string | Pagination cursor for next page |
| `syncToken` | string | Sync token for incremental updates |
### `ashby_change_application_stage`
@@ -71,8 +101,37 @@ Moves an application to a different interview stage. Requires an archive reason
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `applicationId` | string | Application UUID |
| `stageId` | string | New interview stage UUID |
| `candidates` | json | List of candidates with rich fields \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], linkedInUrl, githubUrl, profileUrl, position, company, school, timezone, location with locationComponents\[\], tags\[\], applicationIds\[\], customFields\[\], resumeFileHandle, fileHandles\[\], source with sourceType, creditedToUser, fraudStatus, createdAt, updatedAt\) |
| `jobs` | json | List of jobs \(id, title, confidential, status, employmentType, locationId, departmentId, defaultInterviewPlanId, interviewPlanIds\[\], customFields\[\], jobPostingIds\[\], customRequisitionId, brandId, hiringTeam\[\], author, createdAt, updatedAt, openedAt, closedAt, location with address, openings\[\] with latestVersion, compensation with compensationTiers\[\]\) |
| `applications` | json | List of applications \(id, status, customFields\[\], candidate summary, currentInterviewStage, source with sourceType, archiveReason with customFields\[\], archivedAt, job summary, creditedToUser, hiringTeam\[\], appliedViaJobPostingId, submitterClientIp, submitterUserAgent, createdAt, updatedAt\) |
| `notes` | json | List of notes \(id, content, author, isPrivate, createdAt\) |
| `offers` | json | List of offers \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion with id/startDate/salary/createdAt/openingId/customFields\[\]/fileHandles\[\]/author/approvalStatus\) |
| `archiveReasons` | json | List of archive reasons \(id, text, reasonType \[RejectedByCandidate/RejectedByOrg/Other\], isArchived\) |
| `sources` | json | List of sources \(id, title, isArchived, sourceType \{id, title, isArchived\}\) |
| `customFields` | json | List of custom field definitions \(id, title, isPrivate, fieldType, objectType, isArchived, isRequired, selectableValues\[\] \{label, value, isArchived\}\) |
| `departments` | json | List of departments \(id, name, externalName, isArchived, parentId, createdAt, updatedAt\) |
| `locations` | json | List of locations \(id, name, externalName, isArchived, isRemote, workplaceType, parentLocationId, type, address with addressCountry/Region/Locality/postalCode/streetAddress\) |
| `jobPostings` | json | List of job postings \(id, title, jobId, departmentName, teamName, locationName, locationIds, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensationTierSummary, shouldDisplayCompensationOnJobBoard, updatedAt\) |
| `openings` | json | List of openings \(id, openedAt, closedAt, isArchived, archivedAt, closeReasonId, openingState, latestVersion with identifier/description/authorId/createdAt/teamId/jobIds\[\]/targetHireDate/targetStartDate/isBackfill/employmentType/locationIds\[\]/hiringTeam\[\]/customFields\[\]\) |
| `users` | json | List of users \(id, firstName, lastName, email, globalRole, isEnabled, updatedAt, managerId\) |
| `interviewSchedules` | json | List of interview schedules \(id, applicationId, interviewStageId, interviewEvents\[\] with interviewerUserIds/startTime/endTime/feedbackLink/location/meetingLink/hasSubmittedFeedback, status, scheduledBy, createdAt, updatedAt\) |
| `tags` | json | List of candidate tags \(id, title, isArchived\) |
| `id` | string | Resource UUID |
| `name` | string | Resource name |
| `title` | string | Job title or job posting title |
| `status` | string | Status |
| `candidate` | json | Candidate details \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], customFields\[\], source, creditedToUser, createdAt, updatedAt\) |
| `job` | json | Job details \(id, title, status, employmentType, locationId, departmentId, hiringTeam\[\], author, location, openings\[\], compensation, createdAt, updatedAt\) |
| `application` | json | Application details \(id, status, customFields\[\], candidate, currentInterviewStage, source, archiveReason, job, hiringTeam\[\], createdAt, updatedAt\) |
| `offer` | json | Offer details \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion\) |
| `jobPosting` | json | Job posting details \(id, title, descriptionPlain, descriptionHtml, descriptionSocial, descriptionParts, departmentName, teamName, teamNameHierarchy\[\], jobId, locationName, locationIds, linkedData, address, isRemote, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensation, updatedAt\) |
| `content` | string | Note content |
| `author` | json | Note author \(id, firstName, lastName, email, globalRole, isEnabled\) |
| `isPrivate` | boolean | Whether the note is private |
| `createdAt` | string | ISO 8601 creation timestamp |
| `moreDataAvailable` | boolean | Whether more pages exist |
| `nextCursor` | string | Pagination cursor for next page |
| `syncToken` | string | Sync token for incremental updates |
### `ashby_create_application`
@@ -95,7 +154,37 @@ Creates a new application for a candidate on a job. Optionally specify interview
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `applicationId` | string | Created application UUID |
| `candidates` | json | List of candidates with rich fields \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], linkedInUrl, githubUrl, profileUrl, position, company, school, timezone, location with locationComponents\[\], tags\[\], applicationIds\[\], customFields\[\], resumeFileHandle, fileHandles\[\], source with sourceType, creditedToUser, fraudStatus, createdAt, updatedAt\) |
| `jobs` | json | List of jobs \(id, title, confidential, status, employmentType, locationId, departmentId, defaultInterviewPlanId, interviewPlanIds\[\], customFields\[\], jobPostingIds\[\], customRequisitionId, brandId, hiringTeam\[\], author, createdAt, updatedAt, openedAt, closedAt, location with address, openings\[\] with latestVersion, compensation with compensationTiers\[\]\) |
| `applications` | json | List of applications \(id, status, customFields\[\], candidate summary, currentInterviewStage, source with sourceType, archiveReason with customFields\[\], archivedAt, job summary, creditedToUser, hiringTeam\[\], appliedViaJobPostingId, submitterClientIp, submitterUserAgent, createdAt, updatedAt\) |
| `notes` | json | List of notes \(id, content, author, isPrivate, createdAt\) |
| `offers` | json | List of offers \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion with id/startDate/salary/createdAt/openingId/customFields\[\]/fileHandles\[\]/author/approvalStatus\) |
| `archiveReasons` | json | List of archive reasons \(id, text, reasonType \[RejectedByCandidate/RejectedByOrg/Other\], isArchived\) |
| `sources` | json | List of sources \(id, title, isArchived, sourceType \{id, title, isArchived\}\) |
| `customFields` | json | List of custom field definitions \(id, title, isPrivate, fieldType, objectType, isArchived, isRequired, selectableValues\[\] \{label, value, isArchived\}\) |
| `departments` | json | List of departments \(id, name, externalName, isArchived, parentId, createdAt, updatedAt\) |
| `locations` | json | List of locations \(id, name, externalName, isArchived, isRemote, workplaceType, parentLocationId, type, address with addressCountry/Region/Locality/postalCode/streetAddress\) |
| `jobPostings` | json | List of job postings \(id, title, jobId, departmentName, teamName, locationName, locationIds, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensationTierSummary, shouldDisplayCompensationOnJobBoard, updatedAt\) |
| `openings` | json | List of openings \(id, openedAt, closedAt, isArchived, archivedAt, closeReasonId, openingState, latestVersion with identifier/description/authorId/createdAt/teamId/jobIds\[\]/targetHireDate/targetStartDate/isBackfill/employmentType/locationIds\[\]/hiringTeam\[\]/customFields\[\]\) |
| `users` | json | List of users \(id, firstName, lastName, email, globalRole, isEnabled, updatedAt, managerId\) |
| `interviewSchedules` | json | List of interview schedules \(id, applicationId, interviewStageId, interviewEvents\[\] with interviewerUserIds/startTime/endTime/feedbackLink/location/meetingLink/hasSubmittedFeedback, status, scheduledBy, createdAt, updatedAt\) |
| `tags` | json | List of candidate tags \(id, title, isArchived\) |
| `id` | string | Resource UUID |
| `name` | string | Resource name |
| `title` | string | Job title or job posting title |
| `status` | string | Status |
| `candidate` | json | Candidate details \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], customFields\[\], source, creditedToUser, createdAt, updatedAt\) |
| `job` | json | Job details \(id, title, status, employmentType, locationId, departmentId, hiringTeam\[\], author, location, openings\[\], compensation, createdAt, updatedAt\) |
| `application` | json | Application details \(id, status, customFields\[\], candidate, currentInterviewStage, source, archiveReason, job, hiringTeam\[\], createdAt, updatedAt\) |
| `offer` | json | Offer details \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion\) |
| `jobPosting` | json | Job posting details \(id, title, descriptionPlain, descriptionHtml, descriptionSocial, descriptionParts, departmentName, teamName, teamNameHierarchy\[\], jobId, locationName, locationIds, linkedData, address, isRemote, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensation, updatedAt\) |
| `content` | string | Note content |
| `author` | json | Note author \(id, firstName, lastName, email, globalRole, isEnabled\) |
| `isPrivate` | boolean | Whether the note is private |
| `createdAt` | string | ISO 8601 creation timestamp |
| `moreDataAvailable` | boolean | Whether more pages exist |
| `nextCursor` | string | Pagination cursor for next page |
| `syncToken` | string | Sync token for incremental updates |
### `ashby_create_candidate`
@@ -107,7 +196,7 @@ Creates a new candidate record in Ashby.
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Ashby API Key |
| `name` | string | Yes | The candidate full name |
| `email` | string | Yes | Primary email address for the candidate |
| `email` | string | No | Primary email address for the candidate |
| `phoneNumber` | string | No | Primary phone number for the candidate |
| `linkedInUrl` | string | No | LinkedIn profile URL |
| `githubUrl` | string | No | GitHub profile URL |
@@ -117,17 +206,37 @@ Creates a new candidate record in Ashby.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Created candidate UUID |
| `name` | string | Full name |
| `primaryEmailAddress` | object | Primary email contact info |
| ↳ `value` | string | Email address |
| ↳ `type` | string | Contact type \(Personal, Work, Other\) |
| ↳ `isPrimary` | boolean | Whether this is the primary email |
| `primaryPhoneNumber` | object | Primary phone contact info |
| ↳ `value` | string | Phone number |
| ↳ `type` | string | Contact type \(Personal, Work, Other\) |
| ↳ `isPrimary` | boolean | Whether this is the primary phone |
| `candidates` | json | List of candidates with rich fields \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], linkedInUrl, githubUrl, profileUrl, position, company, school, timezone, location with locationComponents\[\], tags\[\], applicationIds\[\], customFields\[\], resumeFileHandle, fileHandles\[\], source with sourceType, creditedToUser, fraudStatus, createdAt, updatedAt\) |
| `jobs` | json | List of jobs \(id, title, confidential, status, employmentType, locationId, departmentId, defaultInterviewPlanId, interviewPlanIds\[\], customFields\[\], jobPostingIds\[\], customRequisitionId, brandId, hiringTeam\[\], author, createdAt, updatedAt, openedAt, closedAt, location with address, openings\[\] with latestVersion, compensation with compensationTiers\[\]\) |
| `applications` | json | List of applications \(id, status, customFields\[\], candidate summary, currentInterviewStage, source with sourceType, archiveReason with customFields\[\], archivedAt, job summary, creditedToUser, hiringTeam\[\], appliedViaJobPostingId, submitterClientIp, submitterUserAgent, createdAt, updatedAt\) |
| `notes` | json | List of notes \(id, content, author, isPrivate, createdAt\) |
| `offers` | json | List of offers \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion with id/startDate/salary/createdAt/openingId/customFields\[\]/fileHandles\[\]/author/approvalStatus\) |
| `archiveReasons` | json | List of archive reasons \(id, text, reasonType \[RejectedByCandidate/RejectedByOrg/Other\], isArchived\) |
| `sources` | json | List of sources \(id, title, isArchived, sourceType \{id, title, isArchived\}\) |
| `customFields` | json | List of custom field definitions \(id, title, isPrivate, fieldType, objectType, isArchived, isRequired, selectableValues\[\] \{label, value, isArchived\}\) |
| `departments` | json | List of departments \(id, name, externalName, isArchived, parentId, createdAt, updatedAt\) |
| `locations` | json | List of locations \(id, name, externalName, isArchived, isRemote, workplaceType, parentLocationId, type, address with addressCountry/Region/Locality/postalCode/streetAddress\) |
| `jobPostings` | json | List of job postings \(id, title, jobId, departmentName, teamName, locationName, locationIds, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensationTierSummary, shouldDisplayCompensationOnJobBoard, updatedAt\) |
| `openings` | json | List of openings \(id, openedAt, closedAt, isArchived, archivedAt, closeReasonId, openingState, latestVersion with identifier/description/authorId/createdAt/teamId/jobIds\[\]/targetHireDate/targetStartDate/isBackfill/employmentType/locationIds\[\]/hiringTeam\[\]/customFields\[\]\) |
| `users` | json | List of users \(id, firstName, lastName, email, globalRole, isEnabled, updatedAt, managerId\) |
| `interviewSchedules` | json | List of interview schedules \(id, applicationId, interviewStageId, interviewEvents\[\] with interviewerUserIds/startTime/endTime/feedbackLink/location/meetingLink/hasSubmittedFeedback, status, scheduledBy, createdAt, updatedAt\) |
| `tags` | json | List of candidate tags \(id, title, isArchived\) |
| `id` | string | Resource UUID |
| `name` | string | Resource name |
| `title` | string | Job title or job posting title |
| `status` | string | Status |
| `candidate` | json | Candidate details \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], customFields\[\], source, creditedToUser, createdAt, updatedAt\) |
| `job` | json | Job details \(id, title, status, employmentType, locationId, departmentId, hiringTeam\[\], author, location, openings\[\], compensation, createdAt, updatedAt\) |
| `application` | json | Application details \(id, status, customFields\[\], candidate, currentInterviewStage, source, archiveReason, job, hiringTeam\[\], createdAt, updatedAt\) |
| `offer` | json | Offer details \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion\) |
| `jobPosting` | json | Job posting details \(id, title, descriptionPlain, descriptionHtml, descriptionSocial, descriptionParts, departmentName, teamName, teamNameHierarchy\[\], jobId, locationName, locationIds, linkedData, address, isRemote, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensation, updatedAt\) |
| `content` | string | Note content |
| `author` | json | Note author \(id, firstName, lastName, email, globalRole, isEnabled\) |
| `isPrivate` | boolean | Whether the note is private |
| `createdAt` | string | ISO 8601 creation timestamp |
| `moreDataAvailable` | boolean | Whether more pages exist |
| `nextCursor` | string | Pagination cursor for next page |
| `syncToken` | string | Sync token for incremental updates |
### `ashby_create_note`
@@ -147,7 +256,15 @@ Creates a note on a candidate in Ashby. Supports plain text and HTML content (bo
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `noteId` | string | Created note UUID |
| `id` | string | Created note UUID |
| `createdAt` | string | ISO 8601 creation timestamp |
| `isPrivate` | boolean | Whether the note is private |
| `content` | string | Note content |
| `author` | object | Author of the note |
| ↳ `id` | string | Author user UUID |
| ↳ `firstName` | string | Author first name |
| ↳ `lastName` | string | Author last name |
| ↳ `email` | string | Author email |
### `ashby_get_application`
@@ -164,28 +281,37 @@ Retrieves full details about a single application by its ID.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Application UUID |
| `status` | string | Application status \(Active, Hired, Archived, Lead\) |
| `candidate` | object | Associated candidate |
| ↳ `id` | string | Candidate UUID |
| ↳ `name` | string | Candidate name |
| `job` | object | Associated job |
| ↳ `id` | string | Job UUID |
| ↳ `title` | string | Job title |
| `currentInterviewStage` | object | Current interview stage |
| ↳ `id` | string | Stage UUID |
| ↳ `title` | string | Stage title |
| ↳ `type` | string | Stage type |
| `source` | object | Application source |
| ↳ `id` | string | Source UUID |
| ↳ `title` | string | Source title |
| `archiveReason` | object | Reason for archival |
| ↳ `id` | string | Reason UUID |
| ↳ `text` | string | Reason text |
| ↳ `reasonType` | string | Reason type |
| `archivedAt` | string | ISO 8601 archive timestamp |
| `candidates` | json | List of candidates with rich fields \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], linkedInUrl, githubUrl, profileUrl, position, company, school, timezone, location with locationComponents\[\], tags\[\], applicationIds\[\], customFields\[\], resumeFileHandle, fileHandles\[\], source with sourceType, creditedToUser, fraudStatus, createdAt, updatedAt\) |
| `jobs` | json | List of jobs \(id, title, confidential, status, employmentType, locationId, departmentId, defaultInterviewPlanId, interviewPlanIds\[\], customFields\[\], jobPostingIds\[\], customRequisitionId, brandId, hiringTeam\[\], author, createdAt, updatedAt, openedAt, closedAt, location with address, openings\[\] with latestVersion, compensation with compensationTiers\[\]\) |
| `applications` | json | List of applications \(id, status, customFields\[\], candidate summary, currentInterviewStage, source with sourceType, archiveReason with customFields\[\], archivedAt, job summary, creditedToUser, hiringTeam\[\], appliedViaJobPostingId, submitterClientIp, submitterUserAgent, createdAt, updatedAt\) |
| `notes` | json | List of notes \(id, content, author, isPrivate, createdAt\) |
| `offers` | json | List of offers \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion with id/startDate/salary/createdAt/openingId/customFields\[\]/fileHandles\[\]/author/approvalStatus\) |
| `archiveReasons` | json | List of archive reasons \(id, text, reasonType \[RejectedByCandidate/RejectedByOrg/Other\], isArchived\) |
| `sources` | json | List of sources \(id, title, isArchived, sourceType \{id, title, isArchived\}\) |
| `customFields` | json | List of custom field definitions \(id, title, isPrivate, fieldType, objectType, isArchived, isRequired, selectableValues\[\] \{label, value, isArchived\}\) |
| `departments` | json | List of departments \(id, name, externalName, isArchived, parentId, createdAt, updatedAt\) |
| `locations` | json | List of locations \(id, name, externalName, isArchived, isRemote, workplaceType, parentLocationId, type, address with addressCountry/Region/Locality/postalCode/streetAddress\) |
| `jobPostings` | json | List of job postings \(id, title, jobId, departmentName, teamName, locationName, locationIds, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensationTierSummary, shouldDisplayCompensationOnJobBoard, updatedAt\) |
| `openings` | json | List of openings \(id, openedAt, closedAt, isArchived, archivedAt, closeReasonId, openingState, latestVersion with identifier/description/authorId/createdAt/teamId/jobIds\[\]/targetHireDate/targetStartDate/isBackfill/employmentType/locationIds\[\]/hiringTeam\[\]/customFields\[\]\) |
| `users` | json | List of users \(id, firstName, lastName, email, globalRole, isEnabled, updatedAt, managerId\) |
| `interviewSchedules` | json | List of interview schedules \(id, applicationId, interviewStageId, interviewEvents\[\] with interviewerUserIds/startTime/endTime/feedbackLink/location/meetingLink/hasSubmittedFeedback, status, scheduledBy, createdAt, updatedAt\) |
| `tags` | json | List of candidate tags \(id, title, isArchived\) |
| `id` | string | Resource UUID |
| `name` | string | Resource name |
| `title` | string | Job title or job posting title |
| `status` | string | Status |
| `candidate` | json | Candidate details \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], customFields\[\], source, creditedToUser, createdAt, updatedAt\) |
| `job` | json | Job details \(id, title, status, employmentType, locationId, departmentId, hiringTeam\[\], author, location, openings\[\], compensation, createdAt, updatedAt\) |
| `application` | json | Application details \(id, status, customFields\[\], candidate, currentInterviewStage, source, archiveReason, job, hiringTeam\[\], createdAt, updatedAt\) |
| `offer` | json | Offer details \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion\) |
| `jobPosting` | json | Job posting details \(id, title, descriptionPlain, descriptionHtml, descriptionSocial, descriptionParts, departmentName, teamName, teamNameHierarchy\[\], jobId, locationName, locationIds, linkedData, address, isRemote, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensation, updatedAt\) |
| `content` | string | Note content |
| `author` | json | Note author \(id, firstName, lastName, email, globalRole, isEnabled\) |
| `isPrivate` | boolean | Whether the note is private |
| `createdAt` | string | ISO 8601 creation timestamp |
| `updatedAt` | string | ISO 8601 last update timestamp |
| `moreDataAvailable` | boolean | Whether more pages exist |
| `nextCursor` | string | Pagination cursor for next page |
| `syncToken` | string | Sync token for incremental updates |
### `ashby_get_candidate`
@@ -202,27 +328,37 @@ Retrieves full details about a single candidate by their ID.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Candidate UUID |
| `name` | string | Full name |
| `primaryEmailAddress` | object | Primary email contact info |
| ↳ `value` | string | Email address |
| ↳ `type` | string | Contact type \(Personal, Work, Other\) |
| ↳ `isPrimary` | boolean | Whether this is the primary email |
| `primaryPhoneNumber` | object | Primary phone contact info |
| ↳ `value` | string | Phone number |
| ↳ `type` | string | Contact type \(Personal, Work, Other\) |
| ↳ `isPrimary` | boolean | Whether this is the primary phone |
| `profileUrl` | string | URL to the candidate Ashby profile |
| `position` | string | Current position or title |
| `company` | string | Current company |
| `linkedInUrl` | string | LinkedIn profile URL |
| `githubUrl` | string | GitHub profile URL |
| `tags` | array | Tags applied to the candidate |
| ↳ `id` | string | Tag UUID |
| ↳ `title` | string | Tag title |
| `applicationIds` | array | IDs of associated applications |
| `candidates` | json | List of candidates with rich fields \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], linkedInUrl, githubUrl, profileUrl, position, company, school, timezone, location with locationComponents\[\], tags\[\], applicationIds\[\], customFields\[\], resumeFileHandle, fileHandles\[\], source with sourceType, creditedToUser, fraudStatus, createdAt, updatedAt\) |
| `jobs` | json | List of jobs \(id, title, confidential, status, employmentType, locationId, departmentId, defaultInterviewPlanId, interviewPlanIds\[\], customFields\[\], jobPostingIds\[\], customRequisitionId, brandId, hiringTeam\[\], author, createdAt, updatedAt, openedAt, closedAt, location with address, openings\[\] with latestVersion, compensation with compensationTiers\[\]\) |
| `applications` | json | List of applications \(id, status, customFields\[\], candidate summary, currentInterviewStage, source with sourceType, archiveReason with customFields\[\], archivedAt, job summary, creditedToUser, hiringTeam\[\], appliedViaJobPostingId, submitterClientIp, submitterUserAgent, createdAt, updatedAt\) |
| `notes` | json | List of notes \(id, content, author, isPrivate, createdAt\) |
| `offers` | json | List of offers \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion with id/startDate/salary/createdAt/openingId/customFields\[\]/fileHandles\[\]/author/approvalStatus\) |
| `archiveReasons` | json | List of archive reasons \(id, text, reasonType \[RejectedByCandidate/RejectedByOrg/Other\], isArchived\) |
| `sources` | json | List of sources \(id, title, isArchived, sourceType \{id, title, isArchived\}\) |
| `customFields` | json | List of custom field definitions \(id, title, isPrivate, fieldType, objectType, isArchived, isRequired, selectableValues\[\] \{label, value, isArchived\}\) |
| `departments` | json | List of departments \(id, name, externalName, isArchived, parentId, createdAt, updatedAt\) |
| `locations` | json | List of locations \(id, name, externalName, isArchived, isRemote, workplaceType, parentLocationId, type, address with addressCountry/Region/Locality/postalCode/streetAddress\) |
| `jobPostings` | json | List of job postings \(id, title, jobId, departmentName, teamName, locationName, locationIds, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensationTierSummary, shouldDisplayCompensationOnJobBoard, updatedAt\) |
| `openings` | json | List of openings \(id, openedAt, closedAt, isArchived, archivedAt, closeReasonId, openingState, latestVersion with identifier/description/authorId/createdAt/teamId/jobIds\[\]/targetHireDate/targetStartDate/isBackfill/employmentType/locationIds\[\]/hiringTeam\[\]/customFields\[\]\) |
| `users` | json | List of users \(id, firstName, lastName, email, globalRole, isEnabled, updatedAt, managerId\) |
| `interviewSchedules` | json | List of interview schedules \(id, applicationId, interviewStageId, interviewEvents\[\] with interviewerUserIds/startTime/endTime/feedbackLink/location/meetingLink/hasSubmittedFeedback, status, scheduledBy, createdAt, updatedAt\) |
| `tags` | json | List of candidate tags \(id, title, isArchived\) |
| `id` | string | Resource UUID |
| `name` | string | Resource name |
| `title` | string | Job title or job posting title |
| `status` | string | Status |
| `candidate` | json | Candidate details \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], customFields\[\], source, creditedToUser, createdAt, updatedAt\) |
| `job` | json | Job details \(id, title, status, employmentType, locationId, departmentId, hiringTeam\[\], author, location, openings\[\], compensation, createdAt, updatedAt\) |
| `application` | json | Application details \(id, status, customFields\[\], candidate, currentInterviewStage, source, archiveReason, job, hiringTeam\[\], createdAt, updatedAt\) |
| `offer` | json | Offer details \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion\) |
| `jobPosting` | json | Job posting details \(id, title, descriptionPlain, descriptionHtml, descriptionSocial, descriptionParts, departmentName, teamName, teamNameHierarchy\[\], jobId, locationName, locationIds, linkedData, address, isRemote, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensation, updatedAt\) |
| `content` | string | Note content |
| `author` | json | Note author \(id, firstName, lastName, email, globalRole, isEnabled\) |
| `isPrivate` | boolean | Whether the note is private |
| `createdAt` | string | ISO 8601 creation timestamp |
| `updatedAt` | string | ISO 8601 last update timestamp |
| `moreDataAvailable` | boolean | Whether more pages exist |
| `nextCursor` | string | Pagination cursor for next page |
| `syncToken` | string | Sync token for incremental updates |
### `ashby_get_job`
@@ -239,16 +375,37 @@ Retrieves full details about a single job by its ID.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Job UUID |
| `title` | string | Job title |
| `status` | string | Job status \(Open, Closed, Draft, Archived\) |
| `employmentType` | string | Employment type \(FullTime, PartTime, Intern, Contract, Temporary\) |
| `departmentId` | string | Department UUID |
| `locationId` | string | Location UUID |
| `descriptionPlain` | string | Job description in plain text |
| `isArchived` | boolean | Whether the job is archived |
| `candidates` | json | List of candidates with rich fields \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], linkedInUrl, githubUrl, profileUrl, position, company, school, timezone, location with locationComponents\[\], tags\[\], applicationIds\[\], customFields\[\], resumeFileHandle, fileHandles\[\], source with sourceType, creditedToUser, fraudStatus, createdAt, updatedAt\) |
| `jobs` | json | List of jobs \(id, title, confidential, status, employmentType, locationId, departmentId, defaultInterviewPlanId, interviewPlanIds\[\], customFields\[\], jobPostingIds\[\], customRequisitionId, brandId, hiringTeam\[\], author, createdAt, updatedAt, openedAt, closedAt, location with address, openings\[\] with latestVersion, compensation with compensationTiers\[\]\) |
| `applications` | json | List of applications \(id, status, customFields\[\], candidate summary, currentInterviewStage, source with sourceType, archiveReason with customFields\[\], archivedAt, job summary, creditedToUser, hiringTeam\[\], appliedViaJobPostingId, submitterClientIp, submitterUserAgent, createdAt, updatedAt\) |
| `notes` | json | List of notes \(id, content, author, isPrivate, createdAt\) |
| `offers` | json | List of offers \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion with id/startDate/salary/createdAt/openingId/customFields\[\]/fileHandles\[\]/author/approvalStatus\) |
| `archiveReasons` | json | List of archive reasons \(id, text, reasonType \[RejectedByCandidate/RejectedByOrg/Other\], isArchived\) |
| `sources` | json | List of sources \(id, title, isArchived, sourceType \{id, title, isArchived\}\) |
| `customFields` | json | List of custom field definitions \(id, title, isPrivate, fieldType, objectType, isArchived, isRequired, selectableValues\[\] \{label, value, isArchived\}\) |
| `departments` | json | List of departments \(id, name, externalName, isArchived, parentId, createdAt, updatedAt\) |
| `locations` | json | List of locations \(id, name, externalName, isArchived, isRemote, workplaceType, parentLocationId, type, address with addressCountry/Region/Locality/postalCode/streetAddress\) |
| `jobPostings` | json | List of job postings \(id, title, jobId, departmentName, teamName, locationName, locationIds, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensationTierSummary, shouldDisplayCompensationOnJobBoard, updatedAt\) |
| `openings` | json | List of openings \(id, openedAt, closedAt, isArchived, archivedAt, closeReasonId, openingState, latestVersion with identifier/description/authorId/createdAt/teamId/jobIds\[\]/targetHireDate/targetStartDate/isBackfill/employmentType/locationIds\[\]/hiringTeam\[\]/customFields\[\]\) |
| `users` | json | List of users \(id, firstName, lastName, email, globalRole, isEnabled, updatedAt, managerId\) |
| `interviewSchedules` | json | List of interview schedules \(id, applicationId, interviewStageId, interviewEvents\[\] with interviewerUserIds/startTime/endTime/feedbackLink/location/meetingLink/hasSubmittedFeedback, status, scheduledBy, createdAt, updatedAt\) |
| `tags` | json | List of candidate tags \(id, title, isArchived\) |
| `id` | string | Resource UUID |
| `name` | string | Resource name |
| `title` | string | Job title or job posting title |
| `status` | string | Status |
| `candidate` | json | Candidate details \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], customFields\[\], source, creditedToUser, createdAt, updatedAt\) |
| `job` | json | Job details \(id, title, status, employmentType, locationId, departmentId, hiringTeam\[\], author, location, openings\[\], compensation, createdAt, updatedAt\) |
| `application` | json | Application details \(id, status, customFields\[\], candidate, currentInterviewStage, source, archiveReason, job, hiringTeam\[\], createdAt, updatedAt\) |
| `offer` | json | Offer details \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion\) |
| `jobPosting` | json | Job posting details \(id, title, descriptionPlain, descriptionHtml, descriptionSocial, descriptionParts, departmentName, teamName, teamNameHierarchy\[\], jobId, locationName, locationIds, linkedData, address, isRemote, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensation, updatedAt\) |
| `content` | string | Note content |
| `author` | json | Note author \(id, firstName, lastName, email, globalRole, isEnabled\) |
| `isPrivate` | boolean | Whether the note is private |
| `createdAt` | string | ISO 8601 creation timestamp |
| `updatedAt` | string | ISO 8601 last update timestamp |
| `moreDataAvailable` | boolean | Whether more pages exist |
| `nextCursor` | string | Pagination cursor for next page |
| `syncToken` | string | Sync token for incremental updates |
### `ashby_get_job_posting`
@@ -260,6 +417,8 @@ Retrieves full details about a single job posting by its ID.
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Ashby API Key |
| `jobPostingId` | string | Yes | The UUID of the job posting to fetch |
| `expandApplicationFormDefinition` | boolean | No | Include application form definition in the response |
| `expandSurveyFormDefinitions` | boolean | No | Include survey form definitions in the response |
#### Output
@@ -267,14 +426,56 @@ Retrieves full details about a single job posting by its ID.
| --------- | ---- | ----------- |
| `id` | string | Job posting UUID |
| `title` | string | Job posting title |
| `jobId` | string | Associated job UUID |
| `locationName` | string | Location name |
| `descriptionPlain` | string | Full description in plain text |
| `descriptionHtml` | string | Full description in HTML |
| `descriptionSocial` | string | Shortened description for social sharing \(max 200 chars\) |
| `descriptionParts` | object | Description broken into opening, body, and closing sections |
| ↳ `descriptionOpening` | object | Opening \(from Job Boards theme settings\) |
| ↳ `html` | string | HTML content |
| ↳ `plain` | string | Plain text content |
| ↳ `descriptionBody` | object | Main description body |
| ↳ `html` | string | HTML content |
| ↳ `plain` | string | Plain text content |
| ↳ `descriptionClosing` | object | Closing \(from Job Boards theme settings\) |
| ↳ `html` | string | HTML content |
| ↳ `plain` | string | Plain text content |
| `departmentName` | string | Department name |
| `employmentType` | string | Employment type \(e.g. FullTime, PartTime, Contract\) |
| `descriptionPlain` | string | Job posting description in plain text |
| `isListed` | boolean | Whether the posting is publicly listed |
| `teamName` | string | Team name |
| `teamNameHierarchy` | array | Hierarchy of team names from root to team |
| `jobId` | string | Associated job UUID |
| `locationName` | string | Primary location name |
| `locationIds` | object | Primary and secondary location UUIDs |
| ↳ `primaryLocationId` | string | Primary location UUID |
| ↳ `secondaryLocationIds` | array | Secondary location UUIDs |
| `address` | object | Postal address of the posting location |
| ↳ `postalAddress` | object | Structured postal address |
| ↳ `addressCountry` | string | Country |
| ↳ `addressRegion` | string | State or region |
| ↳ `addressLocality` | string | City or locality |
| ↳ `postalCode` | string | Postal code |
| ↳ `streetAddress` | string | Street address |
| `isRemote` | boolean | Whether the posting is remote |
| `workplaceType` | string | Workplace type \(OnSite, Remote, Hybrid\) |
| `employmentType` | string | Employment type \(FullTime, PartTime, Intern, Contract, Temporary\) |
| `isListed` | boolean | Whether publicly listed on the job board |
| `suppressDescriptionOpening` | boolean | Whether the theme opening is hidden on this posting |
| `suppressDescriptionClosing` | boolean | Whether the theme closing is hidden on this posting |
| `publishedDate` | string | ISO 8601 published date |
| `applicationDeadline` | string | ISO 8601 application deadline |
| `externalLink` | string | External link to the job posting |
| `applyLink` | string | Direct apply link |
| `compensation` | object | Compensation details for the posting |
| ↳ `compensationTierSummary` | string | Human-readable tier summary |
| ↳ `summaryComponents` | array | Structured compensation components |
| ↳ `summary` | string | Component summary |
| ↳ `compensationTypeLabel` | string | Component type label \(Salary, Commission, Bonus, Equity, etc.\) |
| ↳ `interval` | string | Payment interval \(e.g. annual, hourly\) |
| ↳ `currencyCode` | string | ISO 4217 currency code |
| ↳ `minValue` | number | Minimum value |
| ↳ `maxValue` | number | Maximum value |
| ↳ `shouldDisplayCompensationOnJobBoard` | boolean | Whether compensation is shown on the job board |
| `applicationLimitCalloutHtml` | string | HTML callout shown when application limit is reached |
| `updatedAt` | string | ISO 8601 last update timestamp |
### `ashby_get_offer`
@@ -291,20 +492,41 @@ Retrieves full details about a single offer by its ID.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Offer UUID |
| `offerStatus` | string | Offer status \(e.g. WaitingOnCandidateResponse, CandidateAccepted\) |
| `acceptanceStatus` | string | Acceptance status \(e.g. Accepted, Declined, Pending\) |
| `applicationId` | string | Associated application UUID |
| `startDate` | string | Offer start date |
| `salary` | object | Salary details |
| ↳ `currencyCode` | string | ISO 4217 currency code |
| ↳ `value` | number | Salary amount |
| `openingId` | string | Associated opening UUID |
| `createdAt` | string | ISO 8601 creation timestamp \(from latest version\) |
| `candidates` | json | List of candidates with rich fields \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], linkedInUrl, githubUrl, profileUrl, position, company, school, timezone, location with locationComponents\[\], tags\[\], applicationIds\[\], customFields\[\], resumeFileHandle, fileHandles\[\], source with sourceType, creditedToUser, fraudStatus, createdAt, updatedAt\) |
| `jobs` | json | List of jobs \(id, title, confidential, status, employmentType, locationId, departmentId, defaultInterviewPlanId, interviewPlanIds\[\], customFields\[\], jobPostingIds\[\], customRequisitionId, brandId, hiringTeam\[\], author, createdAt, updatedAt, openedAt, closedAt, location with address, openings\[\] with latestVersion, compensation with compensationTiers\[\]\) |
| `applications` | json | List of applications \(id, status, customFields\[\], candidate summary, currentInterviewStage, source with sourceType, archiveReason with customFields\[\], archivedAt, job summary, creditedToUser, hiringTeam\[\], appliedViaJobPostingId, submitterClientIp, submitterUserAgent, createdAt, updatedAt\) |
| `notes` | json | List of notes \(id, content, author, isPrivate, createdAt\) |
| `offers` | json | List of offers \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion with id/startDate/salary/createdAt/openingId/customFields\[\]/fileHandles\[\]/author/approvalStatus\) |
| `archiveReasons` | json | List of archive reasons \(id, text, reasonType \[RejectedByCandidate/RejectedByOrg/Other\], isArchived\) |
| `sources` | json | List of sources \(id, title, isArchived, sourceType \{id, title, isArchived\}\) |
| `customFields` | json | List of custom field definitions \(id, title, isPrivate, fieldType, objectType, isArchived, isRequired, selectableValues\[\] \{label, value, isArchived\}\) |
| `departments` | json | List of departments \(id, name, externalName, isArchived, parentId, createdAt, updatedAt\) |
| `locations` | json | List of locations \(id, name, externalName, isArchived, isRemote, workplaceType, parentLocationId, type, address with addressCountry/Region/Locality/postalCode/streetAddress\) |
| `jobPostings` | json | List of job postings \(id, title, jobId, departmentName, teamName, locationName, locationIds, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensationTierSummary, shouldDisplayCompensationOnJobBoard, updatedAt\) |
| `openings` | json | List of openings \(id, openedAt, closedAt, isArchived, archivedAt, closeReasonId, openingState, latestVersion with identifier/description/authorId/createdAt/teamId/jobIds\[\]/targetHireDate/targetStartDate/isBackfill/employmentType/locationIds\[\]/hiringTeam\[\]/customFields\[\]\) |
| `users` | json | List of users \(id, firstName, lastName, email, globalRole, isEnabled, updatedAt, managerId\) |
| `interviewSchedules` | json | List of interview schedules \(id, applicationId, interviewStageId, interviewEvents\[\] with interviewerUserIds/startTime/endTime/feedbackLink/location/meetingLink/hasSubmittedFeedback, status, scheduledBy, createdAt, updatedAt\) |
| `tags` | json | List of candidate tags \(id, title, isArchived\) |
| `id` | string | Resource UUID |
| `name` | string | Resource name |
| `title` | string | Job title or job posting title |
| `status` | string | Status |
| `candidate` | json | Candidate details \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], customFields\[\], source, creditedToUser, createdAt, updatedAt\) |
| `job` | json | Job details \(id, title, status, employmentType, locationId, departmentId, hiringTeam\[\], author, location, openings\[\], compensation, createdAt, updatedAt\) |
| `application` | json | Application details \(id, status, customFields\[\], candidate, currentInterviewStage, source, archiveReason, job, hiringTeam\[\], createdAt, updatedAt\) |
| `offer` | json | Offer details \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion\) |
| `jobPosting` | json | Job posting details \(id, title, descriptionPlain, descriptionHtml, descriptionSocial, descriptionParts, departmentName, teamName, teamNameHierarchy\[\], jobId, locationName, locationIds, linkedData, address, isRemote, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensation, updatedAt\) |
| `content` | string | Note content |
| `author` | json | Note author \(id, firstName, lastName, email, globalRole, isEnabled\) |
| `isPrivate` | boolean | Whether the note is private |
| `createdAt` | string | ISO 8601 creation timestamp |
| `moreDataAvailable` | boolean | Whether more pages exist |
| `nextCursor` | string | Pagination cursor for next page |
| `syncToken` | string | Sync token for incremental updates |
### `ashby_list_applications`
Lists all applications in an Ashby organization with pagination and optional filters for status, job, candidate, and creation date.
Lists all applications in an Ashby organization with pagination and optional filters for status, job, and creation date.
#### Input
@@ -315,7 +537,6 @@ Lists all applications in an Ashby organization with pagination and optional fil
| `perPage` | number | No | Number of results per page \(default 100\) |
| `status` | string | No | Filter by application status: Active, Hired, Archived, or Lead |
| `jobId` | string | No | Filter applications by a specific job UUID |
| `candidateId` | string | No | Filter applications by a specific candidate UUID |
| `createdAfter` | string | No | Filter to applications created after this ISO 8601 timestamp \(e.g. 2024-01-01T00:00:00Z\) |
#### Output
@@ -323,23 +544,6 @@ Lists all applications in an Ashby organization with pagination and optional fil
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `applications` | array | List of applications |
| ↳ `id` | string | Application UUID |
| ↳ `status` | string | Application status \(Active, Hired, Archived, Lead\) |
| ↳ `candidate` | object | Associated candidate |
| ↳ `id` | string | Candidate UUID |
| ↳ `name` | string | Candidate name |
| ↳ `job` | object | Associated job |
| ↳ `id` | string | Job UUID |
| ↳ `title` | string | Job title |
| ↳ `currentInterviewStage` | object | Current interview stage |
| ↳ `id` | string | Stage UUID |
| ↳ `title` | string | Stage title |
| ↳ `type` | string | Stage type |
| ↳ `source` | object | Application source |
| ↳ `id` | string | Source UUID |
| ↳ `title` | string | Source title |
| ↳ `createdAt` | string | ISO 8601 creation timestamp |
| ↳ `updatedAt` | string | ISO 8601 last update timestamp |
| `moreDataAvailable` | boolean | Whether more pages of results exist |
| `nextCursor` | string | Opaque cursor for fetching the next page |
@@ -352,6 +556,7 @@ Lists all archive reasons configured in Ashby.
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Ashby API Key |
| `includeArchived` | boolean | No | Whether to include archived archive reasons in the response \(default false\) |
#### Output
@@ -360,7 +565,7 @@ Lists all archive reasons configured in Ashby.
| `archiveReasons` | array | List of archive reasons |
| ↳ `id` | string | Archive reason UUID |
| ↳ `text` | string | Archive reason text |
| ↳ `reasonType` | string | Reason type |
| ↳ `reasonType` | string | Reason type \(RejectedByCandidate, RejectedByOrg, Other\) |
| ↳ `isArchived` | boolean | Whether the reason is archived |
### `ashby_list_candidate_tags`
@@ -372,6 +577,10 @@ Lists all candidate tags configured in Ashby.
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Ashby API Key |
| `includeArchived` | boolean | No | Whether to include archived candidate tags \(default false\) |
| `cursor` | string | No | Opaque pagination cursor from a previous response nextCursor value |
| `syncToken` | string | No | Sync token from a previous response to fetch only changed results |
| `perPage` | number | No | Number of results per page \(default 100\) |
#### Output
@@ -381,6 +590,9 @@ Lists all candidate tags configured in Ashby.
| ↳ `id` | string | Tag UUID |
| ↳ `title` | string | Tag title |
| ↳ `isArchived` | boolean | Whether the tag is archived |
| `moreDataAvailable` | boolean | Whether more pages of results exist |
| `nextCursor` | string | Opaque cursor for fetching the next page |
| `syncToken` | string | Sync token to use for incremental updates in future requests |
### `ashby_list_candidates`
@@ -399,18 +611,6 @@ Lists all candidates in an Ashby organization with cursor-based pagination.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `candidates` | array | List of candidates |
| ↳ `id` | string | Candidate UUID |
| ↳ `name` | string | Full name |
| ↳ `primaryEmailAddress` | object | Primary email contact info |
| ↳ `value` | string | Email address |
| ↳ `type` | string | Contact type \(Personal, Work, Other\) |
| ↳ `isPrimary` | boolean | Whether this is the primary email |
| ↳ `primaryPhoneNumber` | object | Primary phone contact info |
| ↳ `value` | string | Phone number |
| ↳ `type` | string | Contact type \(Personal, Work, Other\) |
| ↳ `isPrimary` | boolean | Whether this is the primary phone |
| ↳ `createdAt` | string | ISO 8601 creation timestamp |
| ↳ `updatedAt` | string | ISO 8601 last update timestamp |
| `moreDataAvailable` | boolean | Whether more pages of results exist |
| `nextCursor` | string | Opaque cursor for fetching the next page |
@@ -431,9 +631,15 @@ Lists all custom field definitions configured in Ashby.
| `customFields` | array | List of custom field definitions |
| ↳ `id` | string | Custom field UUID |
| ↳ `title` | string | Custom field title |
| ↳ `fieldType` | string | Field type \(e.g. String, Number, Boolean\) |
| ↳ `objectType` | string | Object type the field applies to \(e.g. Candidate, Application, Job\) |
| ↳ `isPrivate` | boolean | Whether the custom field is private |
| ↳ `fieldType` | string | Field data type \(MultiValueSelect, NumberRange, String, Date, ValueSelect, Number, Currency, Boolean, LongText, CompensationRange\) |
| ↳ `objectType` | string | Object type the field applies to \(Application, Candidate, Employee, Job, Offer, Opening, Talent_Project\) |
| ↳ `isArchived` | boolean | Whether the custom field is archived |
| ↳ `isRequired` | boolean | Whether a value is required |
| ↳ `selectableValues` | array | Selectable values for MultiValueSelect fields \(empty for other field types\) |
| ↳ `label` | string | Display label |
| ↳ `value` | string | Stored value |
| ↳ `isArchived` | boolean | Whether archived |
### `ashby_list_departments`
@@ -452,8 +658,11 @@ Lists all departments in Ashby.
| `departments` | array | List of departments |
| ↳ `id` | string | Department UUID |
| ↳ `name` | string | Department name |
| ↳ `externalName` | string | Candidate-facing name used on job boards |
| ↳ `isArchived` | boolean | Whether the department is archived |
| ↳ `parentId` | string | Parent department UUID |
| ↳ `createdAt` | string | ISO 8601 creation timestamp |
| ↳ `updatedAt` | string | ISO 8601 last update timestamp |
### `ashby_list_interviews`
@@ -475,10 +684,24 @@ Lists interview schedules in Ashby, optionally filtered by application or interv
| --------- | ---- | ----------- |
| `interviewSchedules` | array | List of interview schedules |
| ↳ `id` | string | Interview schedule UUID |
| ↳ `status` | string | Schedule status \(NeedsScheduling, WaitingOnCandidateBooking, Scheduled, Complete, Cancelled, OnHold, etc.\) |
| ↳ `applicationId` | string | Associated application UUID |
| ↳ `interviewStageId` | string | Interview stage UUID |
| ↳ `status` | string | Schedule status |
| ↳ `createdAt` | string | ISO 8601 creation timestamp |
| ↳ `updatedAt` | string | ISO 8601 last update timestamp |
| ↳ `interviewEvents` | array | Scheduled interview events on this schedule |
| ↳ `id` | string | Event UUID |
| ↳ `interviewId` | string | Interview template UUID |
| ↳ `interviewScheduleId` | string | Parent schedule UUID |
| ↳ `interviewerUserIds` | array | User UUIDs of interviewers assigned to the event |
| ↳ `createdAt` | string | Event creation timestamp |
| ↳ `updatedAt` | string | Event last updated timestamp |
| ↳ `startTime` | string | Event start time |
| ↳ `endTime` | string | Event end time |
| ↳ `feedbackLink` | string | URL to submit feedback for the event |
| ↳ `location` | string | Physical location |
| ↳ `meetingLink` | string | Virtual meeting URL |
| ↳ `hasSubmittedFeedback` | boolean | Whether any feedback has been submitted |
| `moreDataAvailable` | boolean | Whether more pages of results exist |
| `nextCursor` | string | Opaque cursor for fetching the next page |
@@ -500,11 +723,22 @@ Lists all job postings in Ashby.
| ↳ `id` | string | Job posting UUID |
| ↳ `title` | string | Job posting title |
| ↳ `jobId` | string | Associated job UUID |
| ↳ `locationName` | string | Location name |
| ↳ `departmentName` | string | Department name |
| ↳ `employmentType` | string | Employment type \(e.g. FullTime, PartTime, Contract\) |
| ↳ `teamName` | string | Team name |
| ↳ `locationName` | string | Primary location display name |
| ↳ `locationIds` | object | Primary and secondary location UUIDs |
| ↳ `primaryLocationId` | string | Primary location UUID |
| ↳ `secondaryLocationIds` | array | Secondary location UUIDs |
| ↳ `workplaceType` | string | Workplace type \(OnSite, Remote, Hybrid\) |
| ↳ `employmentType` | string | Employment type \(FullTime, PartTime, Intern, Contract, Temporary\) |
| ↳ `isListed` | boolean | Whether the posting is publicly listed |
| ↳ `publishedDate` | string | ISO 8601 published date |
| ↳ `applicationDeadline` | string | ISO 8601 application deadline |
| ↳ `externalLink` | string | External link to the job posting |
| ↳ `applyLink` | string | Direct apply link for the job posting |
| ↳ `compensationTierSummary` | string | Compensation tier summary for job boards |
| ↳ `shouldDisplayCompensationOnJobBoard` | boolean | Whether compensation is shown on the job board |
| ↳ `updatedAt` | string | ISO 8601 last update timestamp |
### `ashby_list_jobs`
@@ -524,14 +758,6 @@ Lists all jobs in an Ashby organization. By default returns Open, Closed, and Ar
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `jobs` | array | List of jobs |
| ↳ `id` | string | Job UUID |
| ↳ `title` | string | Job title |
| ↳ `status` | string | Job status \(Open, Closed, Archived, Draft\) |
| ↳ `employmentType` | string | Employment type \(FullTime, PartTime, Intern, Contract, Temporary\) |
| ↳ `departmentId` | string | Department UUID |
| ↳ `locationId` | string | Location UUID |
| ↳ `createdAt` | string | ISO 8601 creation timestamp |
| ↳ `updatedAt` | string | ISO 8601 last update timestamp |
| `moreDataAvailable` | boolean | Whether more pages of results exist |
| `nextCursor` | string | Opaque cursor for fetching the next page |
@@ -552,12 +778,18 @@ Lists all locations configured in Ashby.
| `locations` | array | List of locations |
| ↳ `id` | string | Location UUID |
| ↳ `name` | string | Location name |
| ↳ `externalName` | string | Candidate-facing name used on job boards |
| ↳ `isArchived` | boolean | Whether the location is archived |
| ↳ `isRemote` | boolean | Whether this is a remote location |
| ↳ `address` | object | Location address |
| ↳ `city` | string | City |
| ↳ `region` | string | State or region |
| ↳ `country` | string | Country |
| ↳ `isRemote` | boolean | Whether the location is remote \(use workplaceType instead\) |
| ↳ `workplaceType` | string | Workplace type \(OnSite, Hybrid, Remote\) |
| ↳ `parentLocationId` | string | Parent location UUID |
| ↳ `type` | string | Location component type \(Location, LocationHierarchy\) |
| ↳ `address` | object | Location postal address |
| ↳ `addressCountry` | string | Country |
| ↳ `addressRegion` | string | State or region |
| ↳ `addressLocality` | string | City or locality |
| ↳ `postalCode` | string | Postal code |
| ↳ `streetAddress` | string | Street address |
### `ashby_list_notes`
@@ -579,6 +811,7 @@ Lists all notes on a candidate with pagination support.
| `notes` | array | List of notes on the candidate |
| ↳ `id` | string | Note UUID |
| ↳ `content` | string | Note content |
| ↳ `isPrivate` | boolean | Whether the note is private |
| ↳ `author` | object | Note author |
| ↳ `id` | string | Author user UUID |
| ↳ `firstName` | string | First name |
@@ -605,16 +838,6 @@ Lists all offers with their latest version in an Ashby organization.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `offers` | array | List of offers |
| ↳ `id` | string | Offer UUID |
| ↳ `offerStatus` | string | Offer status |
| ↳ `acceptanceStatus` | string | Acceptance status |
| ↳ `applicationId` | string | Associated application UUID |
| ↳ `startDate` | string | Offer start date |
| ↳ `salary` | object | Salary details |
| ↳ `currencyCode` | string | ISO 4217 currency code |
| ↳ `value` | number | Salary amount |
| ↳ `openingId` | string | Associated opening UUID |
| ↳ `createdAt` | string | ISO 8601 creation timestamp |
| `moreDataAvailable` | boolean | Whether more pages of results exist |
| `nextCursor` | string | Opaque cursor for fetching the next page |
@@ -634,12 +857,6 @@ Lists all openings in Ashby with pagination.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `openings` | array | List of openings |
| ↳ `id` | string | Opening UUID |
| ↳ `openingState` | string | Opening state \(Approved, Closed, Draft, Filled, Open\) |
| ↳ `isArchived` | boolean | Whether the opening is archived |
| ↳ `openedAt` | string | ISO 8601 opened timestamp |
| ↳ `closedAt` | string | ISO 8601 closed timestamp |
| `moreDataAvailable` | boolean | Whether more pages of results exist |
| `nextCursor` | string | Opaque cursor for fetching the next page |
@@ -661,6 +878,10 @@ Lists all candidate sources configured in Ashby.
| ↳ `id` | string | Source UUID |
| ↳ `title` | string | Source title |
| ↳ `isArchived` | boolean | Whether the source is archived |
| ↳ `sourceType` | object | Source type grouping |
| ↳ `id` | string | Source type UUID |
| ↳ `title` | string | Source type title |
| ↳ `isArchived` | boolean | Whether archived |
### `ashby_list_users`
@@ -679,18 +900,12 @@ Lists all users in Ashby with pagination.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `users` | array | List of users |
| ↳ `id` | string | User UUID |
| ↳ `firstName` | string | First name |
| ↳ `lastName` | string | Last name |
| ↳ `email` | string | Email address |
| ↳ `isEnabled` | boolean | Whether the user account is enabled |
| ↳ `globalRole` | string | User role \(Organization Admin, Elevated Access, Limited Access, External Recruiter\) |
| `moreDataAvailable` | boolean | Whether more pages of results exist |
| `nextCursor` | string | Opaque cursor for fetching the next page |
### `ashby_remove_candidate_tag`
Removes a tag from a candidate in Ashby.
Removes a tag from a candidate in Ashby and returns the updated candidate.
#### Input
@@ -704,7 +919,37 @@ Removes a tag from a candidate in Ashby.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the tag was successfully removed |
| `candidates` | json | List of candidates with rich fields \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], linkedInUrl, githubUrl, profileUrl, position, company, school, timezone, location with locationComponents\[\], tags\[\], applicationIds\[\], customFields\[\], resumeFileHandle, fileHandles\[\], source with sourceType, creditedToUser, fraudStatus, createdAt, updatedAt\) |
| `jobs` | json | List of jobs \(id, title, confidential, status, employmentType, locationId, departmentId, defaultInterviewPlanId, interviewPlanIds\[\], customFields\[\], jobPostingIds\[\], customRequisitionId, brandId, hiringTeam\[\], author, createdAt, updatedAt, openedAt, closedAt, location with address, openings\[\] with latestVersion, compensation with compensationTiers\[\]\) |
| `applications` | json | List of applications \(id, status, customFields\[\], candidate summary, currentInterviewStage, source with sourceType, archiveReason with customFields\[\], archivedAt, job summary, creditedToUser, hiringTeam\[\], appliedViaJobPostingId, submitterClientIp, submitterUserAgent, createdAt, updatedAt\) |
| `notes` | json | List of notes \(id, content, author, isPrivate, createdAt\) |
| `offers` | json | List of offers \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion with id/startDate/salary/createdAt/openingId/customFields\[\]/fileHandles\[\]/author/approvalStatus\) |
| `archiveReasons` | json | List of archive reasons \(id, text, reasonType \[RejectedByCandidate/RejectedByOrg/Other\], isArchived\) |
| `sources` | json | List of sources \(id, title, isArchived, sourceType \{id, title, isArchived\}\) |
| `customFields` | json | List of custom field definitions \(id, title, isPrivate, fieldType, objectType, isArchived, isRequired, selectableValues\[\] \{label, value, isArchived\}\) |
| `departments` | json | List of departments \(id, name, externalName, isArchived, parentId, createdAt, updatedAt\) |
| `locations` | json | List of locations \(id, name, externalName, isArchived, isRemote, workplaceType, parentLocationId, type, address with addressCountry/Region/Locality/postalCode/streetAddress\) |
| `jobPostings` | json | List of job postings \(id, title, jobId, departmentName, teamName, locationName, locationIds, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensationTierSummary, shouldDisplayCompensationOnJobBoard, updatedAt\) |
| `openings` | json | List of openings \(id, openedAt, closedAt, isArchived, archivedAt, closeReasonId, openingState, latestVersion with identifier/description/authorId/createdAt/teamId/jobIds\[\]/targetHireDate/targetStartDate/isBackfill/employmentType/locationIds\[\]/hiringTeam\[\]/customFields\[\]\) |
| `users` | json | List of users \(id, firstName, lastName, email, globalRole, isEnabled, updatedAt, managerId\) |
| `interviewSchedules` | json | List of interview schedules \(id, applicationId, interviewStageId, interviewEvents\[\] with interviewerUserIds/startTime/endTime/feedbackLink/location/meetingLink/hasSubmittedFeedback, status, scheduledBy, createdAt, updatedAt\) |
| `tags` | json | List of candidate tags \(id, title, isArchived\) |
| `id` | string | Resource UUID |
| `name` | string | Resource name |
| `title` | string | Job title or job posting title |
| `status` | string | Status |
| `candidate` | json | Candidate details \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], customFields\[\], source, creditedToUser, createdAt, updatedAt\) |
| `job` | json | Job details \(id, title, status, employmentType, locationId, departmentId, hiringTeam\[\], author, location, openings\[\], compensation, createdAt, updatedAt\) |
| `application` | json | Application details \(id, status, customFields\[\], candidate, currentInterviewStage, source, archiveReason, job, hiringTeam\[\], createdAt, updatedAt\) |
| `offer` | json | Offer details \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion\) |
| `jobPosting` | json | Job posting details \(id, title, descriptionPlain, descriptionHtml, descriptionSocial, descriptionParts, departmentName, teamName, teamNameHierarchy\[\], jobId, locationName, locationIds, linkedData, address, isRemote, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensation, updatedAt\) |
| `content` | string | Note content |
| `author` | json | Note author \(id, firstName, lastName, email, globalRole, isEnabled\) |
| `isPrivate` | boolean | Whether the note is private |
| `createdAt` | string | ISO 8601 creation timestamp |
| `moreDataAvailable` | boolean | Whether more pages exist |
| `nextCursor` | string | Pagination cursor for next page |
| `syncToken` | string | Sync token for incremental updates |
### `ashby_search_candidates`
@@ -723,18 +968,6 @@ Searches for candidates by name and/or email with AND logic. Results are limited
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `candidates` | array | Matching candidates \(max 100 results\) |
| ↳ `id` | string | Candidate UUID |
| ↳ `name` | string | Full name |
| ↳ `primaryEmailAddress` | object | Primary email contact info |
| ↳ `value` | string | Email address |
| ↳ `type` | string | Contact type \(Personal, Work, Other\) |
| ↳ `isPrimary` | boolean | Whether this is the primary email |
| ↳ `primaryPhoneNumber` | object | Primary phone contact info |
| ↳ `value` | string | Phone number |
| ↳ `type` | string | Contact type \(Personal, Work, Other\) |
| ↳ `isPrimary` | boolean | Whether this is the primary phone |
| ↳ `createdAt` | string | ISO 8601 creation timestamp |
| ↳ `updatedAt` | string | ISO 8601 last update timestamp |
### `ashby_update_candidate`
@@ -758,26 +991,36 @@ Updates an existing candidate record in Ashby. Only provided fields are changed.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Candidate UUID |
| `name` | string | Full name |
| `primaryEmailAddress` | object | Primary email contact info |
| ↳ `value` | string | Email address |
| ↳ `type` | string | Contact type \(Personal, Work, Other\) |
| ↳ `isPrimary` | boolean | Whether this is the primary email |
| `primaryPhoneNumber` | object | Primary phone contact info |
| ↳ `value` | string | Phone number |
| ↳ `type` | string | Contact type \(Personal, Work, Other\) |
| ↳ `isPrimary` | boolean | Whether this is the primary phone |
| `profileUrl` | string | URL to the candidate Ashby profile |
| `position` | string | Current position or title |
| `company` | string | Current company |
| `linkedInUrl` | string | LinkedIn profile URL |
| `githubUrl` | string | GitHub profile URL |
| `tags` | array | Tags applied to the candidate |
| ↳ `id` | string | Tag UUID |
| ↳ `title` | string | Tag title |
| `applicationIds` | array | IDs of associated applications |
| `candidates` | json | List of candidates with rich fields \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], linkedInUrl, githubUrl, profileUrl, position, company, school, timezone, location with locationComponents\[\], tags\[\], applicationIds\[\], customFields\[\], resumeFileHandle, fileHandles\[\], source with sourceType, creditedToUser, fraudStatus, createdAt, updatedAt\) |
| `jobs` | json | List of jobs \(id, title, confidential, status, employmentType, locationId, departmentId, defaultInterviewPlanId, interviewPlanIds\[\], customFields\[\], jobPostingIds\[\], customRequisitionId, brandId, hiringTeam\[\], author, createdAt, updatedAt, openedAt, closedAt, location with address, openings\[\] with latestVersion, compensation with compensationTiers\[\]\) |
| `applications` | json | List of applications \(id, status, customFields\[\], candidate summary, currentInterviewStage, source with sourceType, archiveReason with customFields\[\], archivedAt, job summary, creditedToUser, hiringTeam\[\], appliedViaJobPostingId, submitterClientIp, submitterUserAgent, createdAt, updatedAt\) |
| `notes` | json | List of notes \(id, content, author, isPrivate, createdAt\) |
| `offers` | json | List of offers \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion with id/startDate/salary/createdAt/openingId/customFields\[\]/fileHandles\[\]/author/approvalStatus\) |
| `archiveReasons` | json | List of archive reasons \(id, text, reasonType \[RejectedByCandidate/RejectedByOrg/Other\], isArchived\) |
| `sources` | json | List of sources \(id, title, isArchived, sourceType \{id, title, isArchived\}\) |
| `customFields` | json | List of custom field definitions \(id, title, isPrivate, fieldType, objectType, isArchived, isRequired, selectableValues\[\] \{label, value, isArchived\}\) |
| `departments` | json | List of departments \(id, name, externalName, isArchived, parentId, createdAt, updatedAt\) |
| `locations` | json | List of locations \(id, name, externalName, isArchived, isRemote, workplaceType, parentLocationId, type, address with addressCountry/Region/Locality/postalCode/streetAddress\) |
| `jobPostings` | json | List of job postings \(id, title, jobId, departmentName, teamName, locationName, locationIds, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensationTierSummary, shouldDisplayCompensationOnJobBoard, updatedAt\) |
| `openings` | json | List of openings \(id, openedAt, closedAt, isArchived, archivedAt, closeReasonId, openingState, latestVersion with identifier/description/authorId/createdAt/teamId/jobIds\[\]/targetHireDate/targetStartDate/isBackfill/employmentType/locationIds\[\]/hiringTeam\[\]/customFields\[\]\) |
| `users` | json | List of users \(id, firstName, lastName, email, globalRole, isEnabled, updatedAt, managerId\) |
| `interviewSchedules` | json | List of interview schedules \(id, applicationId, interviewStageId, interviewEvents\[\] with interviewerUserIds/startTime/endTime/feedbackLink/location/meetingLink/hasSubmittedFeedback, status, scheduledBy, createdAt, updatedAt\) |
| `tags` | json | List of candidate tags \(id, title, isArchived\) |
| `id` | string | Resource UUID |
| `name` | string | Resource name |
| `title` | string | Job title or job posting title |
| `status` | string | Status |
| `candidate` | json | Candidate details \(id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses\[\], phoneNumbers\[\], socialLinks\[\], customFields\[\], source, creditedToUser, createdAt, updatedAt\) |
| `job` | json | Job details \(id, title, status, employmentType, locationId, departmentId, hiringTeam\[\], author, location, openings\[\], compensation, createdAt, updatedAt\) |
| `application` | json | Application details \(id, status, customFields\[\], candidate, currentInterviewStage, source, archiveReason, job, hiringTeam\[\], createdAt, updatedAt\) |
| `offer` | json | Offer details \(id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion\) |
| `jobPosting` | json | Job posting details \(id, title, descriptionPlain, descriptionHtml, descriptionSocial, descriptionParts, departmentName, teamName, teamNameHierarchy\[\], jobId, locationName, locationIds, linkedData, address, isRemote, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensation, updatedAt\) |
| `content` | string | Note content |
| `author` | json | Note author \(id, firstName, lastName, email, globalRole, isEnabled\) |
| `isPrivate` | boolean | Whether the note is private |
| `createdAt` | string | ISO 8601 creation timestamp |
| `updatedAt` | string | ISO 8601 last update timestamp |
| `moreDataAvailable` | boolean | Whether more pages exist |
| `nextCursor` | string | Pagination cursor for next page |
| `syncToken` | string | Sync token for incremental updates |
@@ -97,6 +97,14 @@ Trigger workflow when a candidate is hired
| ↳ `job` | object | job output from the tool |
| ↳ `id` | string | Job UUID |
| ↳ `title` | string | Job title |
| `offer` | object | offer output from the tool |
| ↳ `id` | string | Accepted offer UUID |
| ↳ `applicationId` | string | Associated application UUID |
| ↳ `acceptanceStatus` | string | Offer acceptance status |
| ↳ `offerStatus` | string | Offer process status |
| ↳ `decidedAt` | string | Offer decision timestamp \(ISO 8601\) |
| ↳ `latestVersion` | object | latestVersion output from the tool |
| ↳ `id` | string | Latest offer version UUID |
---
@@ -1031,7 +1031,7 @@
},
{
"name": "List Applications",
"description": "Lists all applications in an Ashby organization with pagination and optional filters for status, job, candidate, and creation date."
"description": "Lists all applications in an Ashby organization with pagination and optional filters for status, job, and creation date."
},
{
"name": "Get Application",
@@ -1051,11 +1051,11 @@
},
{
"name": "Add Candidate Tag",
"description": "Adds a tag to a candidate in Ashby."
"description": "Adds a tag to a candidate in Ashby and returns the updated candidate."
},
{
"name": "Remove Candidate Tag",
"description": "Removes a tag from a candidate in Ashby."
"description": "Removes a tag from a candidate in Ashby and returns the updated candidate."
},
{
"name": "Get Offer",
+112 -44
View File
@@ -113,7 +113,6 @@ export const AshbyBlock: BlockConfig = {
id: 'email',
title: 'Email',
type: 'short-input',
required: { field: 'operation', value: 'create_candidate' },
placeholder: 'Email address',
condition: { field: 'operation', value: ['create_candidate', 'update_candidate'] },
},
@@ -308,14 +307,6 @@ Output only the ISO 8601 timestamp string, nothing else.`,
condition: { field: 'operation', value: 'list_applications' },
mode: 'advanced',
},
{
id: 'filterCandidateId',
title: 'Candidate ID Filter',
type: 'short-input',
placeholder: 'Filter by candidate UUID',
condition: { field: 'operation', value: 'list_applications' },
mode: 'advanced',
},
{
id: 'createdAfter',
title: 'Created After',
@@ -366,6 +357,7 @@ Output only the ISO 8601 timestamp string, nothing else.`,
'list_openings',
'list_users',
'list_interviews',
'list_candidate_tags',
],
},
mode: 'advanced',
@@ -386,10 +378,43 @@ Output only the ISO 8601 timestamp string, nothing else.`,
'list_openings',
'list_users',
'list_interviews',
'list_candidate_tags',
],
},
mode: 'advanced',
},
{
id: 'syncToken',
title: 'Sync Token',
type: 'short-input',
placeholder: 'Sync token for incremental updates',
condition: { field: 'operation', value: 'list_candidate_tags' },
mode: 'advanced',
},
{
id: 'includeArchived',
title: 'Include Archived',
type: 'switch',
condition: {
field: 'operation',
value: ['list_candidate_tags', 'list_archive_reasons'],
},
mode: 'advanced',
},
{
id: 'expandApplicationFormDefinition',
title: 'Include Application Form Definition',
type: 'switch',
condition: { field: 'operation', value: 'get_job_posting' },
mode: 'advanced',
},
{
id: 'expandSurveyFormDefinitions',
title: 'Include Survey Form Definitions',
type: 'switch',
condition: { field: 'operation', value: 'get_job_posting' },
mode: 'advanced',
},
{
id: 'tagId',
title: 'Tag ID',
@@ -476,11 +501,25 @@ Output only the ISO 8601 timestamp string, nothing else.`,
if (params.searchEmail) result.email = params.searchEmail
if (params.filterStatus) result.status = params.filterStatus
if (params.filterJobId) result.jobId = params.filterJobId
if (params.filterCandidateId) result.candidateId = params.filterCandidateId
if (params.jobStatus) result.status = params.jobStatus
if (params.sendNotifications === 'true' || params.sendNotifications === true) {
result.sendNotifications = true
}
if (params.includeArchived === 'true' || params.includeArchived === true) {
result.includeArchived = true
}
if (
params.expandApplicationFormDefinition === 'true' ||
params.expandApplicationFormDefinition === true
) {
result.expandApplicationFormDefinition = true
}
if (
params.expandSurveyFormDefinitions === 'true' ||
params.expandSurveyFormDefinitions === true
) {
result.expandSurveyFormDefinitions = true
}
if (params.appCandidateId) result.candidateId = params.appCandidateId
if (params.appCreatedAt) result.createdAt = params.appCreatedAt
if (params.updateName) result.name = params.updateName
@@ -515,11 +554,20 @@ Output only the ISO 8601 timestamp string, nothing else.`,
sendNotifications: { type: 'boolean', description: 'Send notifications' },
filterStatus: { type: 'string', description: 'Application status filter' },
filterJobId: { type: 'string', description: 'Job UUID filter' },
filterCandidateId: { type: 'string', description: 'Candidate UUID filter' },
createdAfter: { type: 'string', description: 'Filter by creation date' },
jobStatus: { type: 'string', description: 'Job status filter' },
cursor: { type: 'string', description: 'Pagination cursor' },
perPage: { type: 'number', description: 'Results per page' },
syncToken: { type: 'string', description: 'Sync token for incremental updates' },
includeArchived: { type: 'boolean', description: 'Include archived records' },
expandApplicationFormDefinition: {
type: 'boolean',
description: 'Include application form definition in job posting',
},
expandSurveyFormDefinitions: {
type: 'boolean',
description: 'Include survey form definitions in job posting',
},
tagId: { type: 'string', description: 'Tag UUID' },
offerId: { type: 'string', description: 'Offer UUID' },
jobPostingId: { type: 'string', description: 'Job posting UUID' },
@@ -530,93 +578,113 @@ Output only the ISO 8601 timestamp string, nothing else.`,
candidates: {
type: 'json',
description:
'List of candidates (id, name, primaryEmailAddress, primaryPhoneNumber, createdAt, updatedAt)',
'List of candidates with rich fields (id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses[], phoneNumbers[], socialLinks[], linkedInUrl, githubUrl, profileUrl, position, company, school, timezone, location with locationComponents[], tags[], applicationIds[], customFields[], resumeFileHandle, fileHandles[], source with sourceType, creditedToUser, fraudStatus, createdAt, updatedAt)',
},
jobs: {
type: 'json',
description:
'List of jobs (id, title, status, employmentType, departmentId, locationId, createdAt, updatedAt)',
'List of jobs (id, title, confidential, status, employmentType, locationId, departmentId, defaultInterviewPlanId, interviewPlanIds[], customFields[], jobPostingIds[], customRequisitionId, brandId, hiringTeam[], author, createdAt, updatedAt, openedAt, closedAt, location with address, openings[] with latestVersion, compensation with compensationTiers[])',
},
applications: {
type: 'json',
description:
'List of applications (id, status, candidate, job, currentInterviewStage, source, createdAt, updatedAt)',
'List of applications (id, status, customFields[], candidate summary, currentInterviewStage, source with sourceType, archiveReason with customFields[], archivedAt, job summary, creditedToUser, hiringTeam[], appliedViaJobPostingId, submitterClientIp, submitterUserAgent, createdAt, updatedAt)',
},
notes: {
type: 'json',
description: 'List of notes (id, content, author, createdAt)',
description: 'List of notes (id, content, author, isPrivate, createdAt)',
},
offers: {
type: 'json',
description:
'List of offers (id, offerStatus, acceptanceStatus, applicationId, startDate, salary, openingId)',
'List of offers (id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion with id/startDate/salary/createdAt/openingId/customFields[]/fileHandles[]/author/approvalStatus)',
},
archiveReasons: {
type: 'json',
description: 'List of archive reasons (id, text, reasonType, isArchived)',
description:
'List of archive reasons (id, text, reasonType [RejectedByCandidate/RejectedByOrg/Other], isArchived)',
},
sources: {
type: 'json',
description: 'List of sources (id, title, isArchived)',
description: 'List of sources (id, title, isArchived, sourceType {id, title, isArchived})',
},
customFields: {
type: 'json',
description: 'List of custom fields (id, title, fieldType, objectType, isArchived)',
description:
'List of custom field definitions (id, title, isPrivate, fieldType, objectType, isArchived, isRequired, selectableValues[] {label, value, isArchived})',
},
departments: {
type: 'json',
description: 'List of departments (id, name, isArchived, parentId)',
description:
'List of departments (id, name, externalName, isArchived, parentId, createdAt, updatedAt)',
},
locations: {
type: 'json',
description: 'List of locations (id, name, isArchived, isRemote, address)',
description:
'List of locations (id, name, externalName, isArchived, isRemote, workplaceType, parentLocationId, type, address with addressCountry/Region/Locality/postalCode/streetAddress)',
},
jobPostings: {
type: 'json',
description:
'List of job postings (id, title, jobId, locationName, departmentName, employmentType, isListed, publishedDate)',
'List of job postings (id, title, jobId, departmentName, teamName, locationName, locationIds, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensationTierSummary, shouldDisplayCompensationOnJobBoard, updatedAt)',
},
openings: {
type: 'json',
description: 'List of openings (id, openingState, isArchived, openedAt, closedAt)',
description:
'List of openings (id, openedAt, closedAt, isArchived, archivedAt, closeReasonId, openingState, latestVersion with identifier/description/authorId/createdAt/teamId/jobIds[]/targetHireDate/targetStartDate/isBackfill/employmentType/locationIds[]/hiringTeam[]/customFields[])',
},
users: {
type: 'json',
description: 'List of users (id, firstName, lastName, email, isEnabled, globalRole)',
description:
'List of users (id, firstName, lastName, email, globalRole, isEnabled, updatedAt, managerId)',
},
interviewSchedules: {
type: 'json',
description:
'List of interview schedules (id, applicationId, interviewStageId, status, createdAt)',
'List of interview schedules (id, applicationId, interviewStageId, interviewEvents[] with interviewerUserIds/startTime/endTime/feedbackLink/location/meetingLink/hasSubmittedFeedback, status, scheduledBy, createdAt, updatedAt)',
},
tags: {
type: 'json',
description: 'List of candidate tags (id, title, isArchived)',
},
stageId: { type: 'string', description: 'Interview stage UUID after stage change' },
success: { type: 'boolean', description: 'Whether the operation succeeded' },
offerStatus: {
type: 'string',
description: 'Offer status (e.g. WaitingOnCandidateResponse, CandidateAccepted)',
},
acceptanceStatus: {
type: 'string',
description: 'Acceptance status (e.g. Accepted, Declined, Pending)',
},
applicationId: { type: 'string', description: 'Associated application UUID' },
openingId: { type: 'string', description: 'Opening UUID associated with the offer' },
salary: {
type: 'json',
description: 'Salary details from latest version (currencyCode, value)',
},
startDate: { type: 'string', description: 'Offer start date from latest version' },
id: { type: 'string', description: 'Resource UUID' },
name: { type: 'string', description: 'Resource name' },
title: { type: 'string', description: 'Job title' },
title: { type: 'string', description: 'Job title or job posting title' },
status: { type: 'string', description: 'Status' },
noteId: { type: 'string', description: 'Created note UUID' },
candidate: {
type: 'json',
description:
'Candidate details (id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses[], phoneNumbers[], socialLinks[], customFields[], source, creditedToUser, createdAt, updatedAt)',
},
job: {
type: 'json',
description:
'Job details (id, title, status, employmentType, locationId, departmentId, hiringTeam[], author, location, openings[], compensation, createdAt, updatedAt)',
},
application: {
type: 'json',
description:
'Application details (id, status, customFields[], candidate, currentInterviewStage, source, archiveReason, job, hiringTeam[], createdAt, updatedAt)',
},
offer: {
type: 'json',
description:
'Offer details (id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion)',
},
jobPosting: {
type: 'json',
description:
'Job posting details (id, title, descriptionPlain, descriptionHtml, descriptionSocial, descriptionParts, departmentName, teamName, teamNameHierarchy[], jobId, locationName, locationIds, linkedData, address, isRemote, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensation, updatedAt)',
},
content: { type: 'string', description: 'Note content' },
author: {
type: 'json',
description: 'Note author (id, firstName, lastName, email, globalRole, isEnabled)',
},
isPrivate: { type: 'boolean', description: 'Whether the note is private' },
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
moreDataAvailable: { type: 'boolean', description: 'Whether more pages exist' },
nextCursor: { type: 'string', description: 'Pagination cursor for next page' },
syncToken: { type: 'string', description: 'Sync token for incremental updates' },
},
}
+72 -19
View File
@@ -2,16 +2,18 @@ import { createLogger } from '@sim/logger'
import { safeCompare } from '@sim/security/compare'
import { hmacSha256Hex } from '@sim/security/hmac'
import { generateId } from '@sim/utils/id'
import { NextResponse } from 'next/server'
import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils'
import type {
AuthContext,
DeleteSubscriptionContext,
EventMatchContext,
FormatInputContext,
FormatInputResult,
SubscriptionContext,
SubscriptionResult,
WebhookProviderHandler,
} from '@/lib/webhooks/providers/types'
import { createHmacVerifier } from '@/lib/webhooks/providers/utils'
const logger = createLogger('WebhookProvider:Ashby')
@@ -48,17 +50,74 @@ export const ashbyHandler: WebhookProviderHandler = {
input: {
...((b.data as Record<string, unknown>) || {}),
action: b.action,
data: b.data || {},
},
}
},
verifyAuth: createHmacVerifier({
configKey: 'secretToken',
headerName: 'ashby-signature',
validateFn: validateAshbySignature,
providerLabel: 'Ashby',
}),
verifyAuth({ request, rawBody, requestId, providerConfig }: AuthContext): NextResponse | null {
const secretToken = (providerConfig.secretToken as string | undefined)?.trim()
if (!secretToken) {
logger.warn(
`[${requestId}] Ashby webhook missing secretToken in providerConfig — rejecting request`
)
return new NextResponse(
'Unauthorized - Ashby webhook signing secret is not configured. Re-save the trigger so a webhook can be registered.',
{ status: 401 }
)
}
const signature = request.headers.get('ashby-signature')
if (!signature) {
logger.warn(`[${requestId}] Ashby webhook missing signature header`)
return new NextResponse('Unauthorized - Missing Ashby signature', { status: 401 })
}
if (!validateAshbySignature(secretToken, signature, rawBody)) {
logger.warn(`[${requestId}] Ashby signature verification failed`, {
signatureLength: signature.length,
secretLength: secretToken.length,
})
return new NextResponse('Unauthorized - Invalid Ashby signature', { status: 401 })
}
return null
},
async matchEvent({
webhook,
body,
requestId,
providerConfig,
}: EventMatchContext): Promise<boolean> {
const triggerId = providerConfig.triggerId as string | undefined
const obj = body as Record<string, unknown>
const action = typeof obj?.action === 'string' ? obj.action : ''
if (action === 'ping') {
logger.debug(`[${requestId}] Ashby ping event received. Skipping execution.`, {
webhookId: webhook.id,
triggerId,
})
return false
}
if (!triggerId) return true
const { isAshbyEventMatch } = await import('@/triggers/ashby/utils')
if (!isAshbyEventMatch(triggerId, action)) {
logger.debug(
`[${requestId}] Ashby event mismatch for trigger ${triggerId}. Action: ${action || '(missing)'}. Skipping execution.`,
{
webhookId: webhook.id,
triggerId,
receivedAction: action,
}
)
return false
}
return true
},
async createSubscription(ctx: SubscriptionContext): Promise<SubscriptionResult | undefined> {
try {
@@ -78,18 +137,12 @@ export const ashbyHandler: WebhookProviderHandler = {
throw new Error('Trigger ID is required to create Ashby webhook.')
}
const webhookTypeMap: Record<string, string> = {
ashby_application_submit: 'applicationSubmit',
ashby_candidate_stage_change: 'candidateStageChange',
ashby_candidate_hire: 'candidateHire',
ashby_candidate_delete: 'candidateDelete',
ashby_job_create: 'jobCreate',
ashby_offer_create: 'offerCreate',
}
const webhookType = webhookTypeMap[triggerId]
const { ASHBY_TRIGGER_ACTION_MAP } = await import('@/triggers/ashby/utils')
const webhookType = ASHBY_TRIGGER_ACTION_MAP[triggerId]
if (!webhookType) {
throw new Error(`Unknown Ashby triggerId: ${triggerId}. Add it to webhookTypeMap.`)
throw new Error(
`Unknown Ashby triggerId: ${triggerId}. Add it to ASHBY_TRIGGER_ACTION_MAP.`
)
}
const notificationUrl = getNotificationUrl(ctx.webhook)
@@ -34,6 +34,7 @@ export const SUBBLOCK_ID_MIGRATIONS: Record<string, Record<string, string>> = {
ashby: {
emailType: '_removed_emailType',
phoneType: '_removed_phoneType',
filterCandidateId: '_removed_filterCandidateId',
},
rippling: {
action: '_removed_action',
+8 -12
View File
@@ -1,3 +1,5 @@
import type { AshbyCandidate } from '@/tools/ashby/types'
import { CANDIDATE_OUTPUTS, mapCandidate } from '@/tools/ashby/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyAddCandidateTagParams {
@@ -7,9 +9,7 @@ interface AshbyAddCandidateTagParams {
}
interface AshbyAddCandidateTagResponse extends ToolResponse {
output: {
success: boolean
}
output: AshbyCandidate
}
export const addCandidateTagTool: ToolConfig<
@@ -18,7 +18,7 @@ export const addCandidateTagTool: ToolConfig<
> = {
id: 'ashby_add_candidate_tag',
name: 'Ashby Add Candidate Tag',
description: 'Adds a tag to a candidate in Ashby.',
description: 'Adds a tag to a candidate in Ashby and returns the updated candidate.',
version: '1.0.0',
params: {
@@ -50,8 +50,8 @@ export const addCandidateTagTool: ToolConfig<
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: (params) => ({
candidateId: params.candidateId,
tagId: params.tagId,
candidateId: params.candidateId.trim(),
tagId: params.tagId.trim(),
}),
},
@@ -64,13 +64,9 @@ export const addCandidateTagTool: ToolConfig<
return {
success: true,
output: {
success: true,
},
output: mapCandidate(data.results),
}
},
outputs: {
success: { type: 'boolean', description: 'Whether the tag was successfully added' },
},
outputs: CANDIDATE_OUTPUTS,
}
@@ -1,3 +1,5 @@
import type { AshbyApplication } from '@/tools/ashby/types'
import { APPLICATION_OUTPUTS, mapApplication } from '@/tools/ashby/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyChangeApplicationStageParams {
@@ -8,10 +10,7 @@ interface AshbyChangeApplicationStageParams {
}
interface AshbyChangeApplicationStageResponse extends ToolResponse {
output: {
applicationId: string
stageId: string | null
}
output: AshbyApplication
}
export const changeApplicationStageTool: ToolConfig<
@@ -61,10 +60,10 @@ export const changeApplicationStageTool: ToolConfig<
}),
body: (params) => {
const body: Record<string, unknown> = {
applicationId: params.applicationId,
interviewStageId: params.interviewStageId,
applicationId: params.applicationId.trim(),
interviewStageId: params.interviewStageId.trim(),
}
if (params.archiveReasonId) body.archiveReasonId = params.archiveReasonId
if (params.archiveReasonId) body.archiveReasonId = params.archiveReasonId.trim()
return body
},
},
@@ -76,19 +75,11 @@ export const changeApplicationStageTool: ToolConfig<
throw new Error(data.errorInfo?.message || 'Failed to change application stage')
}
const r = data.results
return {
success: true,
output: {
applicationId: r.id ?? null,
stageId: r.currentInterviewStage?.id ?? null,
},
output: mapApplication(data.results),
}
},
outputs: {
applicationId: { type: 'string', description: 'Application UUID' },
stageId: { type: 'string', description: 'New interview stage UUID' },
},
outputs: APPLICATION_OUTPUTS,
}
+11 -17
View File
@@ -1,3 +1,5 @@
import type { AshbyApplication } from '@/tools/ashby/types'
import { APPLICATION_OUTPUTS, mapApplication } from '@/tools/ashby/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyCreateApplicationParams {
@@ -12,9 +14,7 @@ interface AshbyCreateApplicationParams {
}
interface AshbyCreateApplicationResponse extends ToolResponse {
output: {
applicationId: string
}
output: AshbyApplication
}
export const createApplicationTool: ToolConfig<
@@ -88,13 +88,13 @@ export const createApplicationTool: ToolConfig<
}),
body: (params) => {
const body: Record<string, unknown> = {
candidateId: params.candidateId,
jobId: params.jobId,
candidateId: params.candidateId.trim(),
jobId: params.jobId.trim(),
}
if (params.interviewPlanId) body.interviewPlanId = params.interviewPlanId
if (params.interviewStageId) body.interviewStageId = params.interviewStageId
if (params.sourceId) body.sourceId = params.sourceId
if (params.creditedToUserId) body.creditedToUserId = params.creditedToUserId
if (params.interviewPlanId) body.interviewPlanId = params.interviewPlanId.trim()
if (params.interviewStageId) body.interviewStageId = params.interviewStageId.trim()
if (params.sourceId) body.sourceId = params.sourceId.trim()
if (params.creditedToUserId) body.creditedToUserId = params.creditedToUserId.trim()
if (params.createdAt) body.createdAt = params.createdAt
return body
},
@@ -107,17 +107,11 @@ export const createApplicationTool: ToolConfig<
throw new Error(data.errorInfo?.message || 'Failed to create application')
}
const r = data.results
return {
success: true,
output: {
applicationId: r.applicationId ?? null,
},
output: mapApplication(data.results),
}
},
outputs: {
applicationId: { type: 'string', description: 'Created application UUID' },
},
outputs: APPLICATION_OUTPUTS,
}
+7 -50
View File
@@ -1,5 +1,6 @@
import type { AshbyCreateCandidateParams, AshbyCreateCandidateResponse } from '@/tools/ashby/types'
import { CANDIDATE_OUTPUTS, mapCandidate } from '@/tools/ashby/utils'
import type { ToolConfig } from '@/tools/types'
import type { AshbyCreateCandidateParams, AshbyCreateCandidateResponse } from './types'
export const createCandidateTool: ToolConfig<
AshbyCreateCandidateParams,
@@ -25,7 +26,7 @@ export const createCandidateTool: ToolConfig<
},
email: {
type: 'string',
required: true,
required: false,
visibility: 'user-or-llm',
description: 'Primary email address for the candidate',
},
@@ -65,12 +66,12 @@ export const createCandidateTool: ToolConfig<
body: (params) => {
const body: Record<string, unknown> = {
name: params.name,
email: params.email,
}
if (params.email) body.email = params.email
if (params.phoneNumber) body.phoneNumber = params.phoneNumber
if (params.linkedInUrl) body.linkedInUrl = params.linkedInUrl
if (params.githubUrl) body.githubUrl = params.githubUrl
if (params.sourceId) body.sourceId = params.sourceId
if (params.sourceId) body.sourceId = params.sourceId.trim()
return body
},
},
@@ -82,55 +83,11 @@ export const createCandidateTool: ToolConfig<
throw new Error(data.errorInfo?.message || 'Failed to create candidate')
}
const r = data.results
return {
success: true,
output: {
id: r.id ?? null,
name: r.name ?? null,
primaryEmailAddress: r.primaryEmailAddress
? {
value: r.primaryEmailAddress.value ?? '',
type: r.primaryEmailAddress.type ?? 'Other',
isPrimary: r.primaryEmailAddress.isPrimary ?? true,
}
: null,
primaryPhoneNumber: r.primaryPhoneNumber
? {
value: r.primaryPhoneNumber.value ?? '',
type: r.primaryPhoneNumber.type ?? 'Other',
isPrimary: r.primaryPhoneNumber.isPrimary ?? true,
}
: null,
createdAt: r.createdAt ?? null,
},
output: mapCandidate(data.results),
}
},
outputs: {
id: { type: 'string', description: 'Created candidate UUID' },
name: { type: 'string', description: 'Full name' },
primaryEmailAddress: {
type: 'object',
description: 'Primary email contact info',
optional: true,
properties: {
value: { type: 'string', description: 'Email address' },
type: { type: 'string', description: 'Contact type (Personal, Work, Other)' },
isPrimary: { type: 'boolean', description: 'Whether this is the primary email' },
},
},
primaryPhoneNumber: {
type: 'object',
description: 'Primary phone contact info',
optional: true,
properties: {
value: { type: 'string', description: 'Phone number' },
type: { type: 'string', description: 'Contact type (Personal, Work, Other)' },
isPrimary: { type: 'boolean', description: 'Whether this is the primary phone' },
},
},
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
},
outputs: CANDIDATE_OUTPUTS,
}
+30 -4
View File
@@ -1,5 +1,5 @@
import type { AshbyCreateNoteParams, AshbyCreateNoteResponse } from '@/tools/ashby/types'
import type { ToolConfig } from '@/tools/types'
import type { AshbyCreateNoteParams, AshbyCreateNoteResponse } from './types'
export const createNoteTool: ToolConfig<AshbyCreateNoteParams, AshbyCreateNoteResponse> = {
id: 'ashby_create_note',
@@ -51,7 +51,7 @@ export const createNoteTool: ToolConfig<AshbyCreateNoteParams, AshbyCreateNoteRe
}),
body: (params) => {
const body: Record<string, unknown> = {
candidateId: params.candidateId,
candidateId: params.candidateId.trim(),
sendNotifications: params.sendNotifications ?? false,
}
if (params.noteType === 'text/html') {
@@ -74,16 +74,42 @@ export const createNoteTool: ToolConfig<AshbyCreateNoteParams, AshbyCreateNoteRe
}
const r = data.results
const author = r.author
return {
success: true,
output: {
noteId: r.id ?? null,
id: r.id ?? '',
createdAt: r.createdAt ?? null,
isPrivate: r.isPrivate ?? false,
content: r.content ?? null,
author: author
? {
id: author.id ?? '',
firstName: author.firstName ?? null,
lastName: author.lastName ?? null,
email: author.email ?? null,
}
: null,
},
}
},
outputs: {
noteId: { type: 'string', description: 'Created note UUID' },
id: { type: 'string', description: 'Created note UUID' },
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp', optional: true },
isPrivate: { type: 'boolean', description: 'Whether the note is private' },
content: { type: 'string', description: 'Note content', optional: true },
author: {
type: 'object',
description: 'Author of the note',
optional: true,
properties: {
id: { type: 'string', description: 'Author user UUID' },
firstName: { type: 'string', description: 'Author first name', optional: true },
lastName: { type: 'string', description: 'Author last name', optional: true },
email: { type: 'string', description: 'Author email', optional: true },
},
},
},
}
+6 -119
View File
@@ -1,3 +1,5 @@
import type { AshbyApplication } from '@/tools/ashby/types'
import { APPLICATION_OUTPUTS, mapApplication } from '@/tools/ashby/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyGetApplicationParams {
@@ -6,35 +8,7 @@ interface AshbyGetApplicationParams {
}
interface AshbyGetApplicationResponse extends ToolResponse {
output: {
id: string
status: string
candidate: {
id: string
name: string
}
job: {
id: string
title: string
}
currentInterviewStage: {
id: string
title: string
type: string
} | null
source: {
id: string
title: string
} | null
archiveReason: {
id: string
text: string
reasonType: string
} | null
archivedAt: string | null
createdAt: string
updatedAt: string
}
output: AshbyApplication
}
export const getApplicationTool: ToolConfig<
@@ -69,7 +43,7 @@ export const getApplicationTool: ToolConfig<
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: (params) => ({
applicationId: params.applicationId,
applicationId: params.applicationId.trim(),
}),
},
@@ -80,98 +54,11 @@ export const getApplicationTool: ToolConfig<
throw new Error(data.errorInfo?.message || 'Failed to get application')
}
const r = data.results
return {
success: true,
output: {
id: r.id ?? null,
status: r.status ?? null,
candidate: {
id: r.candidate?.id ?? null,
name: r.candidate?.name ?? null,
},
job: {
id: r.job?.id ?? null,
title: r.job?.title ?? null,
},
currentInterviewStage: r.currentInterviewStage
? {
id: r.currentInterviewStage.id ?? null,
title: r.currentInterviewStage.title ?? null,
type: r.currentInterviewStage.type ?? null,
}
: null,
source: r.source
? {
id: r.source.id ?? null,
title: r.source.title ?? null,
}
: null,
archiveReason: r.archiveReason
? {
id: r.archiveReason.id ?? null,
text: r.archiveReason.text ?? null,
reasonType: r.archiveReason.reasonType ?? null,
}
: null,
archivedAt: r.archivedAt ?? null,
createdAt: r.createdAt ?? null,
updatedAt: r.updatedAt ?? null,
},
output: mapApplication(data.results),
}
},
outputs: {
id: { type: 'string', description: 'Application UUID' },
status: { type: 'string', description: 'Application status (Active, Hired, Archived, Lead)' },
candidate: {
type: 'object',
description: 'Associated candidate',
properties: {
id: { type: 'string', description: 'Candidate UUID' },
name: { type: 'string', description: 'Candidate name' },
},
},
job: {
type: 'object',
description: 'Associated job',
properties: {
id: { type: 'string', description: 'Job UUID' },
title: { type: 'string', description: 'Job title' },
},
},
currentInterviewStage: {
type: 'object',
description: 'Current interview stage',
optional: true,
properties: {
id: { type: 'string', description: 'Stage UUID' },
title: { type: 'string', description: 'Stage title' },
type: { type: 'string', description: 'Stage type' },
},
},
source: {
type: 'object',
description: 'Application source',
optional: true,
properties: {
id: { type: 'string', description: 'Source UUID' },
title: { type: 'string', description: 'Source title' },
},
},
archiveReason: {
type: 'object',
description: 'Reason for archival',
optional: true,
properties: {
id: { type: 'string', description: 'Reason UUID' },
text: { type: 'string', description: 'Reason text' },
reasonType: { type: 'string', description: 'Reason type' },
},
},
archivedAt: { type: 'string', description: 'ISO 8601 archive timestamp', optional: true },
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp' },
},
outputs: APPLICATION_OUTPUTS,
}
+5 -87
View File
@@ -1,5 +1,6 @@
import type { AshbyGetCandidateParams, AshbyGetCandidateResponse } from '@/tools/ashby/types'
import { CANDIDATE_OUTPUTS, mapCandidate } from '@/tools/ashby/utils'
import type { ToolConfig } from '@/tools/types'
import type { AshbyGetCandidateParams, AshbyGetCandidateResponse } from './types'
export const getCandidateTool: ToolConfig<AshbyGetCandidateParams, AshbyGetCandidateResponse> = {
id: 'ashby_get_candidate',
@@ -30,7 +31,7 @@ export const getCandidateTool: ToolConfig<AshbyGetCandidateParams, AshbyGetCandi
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: (params) => ({
candidateId: params.candidateId.trim(),
id: params.candidateId.trim(),
}),
},
@@ -41,94 +42,11 @@ export const getCandidateTool: ToolConfig<AshbyGetCandidateParams, AshbyGetCandi
throw new Error(data.errorInfo?.message || 'Failed to get candidate')
}
const r = data.results
return {
success: true,
output: {
id: r.id ?? null,
name: r.name ?? null,
primaryEmailAddress: r.primaryEmailAddress
? {
value: r.primaryEmailAddress.value ?? '',
type: r.primaryEmailAddress.type ?? 'Other',
isPrimary: r.primaryEmailAddress.isPrimary ?? true,
}
: null,
primaryPhoneNumber: r.primaryPhoneNumber
? {
value: r.primaryPhoneNumber.value ?? '',
type: r.primaryPhoneNumber.type ?? 'Other',
isPrimary: r.primaryPhoneNumber.isPrimary ?? true,
}
: null,
profileUrl: r.profileUrl ?? null,
position: r.position ?? null,
company: r.company ?? null,
linkedInUrl:
(r.socialLinks ?? []).find((l: { type: string }) => l.type === 'LinkedIn')?.url ?? null,
githubUrl:
(r.socialLinks ?? []).find((l: { type: string }) => l.type === 'GitHub')?.url ?? null,
tags: (r.tags ?? []).map((t: { id: string; title: string }) => ({
id: t.id,
title: t.title,
})),
applicationIds: r.applicationIds ?? [],
createdAt: r.createdAt ?? null,
updatedAt: r.updatedAt ?? null,
},
output: mapCandidate(data.results),
}
},
outputs: {
id: { type: 'string', description: 'Candidate UUID' },
name: { type: 'string', description: 'Full name' },
primaryEmailAddress: {
type: 'object',
description: 'Primary email contact info',
optional: true,
properties: {
value: { type: 'string', description: 'Email address' },
type: { type: 'string', description: 'Contact type (Personal, Work, Other)' },
isPrimary: { type: 'boolean', description: 'Whether this is the primary email' },
},
},
primaryPhoneNumber: {
type: 'object',
description: 'Primary phone contact info',
optional: true,
properties: {
value: { type: 'string', description: 'Phone number' },
type: { type: 'string', description: 'Contact type (Personal, Work, Other)' },
isPrimary: { type: 'boolean', description: 'Whether this is the primary phone' },
},
},
profileUrl: {
type: 'string',
description: 'URL to the candidate Ashby profile',
optional: true,
},
position: { type: 'string', description: 'Current position or title', optional: true },
company: { type: 'string', description: 'Current company', optional: true },
linkedInUrl: { type: 'string', description: 'LinkedIn profile URL', optional: true },
githubUrl: { type: 'string', description: 'GitHub profile URL', optional: true },
tags: {
type: 'array',
description: 'Tags applied to the candidate',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Tag UUID' },
title: { type: 'string', description: 'Tag title' },
},
},
},
applicationIds: {
type: 'array',
description: 'IDs of associated applications',
items: { type: 'string', description: 'Application UUID' },
},
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp' },
},
outputs: CANDIDATE_OUTPUTS,
}
+5 -36
View File
@@ -1,5 +1,6 @@
import type { AshbyGetJobParams, AshbyGetJobResponse } from '@/tools/ashby/types'
import { JOB_OUTPUTS, mapJob } from '@/tools/ashby/utils'
import type { ToolConfig } from '@/tools/types'
import type { AshbyGetJobParams, AshbyGetJobResponse } from './types'
export const getJobTool: ToolConfig<AshbyGetJobParams, AshbyGetJobResponse> = {
id: 'ashby_get_job',
@@ -30,7 +31,7 @@ export const getJobTool: ToolConfig<AshbyGetJobParams, AshbyGetJobResponse> = {
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: (params) => ({
jobId: params.jobId.trim(),
id: params.jobId.trim(),
}),
},
@@ -41,43 +42,11 @@ export const getJobTool: ToolConfig<AshbyGetJobParams, AshbyGetJobResponse> = {
throw new Error(data.errorInfo?.message || 'Failed to get job')
}
const r = data.results
return {
success: true,
output: {
id: r.id ?? null,
title: r.title ?? null,
status: r.status ?? null,
employmentType: r.employmentType ?? null,
departmentId: r.departmentId ?? null,
locationId: r.locationId ?? null,
descriptionPlain: r.descriptionPlain ?? null,
isArchived: r.isArchived ?? false,
createdAt: r.createdAt ?? null,
updatedAt: r.updatedAt ?? null,
},
output: mapJob(data.results),
}
},
outputs: {
id: { type: 'string', description: 'Job UUID' },
title: { type: 'string', description: 'Job title' },
status: { type: 'string', description: 'Job status (Open, Closed, Draft, Archived)' },
employmentType: {
type: 'string',
description: 'Employment type (FullTime, PartTime, Intern, Contract, Temporary)',
optional: true,
},
departmentId: { type: 'string', description: 'Department UUID', optional: true },
locationId: { type: 'string', description: 'Location UUID', optional: true },
descriptionPlain: {
type: 'string',
description: 'Job description in plain text',
optional: true,
},
isArchived: { type: 'boolean', description: 'Whether the job is archived' },
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp' },
},
outputs: JOB_OUTPUTS,
}
+348 -35
View File
@@ -3,20 +3,80 @@ import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyGetJobPostingParams {
apiKey: string
jobPostingId: string
expandApplicationFormDefinition?: boolean
expandSurveyFormDefinitions?: boolean
}
interface AshbyDescriptionPart {
html: string | null
plain: string | null
}
interface AshbyJobPosting {
id: string
title: string
descriptionPlain: string | null
descriptionHtml: string | null
descriptionSocial: string | null
descriptionParts: {
descriptionOpening: AshbyDescriptionPart | null
descriptionBody: AshbyDescriptionPart | null
descriptionClosing: AshbyDescriptionPart | null
} | null
departmentName: string | null
teamName: string | null
teamNameHierarchy: string[]
jobId: string | null
locationName: string | null
locationIds: {
primaryLocationId: string | null
secondaryLocationIds: string[]
} | null
address: {
postalAddress: {
addressCountry: string | null
addressRegion: string | null
addressLocality: string | null
postalCode: string | null
streetAddress: string | null
} | null
} | null
isRemote: boolean
workplaceType: string | null
employmentType: string | null
isListed: boolean
suppressDescriptionOpening: boolean
suppressDescriptionClosing: boolean
publishedDate: string | null
applicationDeadline: string | null
externalLink: string | null
applyLink: string | null
compensation: {
compensationTierSummary: string | null
summaryComponents: Array<{
summary: string | null
compensationTypeLabel: string | null
interval: string | null
currencyCode: string | null
minValue: number | null
maxValue: number | null
}>
shouldDisplayCompensationOnJobBoard: boolean
} | null
applicationLimitCalloutHtml: string | null
updatedAt: string | null
}
interface AshbyGetJobPostingResponse extends ToolResponse {
output: {
id: string
title: string
jobId: string | null
locationName: string | null
departmentName: string | null
employmentType: string | null
descriptionPlain: string | null
isListed: boolean
publishedDate: string | null
externalLink: string | null
output: AshbyJobPosting
}
function mapDescriptionPart(raw: unknown): AshbyDescriptionPart | null {
if (!raw || typeof raw !== 'object') return null
const p = raw as Record<string, unknown>
return {
html: (p.html as string) ?? null,
plain: (p.plain as string) ?? null,
}
}
@@ -39,6 +99,18 @@ export const getJobPostingTool: ToolConfig<AshbyGetJobPostingParams, AshbyGetJob
visibility: 'user-or-llm',
description: 'The UUID of the job posting to fetch',
},
expandApplicationFormDefinition: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include application form definition in the response',
},
expandSurveyFormDefinitions: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include survey form definitions in the response',
},
},
request: {
@@ -48,9 +120,18 @@ export const getJobPostingTool: ToolConfig<AshbyGetJobPostingParams, AshbyGetJob
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: (params) => ({
jobPostingId: params.jobPostingId,
}),
body: (params) => {
const body: Record<string, unknown> = {
jobPostingId: params.jobPostingId.trim(),
}
if (params.expandApplicationFormDefinition !== undefined) {
body.expandApplicationFormDefinition = params.expandApplicationFormDefinition
}
if (params.expandSurveyFormDefinitions !== undefined) {
body.expandSurveyFormDefinitions = params.expandSurveyFormDefinitions
}
return body
},
},
transformResponse: async (response: Response) => {
@@ -60,21 +141,90 @@ export const getJobPostingTool: ToolConfig<AshbyGetJobPostingParams, AshbyGetJob
throw new Error(data.errorInfo?.message || 'Failed to get job posting')
}
const r = data.results
const r = (data.results ?? {}) as Record<string, unknown> & {
descriptionParts?: Record<string, unknown>
locationIds?: { primaryLocationId?: string; secondaryLocationIds?: string[] }
address?: { postalAddress?: Record<string, unknown> }
compensation?: Record<string, unknown> & {
summaryComponents?: Array<Record<string, unknown>>
}
}
const pa = r.address?.postalAddress
const comp = r.compensation
const summaryComponents = Array.isArray(comp?.summaryComponents) ? comp.summaryComponents : []
const descParts = r.descriptionParts
return {
success: true,
output: {
id: r.id ?? null,
title: r.jobTitle ?? r.title ?? null,
jobId: r.jobId ?? null,
locationName: r.locationName ?? null,
departmentName: r.departmentName ?? null,
employmentType: r.employmentType ?? null,
descriptionPlain: r.descriptionPlain ?? r.description ?? null,
isListed: r.isListed ?? false,
publishedDate: r.publishedDate ?? null,
externalLink: r.externalLink ?? null,
id: (r.id as string) ?? '',
title: (r.title as string) ?? '',
descriptionPlain: (r.descriptionPlain as string) ?? null,
descriptionHtml: (r.descriptionHtml as string) ?? null,
descriptionSocial: (r.descriptionSocial as string) ?? null,
descriptionParts: descParts
? {
descriptionOpening: mapDescriptionPart(descParts.descriptionOpening),
descriptionBody: mapDescriptionPart(descParts.descriptionBody),
descriptionClosing: mapDescriptionPart(descParts.descriptionClosing),
}
: null,
departmentName: (r.departmentName as string) ?? null,
teamName: (r.teamName as string) ?? null,
teamNameHierarchy: Array.isArray(r.teamNameHierarchy)
? (r.teamNameHierarchy as string[])
: [],
jobId: (r.jobId as string) ?? null,
locationName: (r.locationName as string) ?? null,
locationIds: r.locationIds
? {
primaryLocationId: r.locationIds.primaryLocationId ?? null,
secondaryLocationIds: Array.isArray(r.locationIds.secondaryLocationIds)
? r.locationIds.secondaryLocationIds
: [],
}
: null,
address: r.address
? {
postalAddress: pa
? {
addressCountry: (pa.addressCountry as string) ?? null,
addressRegion: (pa.addressRegion as string) ?? null,
addressLocality: (pa.addressLocality as string) ?? null,
postalCode: (pa.postalCode as string) ?? null,
streetAddress: (pa.streetAddress as string) ?? null,
}
: null,
}
: null,
isRemote: (r.isRemote as boolean) ?? false,
workplaceType: (r.workplaceType as string) ?? null,
employmentType: (r.employmentType as string) ?? null,
isListed: (r.isListed as boolean) ?? false,
suppressDescriptionOpening: (r.suppressDescriptionOpening as boolean) ?? false,
suppressDescriptionClosing: (r.suppressDescriptionClosing as boolean) ?? false,
publishedDate: (r.publishedDate as string) ?? null,
applicationDeadline: (r.applicationDeadline as string) ?? null,
externalLink: (r.externalLink as string) ?? null,
applyLink: (r.applyLink as string) ?? null,
compensation: comp
? {
compensationTierSummary: (comp.compensationTierSummary as string) ?? null,
summaryComponents: summaryComponents.map((c) => ({
summary: (c.summary as string) ?? null,
compensationTypeLabel: (c.compensationTypeLabel as string) ?? null,
interval: (c.interval as string) ?? null,
currencyCode: (c.currencyCode as string) ?? null,
minValue: (c.minValue as number) ?? null,
maxValue: (c.maxValue as number) ?? null,
})),
shouldDisplayCompensationOnJobBoard:
(comp.shouldDisplayCompensationOnJobBoard as boolean) ?? false,
}
: null,
applicationLimitCalloutHtml: (r.applicationLimitCalloutHtml as string) ?? null,
updatedAt: (r.updatedAt as string) ?? null,
},
}
},
@@ -82,25 +232,188 @@ export const getJobPostingTool: ToolConfig<AshbyGetJobPostingParams, AshbyGetJob
outputs: {
id: { type: 'string', description: 'Job posting UUID' },
title: { type: 'string', description: 'Job posting title' },
jobId: { type: 'string', description: 'Associated job UUID', optional: true },
locationName: { type: 'string', description: 'Location name', optional: true },
departmentName: { type: 'string', description: 'Department name', optional: true },
employmentType: {
type: 'string',
description: 'Employment type (e.g. FullTime, PartTime, Contract)',
optional: true,
},
descriptionPlain: {
type: 'string',
description: 'Job posting description in plain text',
description: 'Full description in plain text',
optional: true,
},
isListed: { type: 'boolean', description: 'Whether the posting is publicly listed' },
descriptionHtml: {
type: 'string',
description: 'Full description in HTML',
optional: true,
},
descriptionSocial: {
type: 'string',
description: 'Shortened description for social sharing (max 200 chars)',
optional: true,
},
descriptionParts: {
type: 'object',
description: 'Description broken into opening, body, and closing sections',
optional: true,
properties: {
descriptionOpening: {
type: 'object',
description: 'Opening (from Job Boards theme settings)',
optional: true,
properties: {
html: { type: 'string', description: 'HTML content', optional: true },
plain: { type: 'string', description: 'Plain text content', optional: true },
},
},
descriptionBody: {
type: 'object',
description: 'Main description body',
optional: true,
properties: {
html: { type: 'string', description: 'HTML content', optional: true },
plain: { type: 'string', description: 'Plain text content', optional: true },
},
},
descriptionClosing: {
type: 'object',
description: 'Closing (from Job Boards theme settings)',
optional: true,
properties: {
html: { type: 'string', description: 'HTML content', optional: true },
plain: { type: 'string', description: 'Plain text content', optional: true },
},
},
},
},
departmentName: { type: 'string', description: 'Department name', optional: true },
teamName: { type: 'string', description: 'Team name', optional: true },
teamNameHierarchy: {
type: 'array',
description: 'Hierarchy of team names from root to team',
items: { type: 'string', description: 'Team name' },
},
jobId: { type: 'string', description: 'Associated job UUID', optional: true },
locationName: { type: 'string', description: 'Primary location name', optional: true },
locationIds: {
type: 'object',
description: 'Primary and secondary location UUIDs',
optional: true,
properties: {
primaryLocationId: {
type: 'string',
description: 'Primary location UUID',
optional: true,
},
secondaryLocationIds: {
type: 'array',
description: 'Secondary location UUIDs',
items: { type: 'string', description: 'Location UUID' },
},
},
},
address: {
type: 'object',
description: 'Postal address of the posting location',
optional: true,
properties: {
postalAddress: {
type: 'object',
description: 'Structured postal address',
optional: true,
properties: {
addressCountry: { type: 'string', description: 'Country', optional: true },
addressRegion: { type: 'string', description: 'State or region', optional: true },
addressLocality: { type: 'string', description: 'City or locality', optional: true },
postalCode: { type: 'string', description: 'Postal code', optional: true },
streetAddress: { type: 'string', description: 'Street address', optional: true },
},
},
},
},
isRemote: { type: 'boolean', description: 'Whether the posting is remote' },
workplaceType: {
type: 'string',
description: 'Workplace type (OnSite, Remote, Hybrid)',
optional: true,
},
employmentType: {
type: 'string',
description: 'Employment type (FullTime, PartTime, Intern, Contract, Temporary)',
optional: true,
},
isListed: { type: 'boolean', description: 'Whether publicly listed on the job board' },
suppressDescriptionOpening: {
type: 'boolean',
description: 'Whether the theme opening is hidden on this posting',
},
suppressDescriptionClosing: {
type: 'boolean',
description: 'Whether the theme closing is hidden on this posting',
},
publishedDate: { type: 'string', description: 'ISO 8601 published date', optional: true },
applicationDeadline: {
type: 'string',
description: 'ISO 8601 application deadline',
optional: true,
},
externalLink: {
type: 'string',
description: 'External link to the job posting',
optional: true,
},
applyLink: {
type: 'string',
description: 'Direct apply link',
optional: true,
},
compensation: {
type: 'object',
description: 'Compensation details for the posting',
optional: true,
properties: {
compensationTierSummary: {
type: 'string',
description: 'Human-readable tier summary',
optional: true,
},
summaryComponents: {
type: 'array',
description: 'Structured compensation components',
items: {
type: 'object',
properties: {
summary: { type: 'string', description: 'Component summary', optional: true },
compensationTypeLabel: {
type: 'string',
description: 'Component type label (Salary, Commission, Bonus, Equity, etc.)',
optional: true,
},
interval: {
type: 'string',
description: 'Payment interval (e.g. annual, hourly)',
optional: true,
},
currencyCode: {
type: 'string',
description: 'ISO 4217 currency code',
optional: true,
},
minValue: { type: 'number', description: 'Minimum value', optional: true },
maxValue: { type: 'number', description: 'Maximum value', optional: true },
},
},
},
shouldDisplayCompensationOnJobBoard: {
type: 'boolean',
description: 'Whether compensation is shown on the job board',
},
},
},
applicationLimitCalloutHtml: {
type: 'string',
description: 'HTML callout shown when application limit is reached',
optional: true,
},
updatedAt: {
type: 'string',
description: 'ISO 8601 last update timestamp',
optional: true,
},
},
}
+6 -61
View File
@@ -1,3 +1,5 @@
import type { AshbyOffer } from '@/tools/ashby/types'
import { mapOffer, OFFER_OUTPUTS } from '@/tools/ashby/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyGetOfferParams {
@@ -6,19 +8,7 @@ interface AshbyGetOfferParams {
}
interface AshbyGetOfferResponse extends ToolResponse {
output: {
id: string
offerStatus: string
acceptanceStatus: string | null
applicationId: string | null
startDate: string | null
salary: {
currencyCode: string
value: number
} | null
openingId: string | null
createdAt: string | null
}
output: AshbyOffer
}
export const getOfferTool: ToolConfig<AshbyGetOfferParams, AshbyGetOfferResponse> = {
@@ -50,7 +40,7 @@ export const getOfferTool: ToolConfig<AshbyGetOfferParams, AshbyGetOfferResponse
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: (params) => ({
offerId: params.offerId,
offerId: params.offerId.trim(),
}),
},
@@ -61,56 +51,11 @@ export const getOfferTool: ToolConfig<AshbyGetOfferParams, AshbyGetOfferResponse
throw new Error(data.errorInfo?.message || 'Failed to get offer')
}
const r = data.results
const v = r.latestVersion
return {
success: true,
output: {
id: r.id ?? null,
offerStatus: r.offerStatus ?? null,
acceptanceStatus: r.acceptanceStatus ?? null,
applicationId: r.applicationId ?? null,
startDate: v?.startDate ?? null,
salary: v?.salary
? {
currencyCode: v.salary.currencyCode ?? null,
value: v.salary.value ?? null,
}
: null,
openingId: v?.openingId ?? null,
createdAt: v?.createdAt ?? null,
},
output: mapOffer(data.results),
}
},
outputs: {
id: { type: 'string', description: 'Offer UUID' },
offerStatus: {
type: 'string',
description: 'Offer status (e.g. WaitingOnCandidateResponse, CandidateAccepted)',
},
acceptanceStatus: {
type: 'string',
description: 'Acceptance status (e.g. Accepted, Declined, Pending)',
optional: true,
},
applicationId: { type: 'string', description: 'Associated application UUID', optional: true },
startDate: { type: 'string', description: 'Offer start date', optional: true },
salary: {
type: 'object',
description: 'Salary details',
optional: true,
properties: {
currencyCode: { type: 'string', description: 'ISO 4217 currency code' },
value: { type: 'number', description: 'Salary amount' },
},
},
openingId: { type: 'string', description: 'Associated opening UUID', optional: true },
createdAt: {
type: 'string',
description: 'ISO 8601 creation timestamp (from latest version)',
optional: true,
},
},
outputs: OFFER_OUTPUTS,
}
+14 -92
View File
@@ -1,5 +1,9 @@
import type {
AshbyListApplicationsParams,
AshbyListApplicationsResponse,
} from '@/tools/ashby/types'
import { APPLICATION_OUTPUTS, mapApplication } from '@/tools/ashby/utils'
import type { ToolConfig } from '@/tools/types'
import type { AshbyListApplicationsParams, AshbyListApplicationsResponse } from './types'
export const listApplicationsTool: ToolConfig<
AshbyListApplicationsParams,
@@ -8,7 +12,7 @@ export const listApplicationsTool: ToolConfig<
id: 'ashby_list_applications',
name: 'Ashby List Applications',
description:
'Lists all applications in an Ashby organization with pagination and optional filters for status, job, candidate, and creation date.',
'Lists all applications in an Ashby organization with pagination and optional filters for status, job, and creation date.',
version: '1.0.0',
params: {
@@ -42,12 +46,6 @@ export const listApplicationsTool: ToolConfig<
visibility: 'user-or-llm',
description: 'Filter applications by a specific job UUID',
},
candidateId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter applications by a specific candidate UUID',
},
createdAfter: {
type: 'string',
required: false,
@@ -68,10 +66,12 @@ export const listApplicationsTool: ToolConfig<
const body: Record<string, unknown> = {}
if (params.cursor) body.cursor = params.cursor
if (params.perPage) body.limit = params.perPage
if (params.status) body.status = [params.status]
if (params.jobId) body.jobId = params.jobId
if (params.candidateId) body.candidateId = params.candidateId
if (params.createdAfter) body.createdAfter = new Date(params.createdAfter).getTime()
if (params.status) body.status = params.status
if (params.jobId) body.jobId = params.jobId.trim()
if (params.createdAfter) {
const ms = new Date(params.createdAfter).getTime()
if (!Number.isNaN(ms)) body.createdAfter = ms
}
return body
},
},
@@ -86,42 +86,7 @@ export const listApplicationsTool: ToolConfig<
return {
success: true,
output: {
applications: (data.results ?? []).map(
(
a: Record<string, unknown> & {
candidate?: { id?: string; name?: string }
job?: { id?: string; title?: string }
currentInterviewStage?: { id?: string; title?: string; type?: string } | null
source?: { id?: string; title?: string } | null
}
) => ({
id: a.id ?? null,
status: a.status ?? null,
candidate: {
id: a.candidate?.id ?? null,
name: a.candidate?.name ?? null,
},
job: {
id: a.job?.id ?? null,
title: a.job?.title ?? null,
},
currentInterviewStage: a.currentInterviewStage
? {
id: a.currentInterviewStage.id ?? null,
title: a.currentInterviewStage.title ?? null,
type: a.currentInterviewStage.type ?? null,
}
: null,
source: a.source
? {
id: a.source.id ?? null,
title: a.source.title ?? null,
}
: null,
createdAt: a.createdAt ?? null,
updatedAt: a.updatedAt ?? null,
})
),
applications: (data.results ?? []).map(mapApplication),
moreDataAvailable: data.moreDataAvailable ?? false,
nextCursor: data.nextCursor ?? null,
},
@@ -134,50 +99,7 @@ export const listApplicationsTool: ToolConfig<
description: 'List of applications',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Application UUID' },
status: {
type: 'string',
description: 'Application status (Active, Hired, Archived, Lead)',
},
candidate: {
type: 'object',
description: 'Associated candidate',
properties: {
id: { type: 'string', description: 'Candidate UUID' },
name: { type: 'string', description: 'Candidate name' },
},
},
job: {
type: 'object',
description: 'Associated job',
properties: {
id: { type: 'string', description: 'Job UUID' },
title: { type: 'string', description: 'Job title' },
},
},
currentInterviewStage: {
type: 'object',
description: 'Current interview stage',
optional: true,
properties: {
id: { type: 'string', description: 'Stage UUID' },
title: { type: 'string', description: 'Stage title' },
type: { type: 'string', description: 'Stage type' },
},
},
source: {
type: 'object',
description: 'Application source',
optional: true,
properties: {
id: { type: 'string', description: 'Source UUID' },
title: { type: 'string', description: 'Source title' },
},
},
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp' },
},
properties: APPLICATION_OUTPUTS,
},
},
moreDataAvailable: {
+28 -12
View File
@@ -2,16 +2,19 @@ import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyListArchiveReasonsParams {
apiKey: string
includeArchived?: boolean
}
interface AshbyArchiveReason {
id: string
text: string
reasonType: string
isArchived: boolean
}
interface AshbyListArchiveReasonsResponse extends ToolResponse {
output: {
archiveReasons: Array<{
id: string
text: string
reasonType: string
isArchived: boolean
}>
archiveReasons: AshbyArchiveReason[]
}
}
@@ -31,6 +34,12 @@ export const listArchiveReasonsTool: ToolConfig<
visibility: 'user-only',
description: 'Ashby API Key',
},
includeArchived: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to include archived archive reasons in the response (default false)',
},
},
request: {
@@ -40,7 +49,11 @@ export const listArchiveReasonsTool: ToolConfig<
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: () => ({}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.includeArchived !== undefined) body.includeArchived = params.includeArchived
return body
},
},
transformResponse: async (response: Response) => {
@@ -54,10 +67,10 @@ export const listArchiveReasonsTool: ToolConfig<
success: true,
output: {
archiveReasons: (data.results ?? []).map((r: Record<string, unknown>) => ({
id: r.id ?? null,
text: r.text ?? null,
reasonType: r.reasonType ?? null,
isArchived: r.isArchived ?? false,
id: (r.id as string) ?? '',
text: (r.text as string) ?? '',
reasonType: (r.reasonType as string) ?? '',
isArchived: (r.isArchived as boolean) ?? false,
})),
},
}
@@ -72,7 +85,10 @@ export const listArchiveReasonsTool: ToolConfig<
properties: {
id: { type: 'string', description: 'Archive reason UUID' },
text: { type: 'string', description: 'Archive reason text' },
reasonType: { type: 'string', description: 'Reason type' },
reasonType: {
type: 'string',
description: 'Reason type (RejectedByCandidate, RejectedByOrg, Other)',
},
isArchived: { type: 'boolean', description: 'Whether the reason is archived' },
},
},
+66 -9
View File
@@ -2,15 +2,24 @@ import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyListCandidateTagsParams {
apiKey: string
includeArchived?: boolean
cursor?: string
syncToken?: string
perPage?: number
}
interface AshbyCandidateTag {
id: string
title: string
isArchived: boolean
}
interface AshbyListCandidateTagsResponse extends ToolResponse {
output: {
tags: Array<{
id: string
title: string
isArchived: boolean
}>
tags: AshbyCandidateTag[]
moreDataAvailable: boolean
nextCursor: string | null
syncToken: string | null
}
}
@@ -30,6 +39,30 @@ export const listCandidateTagsTool: ToolConfig<
visibility: 'user-only',
description: 'Ashby API Key',
},
includeArchived: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to include archived candidate tags (default false)',
},
cursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Opaque pagination cursor from a previous response nextCursor value',
},
syncToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sync token from a previous response to fetch only changed results',
},
perPage: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (default 100)',
},
},
request: {
@@ -39,7 +72,14 @@ export const listCandidateTagsTool: ToolConfig<
'Content-Type': 'application/json',
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: () => ({}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.includeArchived !== undefined) body.includeArchived = params.includeArchived
if (params.cursor) body.cursor = params.cursor
if (params.syncToken) body.syncToken = params.syncToken
if (params.perPage) body.limit = params.perPage
return body
},
},
transformResponse: async (response: Response) => {
@@ -53,10 +93,13 @@ export const listCandidateTagsTool: ToolConfig<
success: true,
output: {
tags: (data.results ?? []).map((t: Record<string, unknown>) => ({
id: t.id ?? null,
title: t.title ?? null,
isArchived: t.isArchived ?? false,
id: (t.id as string) ?? '',
title: (t.title as string) ?? '',
isArchived: (t.isArchived as boolean) ?? false,
})),
moreDataAvailable: data.moreDataAvailable ?? false,
nextCursor: data.nextCursor ?? null,
syncToken: data.syncToken ?? null,
},
}
},
@@ -74,5 +117,19 @@ export const listCandidateTagsTool: ToolConfig<
},
},
},
moreDataAvailable: {
type: 'boolean',
description: 'Whether more pages of results exist',
},
nextCursor: {
type: 'string',
description: 'Opaque cursor for fetching the next page',
optional: true,
},
syncToken: {
type: 'string',
description: 'Sync token to use for incremental updates in future requests',
optional: true,
},
},
}
+4 -54
View File
@@ -1,5 +1,6 @@
import type { AshbyListCandidatesParams, AshbyListCandidatesResponse } from '@/tools/ashby/types'
import { CANDIDATE_OUTPUTS, mapCandidate } from '@/tools/ashby/utils'
import type { ToolConfig } from '@/tools/types'
import type { AshbyListCandidatesParams, AshbyListCandidatesResponse } from './types'
export const listCandidatesTool: ToolConfig<
AshbyListCandidatesParams,
@@ -56,33 +57,7 @@ export const listCandidatesTool: ToolConfig<
return {
success: true,
output: {
candidates: (data.results ?? []).map(
(
c: Record<string, unknown> & {
primaryEmailAddress?: { value?: string; type?: string; isPrimary?: boolean }
primaryPhoneNumber?: { value?: string; type?: string; isPrimary?: boolean }
}
) => ({
id: c.id ?? null,
name: c.name ?? null,
primaryEmailAddress: c.primaryEmailAddress
? {
value: c.primaryEmailAddress.value ?? '',
type: c.primaryEmailAddress.type ?? 'Other',
isPrimary: c.primaryEmailAddress.isPrimary ?? true,
}
: null,
primaryPhoneNumber: c.primaryPhoneNumber
? {
value: c.primaryPhoneNumber.value ?? '',
type: c.primaryPhoneNumber.type ?? 'Other',
isPrimary: c.primaryPhoneNumber.isPrimary ?? true,
}
: null,
createdAt: c.createdAt ?? null,
updatedAt: c.updatedAt ?? null,
})
),
candidates: (data.results ?? []).map(mapCandidate),
moreDataAvailable: data.moreDataAvailable ?? false,
nextCursor: data.nextCursor ?? null,
},
@@ -95,32 +70,7 @@ export const listCandidatesTool: ToolConfig<
description: 'List of candidates',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Candidate UUID' },
name: { type: 'string', description: 'Full name' },
primaryEmailAddress: {
type: 'object',
description: 'Primary email contact info',
optional: true,
properties: {
value: { type: 'string', description: 'Email address' },
type: { type: 'string', description: 'Contact type (Personal, Work, Other)' },
isPrimary: { type: 'boolean', description: 'Whether this is the primary email' },
},
},
primaryPhoneNumber: {
type: 'object',
description: 'Primary phone contact info',
optional: true,
properties: {
value: { type: 'string', description: 'Phone number' },
type: { type: 'string', description: 'Contact type (Personal, Work, Other)' },
isPrimary: { type: 'boolean', description: 'Whether this is the primary phone' },
},
},
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp' },
},
properties: CANDIDATE_OUTPUTS,
},
},
moreDataAvailable: {
+59 -16
View File
@@ -4,15 +4,24 @@ interface AshbyListCustomFieldsParams {
apiKey: string
}
interface AshbyCustomFieldDefinition {
id: string
title: string
isPrivate: boolean
fieldType: string
objectType: string
isArchived: boolean
isRequired: boolean
selectableValues: Array<{
label: string
value: string
isArchived: boolean
}>
}
interface AshbyListCustomFieldsResponse extends ToolResponse {
output: {
customFields: Array<{
id: string
title: string
fieldType: string
objectType: string
isArchived: boolean
}>
customFields: AshbyCustomFieldDefinition[]
}
}
@@ -54,13 +63,24 @@ export const listCustomFieldsTool: ToolConfig<
return {
success: true,
output: {
customFields: (data.results ?? []).map((f: Record<string, unknown>) => ({
id: f.id ?? null,
title: f.title ?? null,
fieldType: f.fieldType ?? null,
objectType: f.objectType ?? null,
isArchived: f.isArchived ?? false,
})),
customFields: (data.results ?? []).map(
(f: Record<string, unknown> & { selectableValues?: Array<Record<string, unknown>> }) => ({
id: (f.id as string) ?? '',
title: (f.title as string) ?? '',
isPrivate: (f.isPrivate as boolean) ?? false,
fieldType: (f.fieldType as string) ?? '',
objectType: (f.objectType as string) ?? '',
isArchived: (f.isArchived as boolean) ?? false,
isRequired: (f.isRequired as boolean) ?? false,
selectableValues: Array.isArray(f.selectableValues)
? f.selectableValues.map((v) => ({
label: (v.label as string) ?? '',
value: (v.value as string) ?? '',
isArchived: (v.isArchived as boolean) ?? false,
}))
: [],
})
),
},
}
},
@@ -74,12 +94,35 @@ export const listCustomFieldsTool: ToolConfig<
properties: {
id: { type: 'string', description: 'Custom field UUID' },
title: { type: 'string', description: 'Custom field title' },
fieldType: { type: 'string', description: 'Field type (e.g. String, Number, Boolean)' },
isPrivate: {
type: 'boolean',
description: 'Whether the custom field is private',
},
fieldType: {
type: 'string',
description:
'Field data type (MultiValueSelect, NumberRange, String, Date, ValueSelect, Number, Currency, Boolean, LongText, CompensationRange)',
},
objectType: {
type: 'string',
description: 'Object type the field applies to (e.g. Candidate, Application, Job)',
description:
'Object type the field applies to (Application, Candidate, Employee, Job, Offer, Opening, Talent_Project)',
},
isArchived: { type: 'boolean', description: 'Whether the custom field is archived' },
isRequired: { type: 'boolean', description: 'Whether a value is required' },
selectableValues: {
type: 'array',
description:
'Selectable values for MultiValueSelect fields (empty for other field types)',
items: {
type: 'object',
properties: {
label: { type: 'string', description: 'Display label' },
value: { type: 'string', description: 'Stored value' },
isArchived: { type: 'boolean', description: 'Whether archived' },
},
},
},
},
},
},
+32 -9
View File
@@ -4,14 +4,19 @@ interface AshbyListDepartmentsParams {
apiKey: string
}
interface AshbyDepartment {
id: string
name: string
externalName: string | null
isArchived: boolean
parentId: string | null
createdAt: string | null
updatedAt: string | null
}
interface AshbyListDepartmentsResponse extends ToolResponse {
output: {
departments: Array<{
id: string
name: string
isArchived: boolean
parentId: string | null
}>
departments: AshbyDepartment[]
}
}
@@ -54,10 +59,13 @@ export const listDepartmentsTool: ToolConfig<
success: true,
output: {
departments: (data.results ?? []).map((d: Record<string, unknown>) => ({
id: d.id ?? null,
name: d.name ?? null,
isArchived: d.isArchived ?? false,
id: (d.id as string) ?? '',
name: (d.name as string) ?? '',
externalName: (d.externalName as string) ?? null,
isArchived: (d.isArchived as boolean) ?? false,
parentId: (d.parentId as string) ?? null,
createdAt: (d.createdAt as string) ?? null,
updatedAt: (d.updatedAt as string) ?? null,
})),
},
}
@@ -72,12 +80,27 @@ export const listDepartmentsTool: ToolConfig<
properties: {
id: { type: 'string', description: 'Department UUID' },
name: { type: 'string', description: 'Department name' },
externalName: {
type: 'string',
description: 'Candidate-facing name used on job boards',
optional: true,
},
isArchived: { type: 'boolean', description: 'Whether the department is archived' },
parentId: {
type: 'string',
description: 'Parent department UUID',
optional: true,
},
createdAt: {
type: 'string',
description: 'ISO 8601 creation timestamp',
optional: true,
},
updatedAt: {
type: 'string',
description: 'ISO 8601 last update timestamp',
optional: true,
},
},
},
},
+153 -18
View File
@@ -1,3 +1,5 @@
import type { AshbyUserSummary } from '@/tools/ashby/types'
import { mapUserSummary, USER_SUMMARY_OUTPUT } from '@/tools/ashby/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyListInterviewSchedulesParams {
@@ -8,20 +10,81 @@ interface AshbyListInterviewSchedulesParams {
perPage?: number
}
interface AshbyInterviewEvent {
id: string
interviewId: string | null
interviewScheduleId: string | null
interviewerUserIds: string[]
createdAt: string | null
updatedAt: string | null
startTime: string | null
endTime: string | null
feedbackLink: string | null
location: string | null
meetingLink: string | null
hasSubmittedFeedback: boolean
}
interface AshbyInterviewSchedule {
id: string
status: string | null
applicationId: string
interviewStageId: string | null
scheduledBy: AshbyUserSummary | null
createdAt: string | null
updatedAt: string | null
interviewEvents: AshbyInterviewEvent[]
}
interface AshbyListInterviewSchedulesResponse extends ToolResponse {
output: {
interviewSchedules: Array<{
id: string
applicationId: string
interviewStageId: string | null
status: string | null
createdAt: string
}>
interviewSchedules: AshbyInterviewSchedule[]
moreDataAvailable: boolean
nextCursor: string | null
}
}
type UnknownRecord = Record<string, unknown>
function mapInterviewEvent(raw: unknown): AshbyInterviewEvent | null {
if (!raw || typeof raw !== 'object') return null
const e = raw as UnknownRecord
return {
id: (e.id as string) ?? '',
interviewId: (e.interviewId as string) ?? null,
interviewScheduleId: (e.interviewScheduleId as string) ?? null,
interviewerUserIds: Array.isArray(e.interviewerUserIds)
? (e.interviewerUserIds as string[])
: [],
createdAt: (e.createdAt as string) ?? null,
updatedAt: (e.updatedAt as string) ?? null,
startTime: (e.startTime as string) ?? null,
endTime: (e.endTime as string) ?? null,
feedbackLink: (e.feedbackLink as string) ?? null,
location: (e.location as string) ?? null,
meetingLink: (e.meetingLink as string) ?? null,
hasSubmittedFeedback: (e.hasSubmittedFeedback as boolean) ?? false,
}
}
function mapInterviewSchedule(raw: unknown): AshbyInterviewSchedule {
const s = (raw ?? {}) as UnknownRecord
return {
id: (s.id as string) ?? '',
status: (s.status as string) ?? null,
applicationId: (s.applicationId as string) ?? '',
interviewStageId: (s.interviewStageId as string) ?? null,
scheduledBy: mapUserSummary(s.scheduledBy),
createdAt: (s.createdAt as string) ?? null,
updatedAt: (s.updatedAt as string) ?? null,
interviewEvents: Array.isArray(s.interviewEvents)
? (s.interviewEvents as unknown[])
.map(mapInterviewEvent)
.filter((e): e is AshbyInterviewEvent => e !== null)
: [],
}
}
export const listInterviewsTool: ToolConfig<
AshbyListInterviewSchedulesParams,
AshbyListInterviewSchedulesResponse
@@ -74,8 +137,8 @@ export const listInterviewsTool: ToolConfig<
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.applicationId) body.applicationId = params.applicationId
if (params.interviewStageId) body.interviewStageId = params.interviewStageId
if (params.applicationId) body.applicationId = params.applicationId.trim()
if (params.interviewStageId) body.interviewStageId = params.interviewStageId.trim()
if (params.cursor) body.cursor = params.cursor
if (params.perPage) body.limit = params.perPage
return body
@@ -92,13 +155,7 @@ export const listInterviewsTool: ToolConfig<
return {
success: true,
output: {
interviewSchedules: (data.results ?? []).map((s: Record<string, unknown>) => ({
id: s.id ?? null,
applicationId: s.applicationId ?? null,
interviewStageId: s.interviewStageId ?? null,
status: s.status ?? null,
createdAt: s.createdAt ?? null,
})),
interviewSchedules: (data.results ?? []).map(mapInterviewSchedule),
moreDataAvailable: data.moreDataAvailable ?? false,
nextCursor: data.nextCursor ?? null,
},
@@ -113,14 +170,92 @@ export const listInterviewsTool: ToolConfig<
type: 'object',
properties: {
id: { type: 'string', description: 'Interview schedule UUID' },
status: {
type: 'string',
description:
'Schedule status (NeedsScheduling, WaitingOnCandidateBooking, Scheduled, Complete, Cancelled, OnHold, etc.)',
optional: true,
},
applicationId: { type: 'string', description: 'Associated application UUID' },
interviewStageId: {
type: 'string',
description: 'Interview stage UUID',
optional: true,
},
status: { type: 'string', description: 'Schedule status', optional: true },
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
scheduledBy: {
...USER_SUMMARY_OUTPUT,
description: 'User who scheduled the interview (null if not yet scheduled)',
},
createdAt: {
type: 'string',
description: 'ISO 8601 creation timestamp',
optional: true,
},
updatedAt: {
type: 'string',
description: 'ISO 8601 last update timestamp',
optional: true,
},
interviewEvents: {
type: 'array',
description: 'Scheduled interview events on this schedule',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Event UUID' },
interviewId: {
type: 'string',
description: 'Interview template UUID',
optional: true,
},
interviewScheduleId: {
type: 'string',
description: 'Parent schedule UUID',
optional: true,
},
interviewerUserIds: {
type: 'array',
description: 'User UUIDs of interviewers assigned to the event',
items: { type: 'string', description: 'User UUID' },
},
createdAt: {
type: 'string',
description: 'Event creation timestamp',
optional: true,
},
updatedAt: {
type: 'string',
description: 'Event last updated timestamp',
optional: true,
},
startTime: {
type: 'string',
description: 'Event start time',
optional: true,
},
endTime: { type: 'string', description: 'Event end time', optional: true },
feedbackLink: {
type: 'string',
description: 'URL to submit feedback for the event',
optional: true,
},
location: {
type: 'string',
description: 'Physical location',
optional: true,
},
meetingLink: {
type: 'string',
description: 'Virtual meeting URL',
optional: true,
},
hasSubmittedFeedback: {
type: 'boolean',
description: 'Whether any feedback has been submitted',
},
},
},
},
},
},
},
+120 -23
View File
@@ -4,18 +4,32 @@ interface AshbyListJobPostingsParams {
apiKey: string
}
interface AshbyJobPostingSummary {
id: string
title: string
jobId: string | null
departmentName: string | null
teamName: string | null
locationName: string | null
locationIds: {
primaryLocationId: string | null
secondaryLocationIds: string[]
} | null
workplaceType: string | null
employmentType: string | null
isListed: boolean
publishedDate: string | null
applicationDeadline: string | null
externalLink: string | null
applyLink: string | null
compensationTierSummary: string | null
shouldDisplayCompensationOnJobBoard: boolean
updatedAt: string | null
}
interface AshbyListJobPostingsResponse extends ToolResponse {
output: {
jobPostings: Array<{
id: string
title: string
jobId: string | null
locationName: string | null
departmentName: string | null
employmentType: string | null
isListed: boolean
publishedDate: string | null
}>
jobPostings: AshbyJobPostingSummary[]
}
}
@@ -57,16 +71,39 @@ export const listJobPostingsTool: ToolConfig<
return {
success: true,
output: {
jobPostings: (data.results ?? []).map((jp: Record<string, unknown>) => ({
id: jp.id ?? null,
title: (jp.jobTitle as string) ?? (jp.title as string) ?? null,
jobId: jp.jobId ?? null,
locationName: jp.locationName ?? null,
departmentName: jp.departmentName ?? null,
employmentType: jp.employmentType ?? null,
isListed: jp.isListed ?? false,
publishedDate: jp.publishedDate ?? null,
})),
jobPostings: (data.results ?? []).map(
(
jp: Record<string, unknown> & {
locationIds?: { primaryLocationId?: string; secondaryLocationIds?: string[] }
}
) => ({
id: (jp.id as string) ?? '',
title: (jp.title as string) ?? '',
jobId: (jp.jobId as string) ?? null,
departmentName: (jp.departmentName as string) ?? null,
teamName: (jp.teamName as string) ?? null,
locationName: (jp.locationName as string) ?? null,
locationIds: jp.locationIds
? {
primaryLocationId: jp.locationIds.primaryLocationId ?? null,
secondaryLocationIds: Array.isArray(jp.locationIds.secondaryLocationIds)
? jp.locationIds.secondaryLocationIds
: [],
}
: null,
workplaceType: (jp.workplaceType as string) ?? null,
employmentType: (jp.employmentType as string) ?? null,
isListed: (jp.isListed as boolean) ?? false,
publishedDate: (jp.publishedDate as string) ?? null,
applicationDeadline: (jp.applicationDeadline as string) ?? null,
externalLink: (jp.externalLink as string) ?? null,
applyLink: (jp.applyLink as string) ?? null,
compensationTierSummary: (jp.compensationTierSummary as string) ?? null,
shouldDisplayCompensationOnJobBoard:
(jp.shouldDisplayCompensationOnJobBoard as boolean) ?? false,
updatedAt: (jp.updatedAt as string) ?? null,
})
),
},
}
},
@@ -81,15 +118,75 @@ export const listJobPostingsTool: ToolConfig<
id: { type: 'string', description: 'Job posting UUID' },
title: { type: 'string', description: 'Job posting title' },
jobId: { type: 'string', description: 'Associated job UUID', optional: true },
locationName: { type: 'string', description: 'Location name', optional: true },
departmentName: { type: 'string', description: 'Department name', optional: true },
teamName: { type: 'string', description: 'Team name', optional: true },
locationName: {
type: 'string',
description: 'Primary location display name',
optional: true,
},
locationIds: {
type: 'object',
description: 'Primary and secondary location UUIDs',
optional: true,
properties: {
primaryLocationId: {
type: 'string',
description: 'Primary location UUID',
optional: true,
},
secondaryLocationIds: {
type: 'array',
description: 'Secondary location UUIDs',
items: { type: 'string', description: 'Location UUID' },
},
},
},
workplaceType: {
type: 'string',
description: 'Workplace type (OnSite, Remote, Hybrid)',
optional: true,
},
employmentType: {
type: 'string',
description: 'Employment type (e.g. FullTime, PartTime, Contract)',
description: 'Employment type (FullTime, PartTime, Intern, Contract, Temporary)',
optional: true,
},
isListed: { type: 'boolean', description: 'Whether the posting is publicly listed' },
publishedDate: { type: 'string', description: 'ISO 8601 published date', optional: true },
publishedDate: {
type: 'string',
description: 'ISO 8601 published date',
optional: true,
},
applicationDeadline: {
type: 'string',
description: 'ISO 8601 application deadline',
optional: true,
},
externalLink: {
type: 'string',
description: 'External link to the job posting',
optional: true,
},
applyLink: {
type: 'string',
description: 'Direct apply link for the job posting',
optional: true,
},
compensationTierSummary: {
type: 'string',
description: 'Compensation tier summary for job boards',
optional: true,
},
shouldDisplayCompensationOnJobBoard: {
type: 'boolean',
description: 'Whether compensation is shown on the job board',
},
updatedAt: {
type: 'string',
description: 'ISO 8601 last update timestamp',
optional: true,
},
},
},
},
+4 -25
View File
@@ -1,5 +1,6 @@
import type { AshbyListJobsParams, AshbyListJobsResponse } from '@/tools/ashby/types'
import { JOB_OUTPUTS, mapJob } from '@/tools/ashby/utils'
import type { ToolConfig } from '@/tools/types'
import type { AshbyListJobsParams, AshbyListJobsResponse } from './types'
export const listJobsTool: ToolConfig<AshbyListJobsParams, AshbyListJobsResponse> = {
id: 'ashby_list_jobs',
@@ -61,16 +62,7 @@ export const listJobsTool: ToolConfig<AshbyListJobsParams, AshbyListJobsResponse
return {
success: true,
output: {
jobs: (data.results ?? []).map((j: Record<string, unknown>) => ({
id: j.id ?? null,
title: j.title ?? null,
status: j.status ?? null,
employmentType: j.employmentType ?? null,
departmentId: j.departmentId ?? null,
locationId: j.locationId ?? null,
createdAt: j.createdAt ?? null,
updatedAt: j.updatedAt ?? null,
})),
jobs: (data.results ?? []).map(mapJob),
moreDataAvailable: data.moreDataAvailable ?? false,
nextCursor: data.nextCursor ?? null,
},
@@ -83,20 +75,7 @@ export const listJobsTool: ToolConfig<AshbyListJobsParams, AshbyListJobsResponse
description: 'List of jobs',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Job UUID' },
title: { type: 'string', description: 'Job title' },
status: { type: 'string', description: 'Job status (Open, Closed, Archived, Draft)' },
employmentType: {
type: 'string',
description: 'Employment type (FullTime, PartTime, Intern, Contract, Temporary)',
optional: true,
},
departmentId: { type: 'string', description: 'Department UUID', optional: true },
locationId: { type: 'string', description: 'Location UUID', optional: true },
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp' },
},
properties: JOB_OUTPUTS,
},
},
moreDataAvailable: {
+80 -36
View File
@@ -4,19 +4,27 @@ interface AshbyListLocationsParams {
apiKey: string
}
interface AshbyLocation {
id: string
name: string
externalName: string | null
isArchived: boolean
isRemote: boolean
workplaceType: string | null
parentLocationId: string | null
type: string | null
address: {
addressCountry: string | null
addressRegion: string | null
addressLocality: string | null
postalCode: string | null
streetAddress: string | null
} | null
}
interface AshbyListLocationsResponse extends ToolResponse {
output: {
locations: Array<{
id: string
name: string
isArchived: boolean
isRemote: boolean
address: {
city: string | null
region: string | null
country: string | null
} | null
}>
locations: AshbyLocation[]
}
}
@@ -58,27 +66,30 @@ export const listLocationsTool: ToolConfig<AshbyListLocationsParams, AshbyListLo
locations: (data.results ?? []).map(
(
l: Record<string, unknown> & {
address?: {
postalAddress?: {
addressLocality?: string
addressRegion?: string
addressCountry?: string
}
}
address?: { postalAddress?: Record<string, unknown> }
}
) => ({
id: l.id ?? null,
name: l.name ?? null,
isArchived: l.isArchived ?? false,
isRemote: l.isRemote ?? false,
address: l.address?.postalAddress
? {
city: l.address.postalAddress.addressLocality ?? null,
region: l.address.postalAddress.addressRegion ?? null,
country: l.address.postalAddress.addressCountry ?? null,
}
: null,
})
) => {
const pa = l.address?.postalAddress
return {
id: (l.id as string) ?? '',
name: (l.name as string) ?? '',
externalName: (l.externalName as string) ?? null,
isArchived: (l.isArchived as boolean) ?? false,
isRemote: (l.isRemote as boolean) ?? false,
workplaceType: (l.workplaceType as string) ?? null,
parentLocationId: (l.parentLocationId as string) ?? null,
type: (l.type as string) ?? null,
address: pa
? {
addressCountry: (pa.addressCountry as string) ?? null,
addressRegion: (pa.addressRegion as string) ?? null,
addressLocality: (pa.addressLocality as string) ?? null,
postalCode: (pa.postalCode as string) ?? null,
streetAddress: (pa.streetAddress as string) ?? null,
}
: null,
}
}
),
},
}
@@ -93,16 +104,49 @@ export const listLocationsTool: ToolConfig<AshbyListLocationsParams, AshbyListLo
properties: {
id: { type: 'string', description: 'Location UUID' },
name: { type: 'string', description: 'Location name' },
externalName: {
type: 'string',
description: 'Candidate-facing name used on job boards',
optional: true,
},
isArchived: { type: 'boolean', description: 'Whether the location is archived' },
isRemote: { type: 'boolean', description: 'Whether this is a remote location' },
isRemote: {
type: 'boolean',
description: 'Whether the location is remote (use workplaceType instead)',
},
workplaceType: {
type: 'string',
description: 'Workplace type (OnSite, Hybrid, Remote)',
optional: true,
},
parentLocationId: {
type: 'string',
description: 'Parent location UUID',
optional: true,
},
type: {
type: 'string',
description: 'Location component type (Location, LocationHierarchy)',
optional: true,
},
address: {
type: 'object',
description: 'Location address',
description: 'Location postal address',
optional: true,
properties: {
city: { type: 'string', description: 'City', optional: true },
region: { type: 'string', description: 'State or region', optional: true },
country: { type: 'string', description: 'Country', optional: true },
addressCountry: { type: 'string', description: 'Country', optional: true },
addressRegion: {
type: 'string',
description: 'State or region',
optional: true,
},
addressLocality: {
type: 'string',
description: 'City or locality',
optional: true,
},
postalCode: { type: 'string', description: 'Postal code', optional: true },
streetAddress: { type: 'string', description: 'Street address', optional: true },
},
},
},
+18 -15
View File
@@ -11,14 +11,15 @@ interface AshbyListNotesResponse extends ToolResponse {
output: {
notes: Array<{
id: string
content: string
content: string | null
isPrivate: boolean
author: {
id: string
firstName: string
lastName: string
email: string
firstName: string | null
lastName: string | null
email: string | null
} | null
createdAt: string
createdAt: string | null
}>
moreDataAvailable: boolean
nextCursor: string | null
@@ -67,7 +68,7 @@ export const listNotesTool: ToolConfig<AshbyListNotesParams, AshbyListNotesRespo
}),
body: (params) => {
const body: Record<string, unknown> = {
candidateId: params.candidateId,
candidateId: params.candidateId.trim(),
}
if (params.cursor) body.cursor = params.cursor
if (params.perPage) body.limit = params.perPage
@@ -91,17 +92,18 @@ export const listNotesTool: ToolConfig<AshbyListNotesParams, AshbyListNotesRespo
author?: { id?: string; firstName?: string; lastName?: string; email?: string }
}
) => ({
id: n.id ?? null,
content: n.content ?? null,
id: (n.id as string) ?? '',
content: (n.content as string) ?? null,
isPrivate: (n.isPrivate as boolean) ?? false,
author: n.author
? {
id: n.author.id ?? null,
id: n.author.id ?? '',
firstName: n.author.firstName ?? null,
lastName: n.author.lastName ?? null,
email: n.author.email ?? null,
}
: null,
createdAt: n.createdAt ?? null,
createdAt: (n.createdAt as string) ?? null,
})
),
moreDataAvailable: data.moreDataAvailable ?? false,
@@ -118,19 +120,20 @@ export const listNotesTool: ToolConfig<AshbyListNotesParams, AshbyListNotesRespo
type: 'object',
properties: {
id: { type: 'string', description: 'Note UUID' },
content: { type: 'string', description: 'Note content' },
content: { type: 'string', description: 'Note content', optional: true },
isPrivate: { type: 'boolean', description: 'Whether the note is private' },
author: {
type: 'object',
description: 'Note author',
optional: true,
properties: {
id: { type: 'string', description: 'Author user UUID' },
firstName: { type: 'string', description: 'First name' },
lastName: { type: 'string', description: 'Last name' },
email: { type: 'string', description: 'Email address' },
firstName: { type: 'string', description: 'First name', optional: true },
lastName: { type: 'string', description: 'Last name', optional: true },
email: { type: 'string', description: 'Email address', optional: true },
},
},
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp', optional: true },
},
},
},
+5 -64
View File
@@ -1,3 +1,5 @@
import type { AshbyOffer } from '@/tools/ashby/types'
import { mapOffer, OFFER_OUTPUTS } from '@/tools/ashby/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyListOffersParams {
@@ -8,19 +10,7 @@ interface AshbyListOffersParams {
interface AshbyListOffersResponse extends ToolResponse {
output: {
offers: Array<{
id: string
offerStatus: string
acceptanceStatus: string | null
applicationId: string | null
startDate: string | null
salary: {
currencyCode: string
value: number
} | null
openingId: string | null
createdAt: string | null
}>
offers: AshbyOffer[]
moreDataAvailable: boolean
nextCursor: string | null
}
@@ -78,35 +68,7 @@ export const listOffersTool: ToolConfig<AshbyListOffersParams, AshbyListOffersRe
return {
success: true,
output: {
offers: (data.results ?? []).map(
(
o: Record<string, unknown> & {
latestVersion?: {
startDate?: string
salary?: { currencyCode?: string; value?: number }
openingId?: string
createdAt?: string
}
}
) => {
const v = o.latestVersion
return {
id: o.id ?? null,
offerStatus: o.offerStatus ?? null,
acceptanceStatus: o.acceptanceStatus ?? null,
applicationId: o.applicationId ?? null,
startDate: v?.startDate ?? null,
salary: v?.salary
? {
currencyCode: v.salary.currencyCode ?? null,
value: v.salary.value ?? null,
}
: null,
openingId: v?.openingId ?? null,
createdAt: v?.createdAt ?? null,
}
}
),
offers: (data.results ?? []).map(mapOffer),
moreDataAvailable: data.moreDataAvailable ?? false,
nextCursor: data.nextCursor ?? null,
},
@@ -119,28 +81,7 @@ export const listOffersTool: ToolConfig<AshbyListOffersParams, AshbyListOffersRe
description: 'List of offers',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Offer UUID' },
offerStatus: { type: 'string', description: 'Offer status' },
acceptanceStatus: { type: 'string', description: 'Acceptance status', optional: true },
applicationId: {
type: 'string',
description: 'Associated application UUID',
optional: true,
},
startDate: { type: 'string', description: 'Offer start date', optional: true },
salary: {
type: 'object',
description: 'Salary details',
optional: true,
properties: {
currencyCode: { type: 'string', description: 'ISO 4217 currency code' },
value: { type: 'number', description: 'Salary amount' },
},
},
openingId: { type: 'string', description: 'Associated opening UUID', optional: true },
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp', optional: true },
},
properties: OFFER_OUTPUTS,
},
},
moreDataAvailable: {
+5 -32
View File
@@ -1,3 +1,5 @@
import type { AshbyOpening } from '@/tools/ashby/types'
import { mapOpenings, OPENINGS_OUTPUT } from '@/tools/ashby/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyListOpeningsParams {
@@ -8,13 +10,7 @@ interface AshbyListOpeningsParams {
interface AshbyListOpeningsResponse extends ToolResponse {
output: {
openings: Array<{
id: string
openingState: string | null
isArchived: boolean
openedAt: string | null
closedAt: string | null
}>
openings: AshbyOpening[]
moreDataAvailable: boolean
nextCursor: string | null
}
@@ -72,13 +68,7 @@ export const listOpeningsTool: ToolConfig<AshbyListOpeningsParams, AshbyListOpen
return {
success: true,
output: {
openings: (data.results ?? []).map((o: Record<string, unknown>) => ({
id: o.id ?? null,
openingState: o.openingState ?? null,
isArchived: o.isArchived ?? false,
openedAt: o.openedAt ?? null,
closedAt: o.closedAt ?? null,
})),
openings: mapOpenings(data.results),
moreDataAvailable: data.moreDataAvailable ?? false,
nextCursor: data.nextCursor ?? null,
},
@@ -86,24 +76,7 @@ export const listOpeningsTool: ToolConfig<AshbyListOpeningsParams, AshbyListOpen
},
outputs: {
openings: {
type: 'array',
description: 'List of openings',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Opening UUID' },
openingState: {
type: 'string',
description: 'Opening state (Approved, Closed, Draft, Filled, Open)',
optional: true,
},
isArchived: { type: 'boolean', description: 'Whether the opening is archived' },
openedAt: { type: 'string', description: 'ISO 8601 opened timestamp', optional: true },
closedAt: { type: 'string', description: 'ISO 8601 closed timestamp', optional: true },
},
},
},
openings: OPENINGS_OUTPUT,
moreDataAvailable: {
type: 'boolean',
description: 'Whether more pages of results exist',
+29 -10
View File
@@ -1,3 +1,4 @@
import type { AshbySourceSummary } from '@/tools/ashby/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyListSourcesParams {
@@ -6,11 +7,7 @@ interface AshbyListSourcesParams {
interface AshbyListSourcesResponse extends ToolResponse {
output: {
sources: Array<{
id: string
title: string
isArchived: boolean
}>
sources: AshbySourceSummary[]
}
}
@@ -49,11 +46,23 @@ export const listSourcesTool: ToolConfig<AshbyListSourcesParams, AshbyListSource
return {
success: true,
output: {
sources: (data.results ?? []).map((s: Record<string, unknown>) => ({
id: s.id ?? null,
title: s.title ?? null,
isArchived: s.isArchived ?? false,
})),
sources: (data.results ?? []).map(
(s: Record<string, unknown> & { sourceType?: Record<string, unknown> }) => {
const sourceType = s.sourceType
return {
id: (s.id as string) ?? '',
title: (s.title as string) ?? '',
isArchived: (s.isArchived as boolean) ?? false,
sourceType: sourceType
? {
id: (sourceType.id as string) ?? '',
title: (sourceType.title as string) ?? '',
isArchived: (sourceType.isArchived as boolean) ?? false,
}
: null,
}
}
),
},
}
},
@@ -68,6 +77,16 @@ export const listSourcesTool: ToolConfig<AshbyListSourcesParams, AshbyListSource
id: { type: 'string', description: 'Source UUID' },
title: { type: 'string', description: 'Source title' },
isArchived: { type: 'boolean', description: 'Whether the source is archived' },
sourceType: {
type: 'object',
description: 'Source type grouping',
optional: true,
properties: {
id: { type: 'string', description: 'Source type UUID' },
title: { type: 'string', description: 'Source type title' },
isArchived: { type: 'boolean', description: 'Whether archived' },
},
},
},
},
},
+7 -29
View File
@@ -1,3 +1,5 @@
import type { AshbyUserSummary } from '@/tools/ashby/types'
import { mapUserSummary, USER_SUMMARY_OUTPUT } from '@/tools/ashby/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyListUsersParams {
@@ -8,14 +10,7 @@ interface AshbyListUsersParams {
interface AshbyListUsersResponse extends ToolResponse {
output: {
users: Array<{
id: string
firstName: string
lastName: string
email: string
isEnabled: boolean
globalRole: string | null
}>
users: AshbyUserSummary[]
moreDataAvailable: boolean
nextCursor: string | null
}
@@ -73,14 +68,9 @@ export const listUsersTool: ToolConfig<AshbyListUsersParams, AshbyListUsersRespo
return {
success: true,
output: {
users: (data.results ?? []).map((u: Record<string, unknown>) => ({
id: u.id ?? null,
firstName: u.firstName ?? null,
lastName: u.lastName ?? null,
email: u.email ?? null,
isEnabled: u.isEnabled ?? false,
globalRole: u.globalRole ?? null,
})),
users: (data.results ?? [])
.map(mapUserSummary)
.filter((u: AshbyUserSummary | null): u is AshbyUserSummary => u !== null),
moreDataAvailable: data.moreDataAvailable ?? false,
nextCursor: data.nextCursor ?? null,
},
@@ -93,19 +83,7 @@ export const listUsersTool: ToolConfig<AshbyListUsersParams, AshbyListUsersRespo
description: 'List of users',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'User UUID' },
firstName: { type: 'string', description: 'First name' },
lastName: { type: 'string', description: 'Last name' },
email: { type: 'string', description: 'Email address' },
isEnabled: { type: 'boolean', description: 'Whether the user account is enabled' },
globalRole: {
type: 'string',
description:
'User role (Organization Admin, Elevated Access, Limited Access, External Recruiter)',
optional: true,
},
},
properties: USER_SUMMARY_OUTPUT.properties,
},
},
moreDataAvailable: {
+8 -12
View File
@@ -1,3 +1,5 @@
import type { AshbyCandidate } from '@/tools/ashby/types'
import { CANDIDATE_OUTPUTS, mapCandidate } from '@/tools/ashby/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface AshbyRemoveCandidateTagParams {
@@ -7,9 +9,7 @@ interface AshbyRemoveCandidateTagParams {
}
interface AshbyRemoveCandidateTagResponse extends ToolResponse {
output: {
success: boolean
}
output: AshbyCandidate
}
export const removeCandidateTagTool: ToolConfig<
@@ -18,7 +18,7 @@ export const removeCandidateTagTool: ToolConfig<
> = {
id: 'ashby_remove_candidate_tag',
name: 'Ashby Remove Candidate Tag',
description: 'Removes a tag from a candidate in Ashby.',
description: 'Removes a tag from a candidate in Ashby and returns the updated candidate.',
version: '1.0.0',
params: {
@@ -50,8 +50,8 @@ export const removeCandidateTagTool: ToolConfig<
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
}),
body: (params) => ({
candidateId: params.candidateId,
tagId: params.tagId,
candidateId: params.candidateId.trim(),
tagId: params.tagId.trim(),
}),
},
@@ -64,13 +64,9 @@ export const removeCandidateTagTool: ToolConfig<
return {
success: true,
output: {
success: true,
},
output: mapCandidate(data.results),
}
},
outputs: {
success: { type: 'boolean', description: 'Whether the tag was successfully removed' },
},
outputs: CANDIDATE_OUTPUTS,
}
+7 -54
View File
@@ -1,5 +1,9 @@
import type {
AshbySearchCandidatesParams,
AshbySearchCandidatesResponse,
} from '@/tools/ashby/types'
import { CANDIDATE_OUTPUTS, mapCandidate } from '@/tools/ashby/utils'
import type { ToolConfig } from '@/tools/types'
import type { AshbySearchCandidatesParams, AshbySearchCandidatesResponse } from './types'
export const searchCandidatesTool: ToolConfig<
AshbySearchCandidatesParams,
@@ -57,33 +61,7 @@ export const searchCandidatesTool: ToolConfig<
return {
success: true,
output: {
candidates: (data.results ?? []).map(
(
c: Record<string, unknown> & {
primaryEmailAddress?: { value?: string; type?: string; isPrimary?: boolean }
primaryPhoneNumber?: { value?: string; type?: string; isPrimary?: boolean }
}
) => ({
id: c.id ?? null,
name: c.name ?? null,
primaryEmailAddress: c.primaryEmailAddress
? {
value: c.primaryEmailAddress.value ?? '',
type: c.primaryEmailAddress.type ?? 'Other',
isPrimary: c.primaryEmailAddress.isPrimary ?? true,
}
: null,
primaryPhoneNumber: c.primaryPhoneNumber
? {
value: c.primaryPhoneNumber.value ?? '',
type: c.primaryPhoneNumber.type ?? 'Other',
isPrimary: c.primaryPhoneNumber.isPrimary ?? true,
}
: null,
createdAt: c.createdAt ?? null,
updatedAt: c.updatedAt ?? null,
})
),
candidates: (data.results ?? []).map(mapCandidate),
},
}
},
@@ -94,32 +72,7 @@ export const searchCandidatesTool: ToolConfig<
description: 'Matching candidates (max 100 results)',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Candidate UUID' },
name: { type: 'string', description: 'Full name' },
primaryEmailAddress: {
type: 'object',
description: 'Primary email contact info',
optional: true,
properties: {
value: { type: 'string', description: 'Email address' },
type: { type: 'string', description: 'Contact type (Personal, Work, Other)' },
isPrimary: { type: 'boolean', description: 'Whether this is the primary email' },
},
},
primaryPhoneNumber: {
type: 'object',
description: 'Primary phone contact info',
optional: true,
properties: {
value: { type: 'string', description: 'Phone number' },
type: { type: 'string', description: 'Contact type (Personal, Work, Other)' },
isPrimary: { type: 'boolean', description: 'Whether this is the primary phone' },
},
},
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp' },
},
properties: CANDIDATE_OUTPUTS,
},
},
},
+261 -87
View File
@@ -10,6 +10,87 @@ export interface AshbyContactInfo {
isPrimary: boolean
}
export interface AshbySocialLink {
type: string
url: string
}
export interface AshbyTag {
id: string
title: string
isArchived: boolean
}
export interface AshbyFileHandle {
id: string
name: string
handle: string
}
export interface AshbyCustomField {
id: string | null
title: string
isPrivate: boolean
valueLabel: string | null
value: unknown
}
export interface AshbyUserSummary {
id: string
firstName: string | null
lastName: string | null
email: string | null
globalRole: string | null
isEnabled: boolean
updatedAt: string | null
managerId: string | null
}
export interface AshbySourceSummary {
id: string
title: string
isArchived: boolean
sourceType: {
id: string
title: string
isArchived: boolean
} | null
}
export interface AshbyCandidateLocation {
id: string | null
locationSummary: string | null
locationComponents: Array<{ type: string; name: string }>
}
export interface AshbyCandidate {
id: string
name: string
primaryEmailAddress: AshbyContactInfo | null
primaryPhoneNumber: AshbyContactInfo | null
emailAddresses: AshbyContactInfo[]
phoneNumbers: AshbyContactInfo[]
socialLinks: AshbySocialLink[]
linkedInUrl: string | null
githubUrl: string | null
profileUrl: string | null
position: string | null
company: string | null
school: string | null
timezone: string | null
location: AshbyCandidateLocation | null
tags: AshbyTag[]
applicationIds: string[]
customFields: AshbyCustomField[]
resumeFileHandle: AshbyFileHandle | null
fileHandles: AshbyFileHandle[]
source: AshbySourceSummary | null
creditedToUser: AshbyUserSummary | null
fraudStatus: string | null
createdAt: string | null
updatedAt: string | null
}
export interface AshbyListCandidatesParams extends AshbyBaseParams {
cursor?: string
perPage?: number
@@ -21,7 +102,7 @@ export interface AshbyGetCandidateParams extends AshbyBaseParams {
export interface AshbyCreateCandidateParams extends AshbyBaseParams {
name: string
email: string
email?: string
phoneNumber?: string
linkedInUrl?: string
githubUrl?: string
@@ -55,130 +136,223 @@ export interface AshbyListApplicationsParams extends AshbyBaseParams {
perPage?: number
status?: string
jobId?: string
candidateId?: string
createdAfter?: string
}
export interface AshbyListCandidatesResponse extends ToolResponse {
output: {
candidates: Array<{
id: string
name: string
primaryEmailAddress: AshbyContactInfo | null
primaryPhoneNumber: AshbyContactInfo | null
createdAt: string
updatedAt: string
}>
candidates: AshbyCandidate[]
moreDataAvailable: boolean
nextCursor: string | null
}
}
export interface AshbyGetCandidateResponse extends ToolResponse {
output: {
id: string
name: string
primaryEmailAddress: AshbyContactInfo | null
primaryPhoneNumber: AshbyContactInfo | null
profileUrl: string | null
position: string | null
company: string | null
linkedInUrl: string | null
githubUrl: string | null
tags: Array<{ id: string; title: string }>
applicationIds: string[]
createdAt: string
updatedAt: string
}
output: AshbyCandidate
}
export interface AshbyCreateCandidateResponse extends ToolResponse {
output: {
id: string
name: string
primaryEmailAddress: AshbyContactInfo | null
primaryPhoneNumber: AshbyContactInfo | null
createdAt: string
}
output: AshbyCandidate
}
export interface AshbySearchCandidatesResponse extends ToolResponse {
output: {
candidates: Array<{
id: string
name: string
primaryEmailAddress: AshbyContactInfo | null
primaryPhoneNumber: AshbyContactInfo | null
createdAt: string
updatedAt: string
}>
candidates: AshbyCandidate[]
}
}
export interface AshbyJobLocation {
id: string | null
name: string | null
externalName: string | null
isArchived: boolean
isRemote: boolean
workplaceType: string | null
parentLocationId: string | null
type: string | null
address: {
addressCountry: string | null
addressRegion: string | null
addressLocality: string | null
postalCode: string | null
streetAddress: string | null
} | null
}
export interface AshbyHiringTeamMember {
email: string | null
firstName: string | null
lastName: string | null
role: string | null
userId: string | null
}
export interface AshbyOpeningLatestVersion {
id: string | null
identifier: string | null
description: string | null
authorId: string | null
createdAt: string | null
teamId: string | null
jobIds: string[]
targetHireDate: string | null
targetStartDate: string | null
isBackfill: boolean
employmentType: string | null
locationIds: string[]
hiringTeam: AshbyHiringTeamMember[]
customFields: AshbyCustomField[]
}
export interface AshbyOpening {
id: string
openedAt: string | null
closedAt: string | null
isArchived: boolean
archivedAt: string | null
closeReasonId: string | null
openingState: string | null
latestVersion: AshbyOpeningLatestVersion | null
}
export interface AshbyJobCompensationTier {
id: string | null
title: string | null
additionalInformation: string | null
tierSummary: string | null
}
export interface AshbyJob {
id: string
title: string
confidential: boolean
status: string | null
employmentType: string | null
locationId: string | null
departmentId: string | null
defaultInterviewPlanId: string | null
interviewPlanIds: string[]
customFields: AshbyCustomField[]
jobPostingIds: string[]
customRequisitionId: string | null
brandId: string | null
hiringTeam: AshbyHiringTeamMember[]
author: AshbyUserSummary | null
createdAt: string | null
updatedAt: string | null
openedAt: string | null
closedAt: string | null
location: AshbyJobLocation | null
openings: AshbyOpening[]
compensation: {
compensationTiers: AshbyJobCompensationTier[]
} | null
}
export interface AshbyListJobsResponse extends ToolResponse {
output: {
jobs: Array<{
id: string
title: string
status: string
employmentType: string | null
departmentId: string | null
locationId: string | null
createdAt: string
updatedAt: string
}>
jobs: AshbyJob[]
moreDataAvailable: boolean
nextCursor: string | null
}
}
export interface AshbyGetJobResponse extends ToolResponse {
output: {
output: AshbyJob
}
export interface AshbyNote {
id: string
createdAt: string | null
isPrivate: boolean
content: string | null
author: {
id: string
title: string
status: string
employmentType: string | null
departmentId: string | null
locationId: string | null
descriptionPlain: string | null
isArchived: boolean
createdAt: string
updatedAt: string
}
firstName: string | null
lastName: string | null
email: string | null
} | null
}
export interface AshbyCreateNoteResponse extends ToolResponse {
output: {
noteId: string
}
output: AshbyNote
}
export interface AshbyApplicationCandidate {
id: string
name: string | null
primaryEmailAddress: AshbyContactInfo | null
primaryPhoneNumber: AshbyContactInfo | null
}
export interface AshbyApplicationJob {
id: string
title: string | null
locationId: string | null
departmentId: string | null
}
export interface AshbyApplicationStage {
id: string
title: string | null
type: string | null
orderInInterviewPlan: number | null
interviewStageGroupId: string | null
interviewPlanId: string | null
}
export interface AshbyApplicationArchiveReason {
id: string
text: string | null
reasonType: string | null
isArchived: boolean
customFields: AshbyCustomField[]
}
export interface AshbyApplication {
id: string
createdAt: string | null
updatedAt: string | null
status: string
customFields: AshbyCustomField[]
candidate: AshbyApplicationCandidate
currentInterviewStage: AshbyApplicationStage | null
source: AshbySourceSummary | null
archiveReason: AshbyApplicationArchiveReason | null
archivedAt: string | null
job: AshbyApplicationJob
creditedToUser: AshbyUserSummary | null
hiringTeam: AshbyHiringTeamMember[]
appliedViaJobPostingId: string | null
submitterClientIp: string | null
submitterUserAgent: string | null
}
export interface AshbyListApplicationsResponse extends ToolResponse {
output: {
applications: Array<{
id: string
status: string
candidate: {
id: string
name: string
}
job: {
id: string
title: string
}
currentInterviewStage: {
id: string
title: string
type: string
} | null
source: {
id: string
title: string
} | null
createdAt: string
updatedAt: string
}>
applications: AshbyApplication[]
moreDataAvailable: boolean
nextCursor: string | null
}
}
export interface AshbyOfferVersion {
id: string | null
startDate: string | null
salary: { currencyCode: string | null; value: number | null } | null
createdAt: string | null
openingId: string | null
customFields: AshbyCustomField[]
fileHandles: AshbyFileHandle[]
author: AshbyUserSummary | null
approvalStatus: string | null
}
export interface AshbyOffer {
id: string
decidedAt: string | null
applicationId: string | null
acceptanceStatus: string | null
offerStatus: string | null
latestVersion: AshbyOfferVersion | null
}
+6 -88
View File
@@ -1,5 +1,6 @@
import type { AshbyGetCandidateResponse } from '@/tools/ashby/types'
import { CANDIDATE_OUTPUTS, mapCandidate } from '@/tools/ashby/utils'
import type { ToolConfig } from '@/tools/types'
import type { AshbyGetCandidateResponse } from './types'
interface AshbyUpdateCandidateParams {
apiKey: string
@@ -88,7 +89,7 @@ export const updateCandidateTool: ToolConfig<
}),
body: (params) => {
const body: Record<string, unknown> = {
candidateId: params.candidateId,
candidateId: params.candidateId.trim(),
}
if (params.name) body.name = params.name
if (params.email) body.email = params.email
@@ -96,7 +97,7 @@ export const updateCandidateTool: ToolConfig<
if (params.linkedInUrl) body.linkedInUrl = params.linkedInUrl
if (params.githubUrl) body.githubUrl = params.githubUrl
if (params.websiteUrl) body.websiteUrl = params.websiteUrl
if (params.sourceId) body.sourceId = params.sourceId
if (params.sourceId) body.sourceId = params.sourceId.trim()
return body
},
},
@@ -108,94 +109,11 @@ export const updateCandidateTool: ToolConfig<
throw new Error(data.errorInfo?.message || 'Failed to update candidate')
}
const r = data.results
return {
success: true,
output: {
id: r.id ?? null,
name: r.name ?? null,
primaryEmailAddress: r.primaryEmailAddress
? {
value: r.primaryEmailAddress.value ?? '',
type: r.primaryEmailAddress.type ?? 'Other',
isPrimary: r.primaryEmailAddress.isPrimary ?? true,
}
: null,
primaryPhoneNumber: r.primaryPhoneNumber
? {
value: r.primaryPhoneNumber.value ?? '',
type: r.primaryPhoneNumber.type ?? 'Other',
isPrimary: r.primaryPhoneNumber.isPrimary ?? true,
}
: null,
profileUrl: r.profileUrl ?? null,
position: r.position ?? null,
company: r.company ?? null,
linkedInUrl:
(r.socialLinks ?? []).find((l: { type: string }) => l.type === 'LinkedIn')?.url ?? null,
githubUrl:
(r.socialLinks ?? []).find((l: { type: string }) => l.type === 'GitHub')?.url ?? null,
tags: (r.tags ?? []).map((t: { id: string; title: string }) => ({
id: t.id,
title: t.title,
})),
applicationIds: r.applicationIds ?? [],
createdAt: r.createdAt ?? null,
updatedAt: r.updatedAt ?? null,
},
output: mapCandidate(data.results),
}
},
outputs: {
id: { type: 'string', description: 'Candidate UUID' },
name: { type: 'string', description: 'Full name' },
primaryEmailAddress: {
type: 'object',
description: 'Primary email contact info',
optional: true,
properties: {
value: { type: 'string', description: 'Email address' },
type: { type: 'string', description: 'Contact type (Personal, Work, Other)' },
isPrimary: { type: 'boolean', description: 'Whether this is the primary email' },
},
},
primaryPhoneNumber: {
type: 'object',
description: 'Primary phone contact info',
optional: true,
properties: {
value: { type: 'string', description: 'Phone number' },
type: { type: 'string', description: 'Contact type (Personal, Work, Other)' },
isPrimary: { type: 'boolean', description: 'Whether this is the primary phone' },
},
},
profileUrl: {
type: 'string',
description: 'URL to the candidate Ashby profile',
optional: true,
},
position: { type: 'string', description: 'Current position or title', optional: true },
company: { type: 'string', description: 'Current company', optional: true },
linkedInUrl: { type: 'string', description: 'LinkedIn profile URL', optional: true },
githubUrl: { type: 'string', description: 'GitHub profile URL', optional: true },
tags: {
type: 'array',
description: 'Tags applied to the candidate',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Tag UUID' },
title: { type: 'string', description: 'Tag title' },
},
},
},
applicationIds: {
type: 'array',
description: 'IDs of associated applications',
items: { type: 'string', description: 'Application UUID' },
},
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp' },
},
outputs: CANDIDATE_OUTPUTS,
}
+848
View File
@@ -0,0 +1,848 @@
import type {
AshbyApplication,
AshbyCandidate,
AshbyContactInfo,
AshbyCustomField,
AshbyFileHandle,
AshbyHiringTeamMember,
AshbyJob,
AshbyOffer,
AshbyOfferVersion,
AshbyOpening,
AshbyOpeningLatestVersion,
AshbySourceSummary,
AshbyUserSummary,
} from '@/tools/ashby/types'
import type { OutputProperty } from '@/tools/types'
type Unknown = Record<string, unknown>
function mapContact(raw: unknown): AshbyContactInfo | null {
if (!raw || typeof raw !== 'object') return null
const c = raw as Unknown
return {
value: (c.value as string) ?? '',
type: (c.type as string) ?? 'Other',
isPrimary: (c.isPrimary as boolean) ?? true,
}
}
function mapContactArray(raw: unknown): AshbyContactInfo[] {
if (!Array.isArray(raw)) return []
return raw.map((c) => mapContact(c)).filter((c): c is AshbyContactInfo => c !== null)
}
function mapCustomFields(raw: unknown): AshbyCustomField[] {
if (!Array.isArray(raw)) return []
return raw.map((f) => {
const cf = f as Unknown
return {
id: (cf.id as string) ?? null,
title: (cf.title as string) ?? '',
isPrivate: (cf.isPrivate as boolean) ?? false,
valueLabel: (cf.valueLabel as string) ?? null,
value: cf.value ?? null,
}
})
}
function mapFileHandle(raw: unknown): AshbyFileHandle | null {
if (!raw || typeof raw !== 'object') return null
const f = raw as Unknown
return {
id: (f.id as string) ?? '',
name: (f.name as string) ?? '',
handle: (f.handle as string) ?? '',
}
}
function mapFileHandles(raw: unknown): AshbyFileHandle[] {
if (!Array.isArray(raw)) return []
return raw.map((f) => mapFileHandle(f)).filter((f): f is AshbyFileHandle => f !== null)
}
export function mapUserSummary(raw: unknown): AshbyUserSummary | null {
if (!raw || typeof raw !== 'object') return null
const u = raw as Unknown
return {
id: (u.id as string) ?? '',
firstName: (u.firstName as string) ?? null,
lastName: (u.lastName as string) ?? null,
email: (u.email as string) ?? null,
globalRole: (u.globalRole as string) ?? null,
isEnabled: (u.isEnabled as boolean) ?? false,
updatedAt: (u.updatedAt as string) ?? null,
managerId: (u.managerId as string) ?? null,
}
}
function mapSource(raw: unknown): AshbySourceSummary | null {
if (!raw || typeof raw !== 'object') return null
const s = raw as Unknown
const sourceType = s.sourceType as Unknown | undefined
return {
id: (s.id as string) ?? '',
title: (s.title as string) ?? '',
isArchived: (s.isArchived as boolean) ?? false,
sourceType: sourceType
? {
id: (sourceType.id as string) ?? '',
title: (sourceType.title as string) ?? '',
isArchived: (sourceType.isArchived as boolean) ?? false,
}
: null,
}
}
export function mapCandidate(raw: unknown): AshbyCandidate {
const c = (raw ?? {}) as Unknown
const socialLinks = Array.isArray(c.socialLinks)
? (c.socialLinks as Array<{ type?: string; url?: string }>)
: []
const location = c.location as Unknown | undefined
const locationComponents = Array.isArray(location?.locationComponents)
? (location?.locationComponents as Array<{ type?: string; name?: string }>)
: []
return {
id: (c.id as string) ?? '',
name: (c.name as string) ?? '',
primaryEmailAddress: mapContact(c.primaryEmailAddress),
primaryPhoneNumber: mapContact(c.primaryPhoneNumber),
emailAddresses: mapContactArray(c.emailAddresses),
phoneNumbers: mapContactArray(c.phoneNumbers),
socialLinks: socialLinks.map((l) => ({
type: l.type ?? '',
url: l.url ?? '',
})),
linkedInUrl: socialLinks.find((l) => l.type === 'LinkedIn')?.url ?? null,
githubUrl: socialLinks.find((l) => l.type === 'GitHub')?.url ?? null,
profileUrl: (c.profileUrl as string) ?? null,
position: (c.position as string) ?? null,
company: (c.company as string) ?? null,
school: (c.school as string) ?? null,
timezone: (c.timezone as string) ?? null,
location: location
? {
id: (location.id as string) ?? null,
locationSummary: (location.locationSummary as string) ?? null,
locationComponents: locationComponents.map((lc) => ({
type: lc.type ?? '',
name: lc.name ?? '',
})),
}
: null,
tags: Array.isArray(c.tags)
? (c.tags as Array<{ id?: string; title?: string; isArchived?: boolean }>).map((t) => ({
id: t.id ?? '',
title: t.title ?? '',
isArchived: t.isArchived ?? false,
}))
: [],
applicationIds: Array.isArray(c.applicationIds) ? (c.applicationIds as string[]) : [],
customFields: mapCustomFields(c.customFields),
resumeFileHandle: mapFileHandle(c.resumeFileHandle),
fileHandles: mapFileHandles(c.fileHandles),
source: mapSource(c.source),
creditedToUser: mapUserSummary(c.creditedToUser),
fraudStatus: (c.fraudStatus as string) ?? null,
createdAt: (c.createdAt as string) ?? null,
updatedAt: (c.updatedAt as string) ?? null,
}
}
function mapHiringTeam(raw: unknown): AshbyHiringTeamMember[] {
if (!Array.isArray(raw)) return []
return raw.map((m) => {
const mem = m as Unknown
return {
email: (mem.email as string) ?? null,
firstName: (mem.firstName as string) ?? null,
lastName: (mem.lastName as string) ?? null,
role: (mem.role as string) ?? null,
userId: (mem.userId as string) ?? null,
}
})
}
function mapOpeningLatestVersion(raw: unknown): AshbyOpeningLatestVersion | null {
if (!raw || typeof raw !== 'object') return null
const v = raw as Unknown
return {
id: (v.id as string) ?? null,
identifier: (v.identifier as string) ?? null,
description: (v.description as string) ?? null,
authorId: (v.authorId as string) ?? null,
createdAt: (v.createdAt as string) ?? null,
teamId: (v.teamId as string) ?? null,
jobIds: Array.isArray(v.jobIds) ? (v.jobIds as string[]) : [],
targetHireDate: (v.targetHireDate as string) ?? null,
targetStartDate: (v.targetStartDate as string) ?? null,
isBackfill: (v.isBackfill as boolean) ?? false,
employmentType: (v.employmentType as string) ?? null,
locationIds: Array.isArray(v.locationIds) ? (v.locationIds as string[]) : [],
hiringTeam: mapHiringTeam(v.hiringTeam),
customFields: mapCustomFields(v.customFields),
}
}
export function mapOpenings(raw: unknown): AshbyOpening[] {
if (!Array.isArray(raw)) return []
return raw.map((o) => {
const op = o as Unknown
return {
id: (op.id as string) ?? '',
openedAt: (op.openedAt as string) ?? null,
closedAt: (op.closedAt as string) ?? null,
isArchived: (op.isArchived as boolean) ?? false,
archivedAt: (op.archivedAt as string) ?? null,
closeReasonId: (op.closeReasonId as string) ?? null,
openingState: (op.openingState as string) ?? null,
latestVersion: mapOpeningLatestVersion(op.latestVersion),
}
})
}
export function mapJob(raw: unknown): AshbyJob {
const j = (raw ?? {}) as Unknown
const location = j.location as Unknown | undefined
const address = location?.address as Unknown | undefined
const postalAddress = address?.postalAddress as Unknown | undefined
const compensation = j.compensation as Unknown | undefined
return {
id: (j.id as string) ?? '',
title: (j.title as string) ?? '',
confidential: (j.confidential as boolean) ?? false,
status: (j.status as string) ?? null,
employmentType: (j.employmentType as string) ?? null,
locationId: (j.locationId as string) ?? null,
departmentId: (j.departmentId as string) ?? null,
defaultInterviewPlanId: (j.defaultInterviewPlanId as string) ?? null,
interviewPlanIds: Array.isArray(j.interviewPlanIds) ? (j.interviewPlanIds as string[]) : [],
customFields: mapCustomFields(j.customFields),
jobPostingIds: Array.isArray(j.jobPostingIds) ? (j.jobPostingIds as string[]) : [],
customRequisitionId: (j.customRequisitionId as string) ?? null,
brandId: (j.brandId as string) ?? null,
hiringTeam: mapHiringTeam(j.hiringTeam),
author: mapUserSummary(j.author),
createdAt: (j.createdAt as string) ?? null,
updatedAt: (j.updatedAt as string) ?? null,
openedAt: (j.openedAt as string) ?? null,
closedAt: (j.closedAt as string) ?? null,
location: location
? {
id: (location.id as string) ?? null,
name: (location.name as string) ?? null,
externalName: (location.externalName as string) ?? null,
isArchived: (location.isArchived as boolean) ?? false,
isRemote: (location.isRemote as boolean) ?? false,
workplaceType: (location.workplaceType as string) ?? null,
parentLocationId: (location.parentLocationId as string) ?? null,
type: (location.type as string) ?? null,
address: postalAddress
? {
addressCountry: (postalAddress.addressCountry as string) ?? null,
addressRegion: (postalAddress.addressRegion as string) ?? null,
addressLocality: (postalAddress.addressLocality as string) ?? null,
postalCode: (postalAddress.postalCode as string) ?? null,
streetAddress: (postalAddress.streetAddress as string) ?? null,
}
: null,
}
: null,
openings: mapOpenings(j.openings),
compensation: compensation
? {
compensationTiers: Array.isArray(compensation.compensationTiers)
? (
compensation.compensationTiers as Array<{
id?: string
title?: string
additionalInformation?: string
tierSummary?: string
}>
).map((t) => ({
id: t.id ?? null,
title: t.title ?? null,
additionalInformation: t.additionalInformation ?? null,
tierSummary: t.tierSummary ?? null,
}))
: [],
}
: null,
}
}
export function mapApplication(raw: unknown): AshbyApplication {
const a = (raw ?? {}) as Unknown
const candidate = a.candidate as Unknown | undefined
const job = a.job as Unknown | undefined
const stage = a.currentInterviewStage as Unknown | undefined
const archiveReason = a.archiveReason as Unknown | undefined
return {
id: (a.id as string) ?? '',
createdAt: (a.createdAt as string) ?? null,
updatedAt: (a.updatedAt as string) ?? null,
status: (a.status as string) ?? '',
customFields: mapCustomFields(a.customFields),
candidate: {
id: (candidate?.id as string) ?? '',
name: (candidate?.name as string) ?? null,
primaryEmailAddress: mapContact(candidate?.primaryEmailAddress),
primaryPhoneNumber: mapContact(candidate?.primaryPhoneNumber),
},
currentInterviewStage: stage
? {
id: (stage.id as string) ?? '',
title: (stage.title as string) ?? null,
type: (stage.type as string) ?? null,
orderInInterviewPlan: (stage.orderInInterviewPlan as number) ?? null,
interviewStageGroupId: (stage.interviewStageGroupId as string) ?? null,
interviewPlanId: (stage.interviewPlanId as string) ?? null,
}
: null,
source: mapSource(a.source),
archiveReason: archiveReason
? {
id: (archiveReason.id as string) ?? '',
text: (archiveReason.text as string) ?? null,
reasonType: (archiveReason.reasonType as string) ?? null,
isArchived: (archiveReason.isArchived as boolean) ?? false,
customFields: mapCustomFields(archiveReason.customFields),
}
: null,
archivedAt: (a.archivedAt as string) ?? null,
job: {
id: (job?.id as string) ?? '',
title: (job?.title as string) ?? null,
locationId: (job?.locationId as string) ?? null,
departmentId: (job?.departmentId as string) ?? null,
},
creditedToUser: mapUserSummary(a.creditedToUser),
hiringTeam: mapHiringTeam(a.hiringTeam),
appliedViaJobPostingId: (a.appliedViaJobPostingId as string) ?? null,
submitterClientIp: (a.submitterClientIp as string) ?? null,
submitterUserAgent: (a.submitterUserAgent as string) ?? null,
}
}
export const CONTACT_INFO_OUTPUT = {
type: 'object',
description: 'Contact info',
optional: true,
properties: {
value: { type: 'string', description: 'Value (email or phone number)' },
type: { type: 'string', description: 'Contact type (Personal, Work, Other)' },
isPrimary: { type: 'boolean', description: 'Whether this is the primary contact' },
},
} as const satisfies OutputProperty
export const CUSTOM_FIELDS_OUTPUT = {
type: 'array',
description: 'Custom field values',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Custom field UUID' },
title: { type: 'string', description: 'Field title' },
isPrivate: { type: 'boolean', description: 'Whether the field is private' },
valueLabel: { type: 'string', description: 'Human-readable value label', optional: true },
value: { type: 'string', description: 'Raw field value (type depends on fieldType)' },
},
},
} as const satisfies OutputProperty
export const FILE_HANDLE_OUTPUT = {
type: 'object',
description: 'File reference',
optional: true,
properties: {
id: { type: 'string', description: 'File UUID' },
name: { type: 'string', description: 'File name' },
handle: { type: 'string', description: 'File handle used with file.info' },
},
} as const satisfies OutputProperty
export const FILE_HANDLES_OUTPUT = {
type: 'array',
description: 'File references',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'File UUID' },
name: { type: 'string', description: 'File name' },
handle: { type: 'string', description: 'File handle used with file.info' },
},
},
} as const satisfies OutputProperty
export const USER_SUMMARY_OUTPUT = {
type: 'object',
description: 'User summary',
optional: true,
properties: {
id: { type: 'string', description: 'User UUID' },
firstName: { type: 'string', description: 'First name', optional: true },
lastName: { type: 'string', description: 'Last name', optional: true },
email: { type: 'string', description: 'Email', optional: true },
globalRole: { type: 'string', description: 'Role', optional: true },
isEnabled: { type: 'boolean', description: 'Whether enabled' },
updatedAt: { type: 'string', description: 'Last update timestamp', optional: true },
managerId: { type: 'string', description: 'Manager user UUID', optional: true },
},
} as const satisfies OutputProperty
export const SOURCE_SUMMARY_OUTPUT = {
type: 'object',
description: 'Attribution source',
optional: true,
properties: {
id: { type: 'string', description: 'Source UUID' },
title: { type: 'string', description: 'Source title' },
isArchived: { type: 'boolean', description: 'Whether archived' },
sourceType: {
type: 'object',
description: 'Source type grouping',
optional: true,
properties: {
id: { type: 'string', description: 'Source type UUID' },
title: { type: 'string', description: 'Source type title' },
isArchived: { type: 'boolean', description: 'Whether archived' },
},
},
},
} as const satisfies OutputProperty
export const HIRING_TEAM_OUTPUT = {
type: 'array',
description: 'Hiring team members',
items: {
type: 'object',
properties: {
userId: { type: 'string', description: 'User UUID' },
firstName: { type: 'string', description: 'First name' },
lastName: { type: 'string', description: 'Last name' },
email: { type: 'string', description: 'Email' },
role: { type: 'string', description: 'Hiring team role' },
},
},
} as const satisfies OutputProperty
export const CANDIDATE_OUTPUTS = {
id: { type: 'string', description: 'Candidate UUID' },
name: { type: 'string', description: 'Full name' },
primaryEmailAddress: { ...CONTACT_INFO_OUTPUT, description: 'Primary email contact info' },
primaryPhoneNumber: { ...CONTACT_INFO_OUTPUT, description: 'Primary phone contact info' },
emailAddresses: {
type: 'array',
description: 'All email addresses',
items: {
type: 'object',
properties: {
value: { type: 'string', description: 'Email address' },
type: { type: 'string', description: 'Contact type' },
isPrimary: { type: 'boolean', description: 'Whether primary' },
},
},
},
phoneNumbers: {
type: 'array',
description: 'All phone numbers',
items: {
type: 'object',
properties: {
value: { type: 'string', description: 'Phone number' },
type: { type: 'string', description: 'Contact type' },
isPrimary: { type: 'boolean', description: 'Whether primary' },
},
},
},
socialLinks: {
type: 'array',
description: 'Social network links',
items: {
type: 'object',
properties: {
type: { type: 'string', description: 'Link type (LinkedIn, GitHub, Twitter, etc.)' },
url: { type: 'string', description: 'Profile URL' },
},
},
},
linkedInUrl: { type: 'string', description: 'LinkedIn profile URL', optional: true },
githubUrl: { type: 'string', description: 'GitHub profile URL', optional: true },
profileUrl: { type: 'string', description: 'URL to the candidate Ashby profile', optional: true },
position: { type: 'string', description: 'Current position or title', optional: true },
company: { type: 'string', description: 'Current company', optional: true },
school: { type: 'string', description: 'Most recent school', optional: true },
timezone: { type: 'string', description: 'Candidate timezone', optional: true },
location: {
type: 'object',
description: 'Candidate location',
optional: true,
properties: {
id: { type: 'string', description: 'Location UUID', optional: true },
locationSummary: { type: 'string', description: 'Human-readable location summary' },
locationComponents: {
type: 'array',
description: 'Structured location parts (city, region, country, etc.)',
items: {
type: 'object',
properties: {
type: { type: 'string', description: 'Component type' },
name: { type: 'string', description: 'Component value' },
},
},
},
},
},
tags: {
type: 'array',
description: 'Tags applied to the candidate',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Tag UUID' },
title: { type: 'string', description: 'Tag title' },
isArchived: { type: 'boolean', description: 'Whether archived' },
},
},
},
applicationIds: {
type: 'array',
description: 'IDs of associated applications',
items: { type: 'string', description: 'Application UUID' },
},
customFields: CUSTOM_FIELDS_OUTPUT,
resumeFileHandle: { ...FILE_HANDLE_OUTPUT, description: 'Resume file reference' },
fileHandles: { ...FILE_HANDLES_OUTPUT, description: 'All uploaded file references' },
source: SOURCE_SUMMARY_OUTPUT,
creditedToUser: { ...USER_SUMMARY_OUTPUT, description: 'User credited with sourcing' },
fraudStatus: { type: 'string', description: 'Fraud detection status', optional: true },
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp' },
} as const satisfies Record<string, OutputProperty>
export const APPLICATION_OUTPUTS = {
id: { type: 'string', description: 'Application UUID' },
status: { type: 'string', description: 'Status (Active, Hired, Archived, Lead)' },
customFields: CUSTOM_FIELDS_OUTPUT,
candidate: {
type: 'object',
description: 'Associated candidate summary',
properties: {
id: { type: 'string', description: 'Candidate UUID' },
name: { type: 'string', description: 'Candidate name' },
primaryEmailAddress: { ...CONTACT_INFO_OUTPUT, description: 'Primary email' },
primaryPhoneNumber: { ...CONTACT_INFO_OUTPUT, description: 'Primary phone' },
},
},
currentInterviewStage: {
type: 'object',
description: 'Current interview stage',
optional: true,
properties: {
id: { type: 'string', description: 'Stage UUID' },
title: { type: 'string', description: 'Stage title' },
type: { type: 'string', description: 'Stage type' },
orderInInterviewPlan: {
type: 'number',
description: 'Position in plan',
optional: true,
},
interviewStageGroupId: { type: 'string', description: 'Stage group UUID', optional: true },
interviewPlanId: { type: 'string', description: 'Interview plan UUID', optional: true },
},
},
source: SOURCE_SUMMARY_OUTPUT,
archiveReason: {
type: 'object',
description: 'Reason for archival (when archived)',
optional: true,
properties: {
id: { type: 'string', description: 'Reason UUID' },
text: { type: 'string', description: 'Reason text' },
reasonType: { type: 'string', description: 'Reason category' },
isArchived: { type: 'boolean', description: 'Whether the reason is archived' },
customFields: CUSTOM_FIELDS_OUTPUT,
},
},
archivedAt: { type: 'string', description: 'ISO 8601 archive timestamp', optional: true },
job: {
type: 'object',
description: 'Associated job summary',
properties: {
id: { type: 'string', description: 'Job UUID' },
title: { type: 'string', description: 'Job title' },
locationId: { type: 'string', description: 'Location UUID', optional: true },
departmentId: { type: 'string', description: 'Department UUID', optional: true },
},
},
creditedToUser: { ...USER_SUMMARY_OUTPUT, description: 'User credited with the application' },
hiringTeam: HIRING_TEAM_OUTPUT,
appliedViaJobPostingId: {
type: 'string',
description: 'Job posting UUID the candidate applied through',
optional: true,
},
submitterClientIp: { type: 'string', description: 'Submitter IP address', optional: true },
submitterUserAgent: {
type: 'string',
description: 'Submitter browser user agent',
optional: true,
},
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp' },
} as const satisfies Record<string, OutputProperty>
export const OPENINGS_OUTPUT = {
type: 'array',
description: 'Headcount openings associated with the job',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Opening UUID' },
openedAt: { type: 'string', description: 'Opening open timestamp', optional: true },
closedAt: { type: 'string', description: 'Opening close timestamp', optional: true },
isArchived: { type: 'boolean', description: 'Whether archived' },
archivedAt: { type: 'string', description: 'Archive timestamp', optional: true },
closeReasonId: { type: 'string', description: 'Close reason UUID', optional: true },
openingState: {
type: 'string',
description: 'Opening state (Approved, Open, Filled, Closed, Draft)',
optional: true,
},
latestVersion: {
type: 'object',
description: 'Latest opening version',
optional: true,
properties: {
id: { type: 'string', description: 'Version UUID', optional: true },
identifier: { type: 'string', description: 'Human-readable identifier' },
description: { type: 'string', description: 'Opening description' },
authorId: { type: 'string', description: 'Author user UUID', optional: true },
createdAt: { type: 'string', description: 'Version creation timestamp', optional: true },
teamId: { type: 'string', description: 'Team UUID', optional: true },
jobIds: {
type: 'array',
description: 'Associated job UUIDs',
items: { type: 'string', description: 'Job UUID' },
},
targetHireDate: { type: 'string', description: 'Target hire date', optional: true },
targetStartDate: { type: 'string', description: 'Target start date', optional: true },
isBackfill: { type: 'boolean', description: 'Whether this is a backfill opening' },
employmentType: { type: 'string', description: 'Employment type', optional: true },
locationIds: {
type: 'array',
description: 'Location UUIDs',
items: { type: 'string', description: 'Location UUID' },
},
hiringTeam: HIRING_TEAM_OUTPUT,
customFields: CUSTOM_FIELDS_OUTPUT,
},
},
},
},
} as const satisfies OutputProperty
function mapOfferVersion(raw: unknown): AshbyOfferVersion | null {
if (!raw || typeof raw !== 'object') return null
const v = raw as Unknown
const salary = v.salary as Unknown | undefined
return {
id: (v.id as string) ?? null,
startDate: (v.startDate as string) ?? null,
salary: salary
? {
currencyCode: (salary.currencyCode as string) ?? null,
value: (salary.value as number) ?? null,
}
: null,
createdAt: (v.createdAt as string) ?? null,
openingId: (v.openingId as string) ?? null,
customFields: mapCustomFields(v.customFields),
fileHandles: mapFileHandles(v.fileHandles),
author: mapUserSummary(v.author),
approvalStatus: (v.approvalStatus as string) ?? null,
}
}
export function mapOffer(raw: unknown): AshbyOffer {
const r = (raw ?? {}) as Unknown
return {
id: (r.id as string) ?? '',
decidedAt: (r.decidedAt as string) ?? null,
applicationId: (r.applicationId as string) ?? null,
acceptanceStatus: (r.acceptanceStatus as string) ?? null,
offerStatus: (r.offerStatus as string) ?? null,
latestVersion: mapOfferVersion(r.latestVersion),
}
}
export const OFFER_OUTPUTS = {
id: { type: 'string', description: 'Offer UUID' },
decidedAt: {
type: 'string',
description: 'Timestamp the offer was decided',
optional: true,
},
applicationId: {
type: 'string',
description: 'Associated application UUID',
optional: true,
},
acceptanceStatus: {
type: 'string',
description: 'Acceptance status (Accepted, Declined, Pending, etc.)',
optional: true,
},
offerStatus: {
type: 'string',
description: 'Offer status (e.g. WaitingOnCandidateResponse, CandidateAccepted)',
optional: true,
},
latestVersion: {
type: 'object',
description: 'Most recent version of the offer with pricing and metadata',
optional: true,
properties: {
id: { type: 'string', description: 'Version UUID', optional: true },
startDate: { type: 'string', description: 'Offer start date', optional: true },
salary: {
type: 'object',
description: 'Salary details',
optional: true,
properties: {
currencyCode: {
type: 'string',
description: 'ISO 4217 currency code',
optional: true,
},
value: { type: 'number', description: 'Salary amount', optional: true },
},
},
createdAt: {
type: 'string',
description: 'Version creation timestamp',
optional: true,
},
openingId: {
type: 'string',
description: 'Associated opening UUID',
optional: true,
},
customFields: CUSTOM_FIELDS_OUTPUT,
fileHandles: {
...FILE_HANDLES_OUTPUT,
description:
'Offer letter file handles (unsigned .pdf, .docx, and signed .pdf when generated)',
},
author: { ...USER_SUMMARY_OUTPUT, description: 'User who authored the version' },
approvalStatus: {
type: 'string',
description: 'Approval workflow status',
optional: true,
},
},
},
} as const satisfies Record<string, OutputProperty>
export const JOB_OUTPUTS = {
id: { type: 'string', description: 'Job UUID' },
title: { type: 'string', description: 'Job title' },
confidential: { type: 'boolean', description: 'Whether the job is confidential' },
status: { type: 'string', description: 'Status (Open, Closed, Draft, Archived)', optional: true },
employmentType: {
type: 'string',
description: 'Employment type (FullTime, PartTime, Intern, Contract, Temporary)',
optional: true,
},
locationId: { type: 'string', description: 'Primary location UUID', optional: true },
departmentId: { type: 'string', description: 'Department UUID', optional: true },
defaultInterviewPlanId: {
type: 'string',
description: 'Default interview plan UUID',
optional: true,
},
interviewPlanIds: {
type: 'array',
description: 'All interview plan UUIDs',
items: { type: 'string', description: 'Interview plan UUID' },
},
customFields: CUSTOM_FIELDS_OUTPUT,
jobPostingIds: {
type: 'array',
description: 'Associated job posting UUIDs',
items: { type: 'string', description: 'Job posting UUID' },
},
customRequisitionId: {
type: 'string',
description: 'Custom requisition identifier',
optional: true,
},
brandId: { type: 'string', description: 'Brand UUID', optional: true },
hiringTeam: HIRING_TEAM_OUTPUT,
author: { ...USER_SUMMARY_OUTPUT, description: 'Job author (creator)' },
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp', optional: true },
updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp', optional: true },
openedAt: { type: 'string', description: 'ISO 8601 opened timestamp', optional: true },
closedAt: { type: 'string', description: 'ISO 8601 closed timestamp', optional: true },
location: {
type: 'object',
description: 'Primary location details',
optional: true,
properties: {
id: { type: 'string', description: 'Location UUID', optional: true },
name: { type: 'string', description: 'Location name', optional: true },
externalName: { type: 'string', description: 'External display name', optional: true },
isArchived: { type: 'boolean', description: 'Whether archived' },
isRemote: { type: 'boolean', description: 'Whether remote' },
workplaceType: {
type: 'string',
description: 'Workplace type (OnSite, Remote, Hybrid)',
optional: true,
},
parentLocationId: {
type: 'string',
description: 'Parent location UUID',
optional: true,
},
type: { type: 'string', description: 'Location type', optional: true },
address: {
type: 'object',
description: 'Postal address',
optional: true,
properties: {
addressCountry: { type: 'string', description: 'Country', optional: true },
addressRegion: { type: 'string', description: 'State or region', optional: true },
addressLocality: { type: 'string', description: 'City or locality', optional: true },
postalCode: { type: 'string', description: 'Postal code', optional: true },
streetAddress: { type: 'string', description: 'Street address', optional: true },
},
},
},
},
openings: OPENINGS_OUTPUT,
compensation: {
type: 'object',
description: 'Job compensation structure',
optional: true,
properties: {
compensationTiers: {
type: 'array',
description: 'Compensation tiers',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Tier UUID', optional: true },
title: { type: 'string', description: 'Tier title', optional: true },
additionalInformation: {
type: 'string',
description: 'Additional info',
optional: true,
},
tierSummary: { type: 'string', description: 'Tier summary', optional: true },
},
},
},
},
},
} as const satisfies Record<string, OutputProperty>
+39 -3
View File
@@ -13,6 +13,30 @@ export const ashbyTriggerOptions = [
{ label: 'Offer Created', id: 'ashby_offer_create' },
]
/**
* Maps Sim trigger IDs to Ashby webhookType / event action values.
* Used by webhook.create body and matchEvent filtering.
*/
export const ASHBY_TRIGGER_ACTION_MAP: Record<string, string> = {
ashby_application_submit: 'applicationSubmit',
ashby_candidate_stage_change: 'candidateStageChange',
ashby_candidate_hire: 'candidateHire',
ashby_candidate_delete: 'candidateDelete',
ashby_job_create: 'jobCreate',
ashby_offer_create: 'offerCreate',
}
/**
* Checks if an Ashby webhook event matches the configured trigger.
* Ashby sends a ping event on webhook create/edit; this filter rejects
* any event whose `action` does not equal the expected webhookType.
*/
export function isAshbyEventMatch(triggerId: string, action: string): boolean {
const expected = ASHBY_TRIGGER_ACTION_MAP[triggerId]
if (!expected) return false
return expected === action
}
/**
* Generates setup instructions for Ashby webhooks.
* Webhooks are automatically created/deleted via the Ashby API.
@@ -168,9 +192,8 @@ export function buildCandidateStageChangeOutputs(): Record<string, TriggerOutput
/**
* Build outputs for candidateHire events.
* Payload: { action, data: { application: { id, createdAt, updatedAt, status,
* candidate: { id, name }, currentInterviewStage: { id, title },
* job: { id, title } } } }
* Per Ashby docs, candidateHire payloads include application details and most
* recent accepted offer information.
*/
export function buildCandidateHireOutputs(): Record<string, TriggerOutput> {
return {
@@ -196,6 +219,19 @@ export function buildCandidateHireOutputs(): Record<string, TriggerOutput> {
title: { type: 'string', description: 'Job title' },
},
},
offer: {
id: { type: 'string', description: 'Accepted offer UUID' },
applicationId: { type: 'string', description: 'Associated application UUID' },
acceptanceStatus: { type: 'string', description: 'Offer acceptance status' },
offerStatus: { type: 'string', description: 'Offer process status' },
decidedAt: {
type: 'string',
description: 'Offer decision timestamp (ISO 8601)',
},
latestVersion: {
id: { type: 'string', description: 'Latest offer version UUID' },
},
},
} as Record<string, TriggerOutput>
}