mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
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:
co-authored by
Claude Opus 4.7
parent
efc868263a
commit
f330fe22a2
@@ -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
@@ -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' },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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 },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user