From bbdda0d5e7184787a69ab88182eea7dac46de479 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 26 Feb 2026 17:07:00 -0800 Subject: [PATCH 1/2] feat(ashby): add ashby integration for candidate, job, and application management --- apps/docs/components/icons.tsx | 13 + apps/docs/components/ui/icon-mapping.ts | 14 +- apps/docs/content/docs/en/tools/ashby.mdx | 473 +++++++++++++++++++++ apps/docs/content/docs/en/tools/meta.json | 3 +- apps/sim/blocks/blocks/ashby.ts | 460 ++++++++++++++++++++ apps/sim/blocks/registry.ts | 2 + apps/sim/components/icons.tsx | 13 + apps/sim/tools/ashby/create_application.ts | 205 +++++++++ apps/sim/tools/ashby/create_candidate.ts | 164 +++++++ apps/sim/tools/ashby/create_note.ts | 112 +++++ apps/sim/tools/ashby/get_application.ts | 177 ++++++++ apps/sim/tools/ashby/get_candidate.ts | 134 ++++++ apps/sim/tools/ashby/get_job.ts | 83 ++++ apps/sim/tools/ashby/index.ts | 27 ++ apps/sim/tools/ashby/list_applications.ts | 193 +++++++++ apps/sim/tools/ashby/list_candidates.ts | 136 ++++++ apps/sim/tools/ashby/list_jobs.ts | 112 +++++ apps/sim/tools/ashby/list_notes.ts | 147 +++++++ apps/sim/tools/ashby/list_offers.ts | 155 +++++++ apps/sim/tools/ashby/search_candidates.ts | 122 ++++++ apps/sim/tools/ashby/types.ts | 192 +++++++++ apps/sim/tools/ashby/update_candidate.ts | 231 ++++++++++ apps/sim/tools/registry.ts | 28 ++ 23 files changed, 3189 insertions(+), 7 deletions(-) create mode 100644 apps/docs/content/docs/en/tools/ashby.mdx create mode 100644 apps/sim/blocks/blocks/ashby.ts create mode 100644 apps/sim/tools/ashby/create_application.ts create mode 100644 apps/sim/tools/ashby/create_candidate.ts create mode 100644 apps/sim/tools/ashby/create_note.ts create mode 100644 apps/sim/tools/ashby/get_application.ts create mode 100644 apps/sim/tools/ashby/get_candidate.ts create mode 100644 apps/sim/tools/ashby/get_job.ts create mode 100644 apps/sim/tools/ashby/index.ts create mode 100644 apps/sim/tools/ashby/list_applications.ts create mode 100644 apps/sim/tools/ashby/list_candidates.ts create mode 100644 apps/sim/tools/ashby/list_jobs.ts create mode 100644 apps/sim/tools/ashby/list_notes.ts create mode 100644 apps/sim/tools/ashby/list_offers.ts create mode 100644 apps/sim/tools/ashby/search_candidates.ts create mode 100644 apps/sim/tools/ashby/types.ts create mode 100644 apps/sim/tools/ashby/update_candidate.ts diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 9e68974089..84f6977924 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -2962,6 +2962,19 @@ export function QdrantIcon(props: SVGProps) { ) } +export function AshbyIcon(props: SVGProps) { + return ( + + + + ) +} + export function ArxivIcon(props: SVGProps) { return ( diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 822ce48aeb..5c2a3fd9d1 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -13,6 +13,7 @@ import { ApolloIcon, ArxivIcon, AsanaIcon, + AshbyIcon, AttioIcon, BrainIcon, BrowserUseIcon, @@ -39,8 +40,8 @@ import { EyeIcon, FirecrawlIcon, FirefliesIcon, - GithubIcon, GitLabIcon, + GithubIcon, GmailIcon, GongIcon, GoogleBigQueryIcon, @@ -76,9 +77,9 @@ import { LinearIcon, LinkedInIcon, LinkupIcon, + MailServerIcon, MailchimpIcon, MailgunIcon, - MailServerIcon, Mem0Icon, MicrosoftDataverseIcon, MicrosoftExcelIcon, @@ -111,6 +112,8 @@ import { ResendIcon, RevenueCatIcon, S3Icon, + SQSIcon, + STTIcon, SalesforceIcon, SearchIcon, SendgridIcon, @@ -122,19 +125,17 @@ import { SimilarwebIcon, SlackIcon, SmtpIcon, - SQSIcon, SshIcon, - STTIcon, StagehandIcon, StripeIcon, SupabaseIcon, + TTSIcon, TavilyIcon, TelegramIcon, TextractIcon, TinybirdIcon, TranslateIcon, TrelloIcon, - TTSIcon, TwilioIcon, TypeformIcon, UpstashIcon, @@ -145,11 +146,11 @@ import { WhatsAppIcon, WikipediaIcon, WordpressIcon, - xIcon, YouTubeIcon, ZendeskIcon, ZepIcon, ZoomIcon, + xIcon, } from '@/components/icons' type IconComponent = ComponentType> @@ -164,6 +165,7 @@ export const blockTypeToIconMap: Record = { apollo: ApolloIcon, arxiv: ArxivIcon, asana: AsanaIcon, + ashby: AshbyIcon, attio: AttioIcon, browser_use: BrowserUseIcon, calcom: CalComIcon, diff --git a/apps/docs/content/docs/en/tools/ashby.mdx b/apps/docs/content/docs/en/tools/ashby.mdx new file mode 100644 index 0000000000..72c050beac --- /dev/null +++ b/apps/docs/content/docs/en/tools/ashby.mdx @@ -0,0 +1,473 @@ +--- +title: Ashby +description: Manage candidates, jobs, and applications in Ashby +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Ashby](https://www.ashbyhq.com/) is an all-in-one recruiting platform that combines an applicant tracking system (ATS), CRM, scheduling, and analytics to help teams hire more effectively. + +With Ashby, you can: + +- **List and search candidates**: Browse your full candidate pipeline or search by name and email to quickly find specific people +- **Create candidates**: Add new candidates to your Ashby organization with contact details +- **View candidate details**: Retrieve full candidate profiles including tags, email, phone, and timestamps +- **Add notes to candidates**: Attach notes to candidate records to capture feedback, context, or follow-up items +- **List and view jobs**: Browse all open, closed, and archived job postings with location and department info +- **List applications**: View all applications across your organization with candidate and job details, status tracking, and pagination + +In Sim, the Ashby integration enables your agents to programmatically manage your recruiting pipeline. Agents can search for candidates, create new candidate records, add notes after interviews, and monitor applications across jobs. This allows you to automate recruiting workflows like candidate intake, interview follow-ups, pipeline reporting, and cross-referencing candidates across roles. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrate Ashby into the workflow. Can list, search, create, and update candidates, list and get job details, create notes, list notes, list and get applications, create applications, and list offers. + + + +## Tools + +### `ashby_create_application` + +Creates a new application for a candidate on a job. Optionally specify interview plan, stage, source, and credited user. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Ashby API Key | +| `candidateId` | string | Yes | The UUID of the candidate to consider for the job | +| `jobId` | string | Yes | The UUID of the job to consider the candidate for | +| `interviewPlanId` | string | No | UUID of the interview plan to use \(defaults to the job default plan\) | +| `interviewStageId` | string | No | UUID of the interview stage to place the application in \(defaults to first Lead stage\) | +| `sourceId` | string | No | UUID of the source to set on the application | +| `creditedToUserId` | string | No | UUID of the user the application is credited to | +| `createdAt` | string | No | ISO 8601 timestamp to set as the application creation date \(defaults to now\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Created 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 | + +### `ashby_create_candidate` + +Creates a new candidate record in Ashby. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Ashby API Key | +| `name` | string | Yes | The candidate full name | +| `email` | string | No | Primary email address for the candidate | +| `emailType` | string | No | Email address type: Personal, Work, or Other \(default Work\) | +| `phoneNumber` | string | No | Primary phone number for the candidate | +| `phoneType` | string | No | Phone number type: Personal, Work, or Other \(default Work\) | +| `linkedInUrl` | string | No | LinkedIn profile URL | +| `githubUrl` | string | No | GitHub profile URL | +| `sourceId` | string | No | UUID of the source to attribute the candidate to | + +#### Output + +| 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 | +| `createdAt` | string | ISO 8601 creation timestamp | + +### `ashby_create_note` + +Creates a note on a candidate in Ashby. Supports plain text and HTML content (bold, italic, underline, links, lists, code). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Ashby API Key | +| `candidateId` | string | Yes | The UUID of the candidate to add the note to | +| `note` | string | Yes | The note content. If noteType is text/html, supports: <b>, <i>, <u>, <a>, <ul>, <ol>, <li>, <code>, <pre> | +| `noteType` | string | No | Content type of the note: text/plain \(default\) or text/html | +| `sendNotifications` | boolean | No | Whether to send notifications to subscribed users \(default false\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Created note UUID | +| `content` | string | Note content as stored | +| `author` | object | Note author | +| ↳ `id` | string | Author user UUID | +| ↳ `firstName` | string | First name | +| ↳ `lastName` | string | Last name | +| ↳ `email` | string | Email address | +| `createdAt` | string | ISO 8601 creation timestamp | + +### `ashby_get_application` + +Retrieves full details about a single application by its ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Ashby API Key | +| `applicationId` | string | Yes | The UUID of the application to fetch | + +#### Output + +| 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 | +| `createdAt` | string | ISO 8601 creation timestamp | +| `updatedAt` | string | ISO 8601 last update timestamp | + +### `ashby_get_candidate` + +Retrieves full details about a single candidate by their ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Ashby API Key | +| `candidateId` | string | Yes | The UUID of the candidate to fetch | + +#### Output + +| 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 | +| `createdAt` | string | ISO 8601 creation timestamp | +| `updatedAt` | string | ISO 8601 last update timestamp | + +### `ashby_get_job` + +Retrieves full details about a single job by its ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Ashby API Key | +| `jobId` | string | Yes | The UUID of the job to fetch | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Job UUID | +| `title` | string | Job title | +| `status` | string | Job status \(Open, Closed, Draft, Archived, On Hold\) | +| `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 | +| `createdAt` | string | ISO 8601 creation timestamp | +| `updatedAt` | string | ISO 8601 last update timestamp | + +### `ashby_list_applications` + +Lists all applications in an Ashby organization with pagination and optional filters for status, job, candidate, and creation date. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Ashby API Key | +| `cursor` | string | No | Opaque pagination cursor from a previous response nextCursor value | +| `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 + +| 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 | + +### `ashby_list_candidates` + +Lists all candidates in an Ashby organization with cursor-based pagination. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Ashby API Key | +| `cursor` | string | No | Opaque pagination cursor from a previous response nextCursor value | +| `perPage` | number | No | Number of results per page \(default 100\) | + +#### Output + +| 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 | + +### `ashby_list_jobs` + +Lists all jobs in an Ashby organization. By default returns Open, Closed, and Archived jobs. Specify status to filter. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Ashby API Key | +| `cursor` | string | No | Opaque pagination cursor from a previous response nextCursor value | +| `perPage` | number | No | Number of results per page \(default 100\) | +| `status` | string | No | Filter by job status: Open, Closed, Archived, or Draft | + +#### Output + +| 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 | + +### `ashby_list_notes` + +Lists all notes on a candidate with pagination support. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Ashby API Key | +| `candidateId` | string | Yes | The UUID of the candidate to list notes for | +| `cursor` | string | No | Opaque pagination cursor from a previous response nextCursor value | +| `perPage` | number | No | Number of results per page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `notes` | array | List of notes on the candidate | +| ↳ `id` | string | Note UUID | +| ↳ `content` | string | Note content | +| ↳ `author` | object | Note author | +| ↳ `id` | string | Author user UUID | +| ↳ `firstName` | string | First name | +| ↳ `lastName` | string | Last name | +| ↳ `email` | string | Email address | +| ↳ `createdAt` | string | ISO 8601 creation timestamp | +| `moreDataAvailable` | boolean | Whether more pages of results exist | +| `nextCursor` | string | Opaque cursor for fetching the next page | + +### `ashby_list_offers` + +Lists all offers with their latest version in an Ashby organization. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Ashby API Key | +| `cursor` | string | No | Opaque pagination cursor from a previous response nextCursor value | +| `perPage` | number | No | Number of results per page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `offers` | array | List of offers | +| ↳ `id` | string | Offer UUID | +| ↳ `status` | string | Offer status | +| ↳ `candidate` | object | Associated candidate | +| ↳ `id` | string | Candidate UUID | +| ↳ `name` | string | Candidate name | +| ↳ `job` | object | Associated job | +| ↳ `id` | string | Job UUID | +| ↳ `title` | string | Job 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 | + +### `ashby_search_candidates` + +Searches for candidates by name and/or email with AND logic. Results are limited to 100 matches. Use candidate.list for full pagination. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Ashby API Key | +| `name` | string | No | Candidate name to search for \(combined with email using AND logic\) | +| `email` | string | No | Candidate email to search for \(combined with name using AND logic\) | + +#### Output + +| 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 | + +### `ashby_update_candidate` + +Updates an existing candidate record in Ashby. Only provided fields are changed. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Ashby API Key | +| `candidateId` | string | Yes | The UUID of the candidate to update | +| `name` | string | No | Updated full name | +| `email` | string | No | Updated primary email address | +| `emailType` | string | No | Email address type: Personal, Work, or Other \(default Work\) | +| `phoneNumber` | string | No | Updated primary phone number | +| `phoneType` | string | No | Phone number type: Personal, Work, or Other \(default Work\) | +| `linkedInUrl` | string | No | LinkedIn profile URL | +| `githubUrl` | string | No | GitHub profile URL | +| `websiteUrl` | string | No | Personal website URL | +| `sourceId` | string | No | UUID of the source to attribute the candidate to | + +#### Output + +| 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 | +| `createdAt` | string | ISO 8601 creation timestamp | +| `updatedAt` | string | ISO 8601 last update timestamp | + + diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index a089247e2e..b67889770a 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -10,6 +10,7 @@ "apollo", "arxiv", "asana", + "ashby", "attio", "browser_use", "calcom", @@ -150,4 +151,4 @@ "zep", "zoom" ] -} +} \ No newline at end of file diff --git a/apps/sim/blocks/blocks/ashby.ts b/apps/sim/blocks/blocks/ashby.ts new file mode 100644 index 0000000000..f1edc6e9ba --- /dev/null +++ b/apps/sim/blocks/blocks/ashby.ts @@ -0,0 +1,460 @@ +import { AshbyIcon } from '@/components/icons' +import { AuthMode, type BlockConfig } from '@/blocks/types' + +export const AshbyBlock: BlockConfig = { + type: 'ashby', + name: 'Ashby', + description: 'Manage candidates, jobs, and applications in Ashby', + longDescription: + 'Integrate Ashby into the workflow. Can list, search, create, and update candidates, list and get job details, create notes, list notes, list and get applications, create applications, and list offers.', + docsLink: 'https://docs.sim.ai/tools/ashby', + category: 'tools', + bgColor: '#5D4ED6', + icon: AshbyIcon, + authMode: AuthMode.ApiKey, + + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'List Candidates', id: 'list_candidates' }, + { label: 'Get Candidate', id: 'get_candidate' }, + { label: 'Create Candidate', id: 'create_candidate' }, + { label: 'Update Candidate', id: 'update_candidate' }, + { label: 'Search Candidates', id: 'search_candidates' }, + { label: 'List Jobs', id: 'list_jobs' }, + { label: 'Get Job', id: 'get_job' }, + { label: 'Create Note', id: 'create_note' }, + { label: 'List Notes', id: 'list_notes' }, + { label: 'List Applications', id: 'list_applications' }, + { label: 'Get Application', id: 'get_application' }, + { label: 'Create Application', id: 'create_application' }, + { label: 'List Offers', id: 'list_offers' }, + ], + value: () => 'list_candidates', + }, + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + required: true, + placeholder: 'Enter your Ashby API key', + password: true, + }, + + // Get Candidate / Create Note / List Notes / Update Candidate - candidateId + { + id: 'candidateId', + title: 'Candidate ID', + type: 'short-input', + required: { + field: 'operation', + value: ['get_candidate', 'create_note', 'list_notes', 'update_candidate'], + }, + placeholder: 'Enter candidate UUID', + condition: { + field: 'operation', + value: ['get_candidate', 'create_note', 'list_notes', 'update_candidate'], + }, + }, + + // Create Candidate fields + { + id: 'name', + title: 'Name', + type: 'short-input', + required: { field: 'operation', value: 'create_candidate' }, + placeholder: 'Full name (e.g. Jane Smith)', + condition: { field: 'operation', value: 'create_candidate' }, + }, + { + id: 'email', + title: 'Email', + type: 'short-input', + placeholder: 'Email address', + condition: { field: 'operation', value: ['create_candidate', 'update_candidate'] }, + }, + { + id: 'emailType', + title: 'Email Type', + type: 'dropdown', + options: [ + { label: 'Work', id: 'Work' }, + { label: 'Personal', id: 'Personal' }, + { label: 'Other', id: 'Other' }, + ], + value: () => 'Work', + condition: { field: 'operation', value: ['create_candidate', 'update_candidate'] }, + mode: 'advanced', + }, + { + id: 'phoneNumber', + title: 'Phone Number', + type: 'short-input', + placeholder: 'Phone number', + condition: { field: 'operation', value: ['create_candidate', 'update_candidate'] }, + mode: 'advanced', + }, + { + id: 'phoneType', + title: 'Phone Type', + type: 'dropdown', + options: [ + { label: 'Work', id: 'Work' }, + { label: 'Personal', id: 'Personal' }, + { label: 'Other', id: 'Other' }, + ], + value: () => 'Work', + condition: { field: 'operation', value: ['create_candidate', 'update_candidate'] }, + mode: 'advanced', + }, + { + id: 'linkedInUrl', + title: 'LinkedIn URL', + type: 'short-input', + placeholder: 'https://linkedin.com/in/...', + condition: { field: 'operation', value: ['create_candidate', 'update_candidate'] }, + mode: 'advanced', + }, + { + id: 'githubUrl', + title: 'GitHub URL', + type: 'short-input', + placeholder: 'https://github.com/...', + condition: { field: 'operation', value: ['create_candidate', 'update_candidate'] }, + mode: 'advanced', + }, + { + id: 'sourceId', + title: 'Source ID', + type: 'short-input', + placeholder: 'Source UUID to attribute the candidate to', + condition: { + field: 'operation', + value: ['create_candidate', 'update_candidate', 'create_application'], + }, + mode: 'advanced', + }, + + // Update Candidate fields + { + id: 'updateName', + title: 'Name', + type: 'short-input', + placeholder: 'Updated full name', + condition: { field: 'operation', value: 'update_candidate' }, + mode: 'advanced', + }, + { + id: 'websiteUrl', + title: 'Website URL', + type: 'short-input', + placeholder: 'https://example.com', + condition: { field: 'operation', value: 'update_candidate' }, + mode: 'advanced', + }, + + // Search Candidates fields + { + id: 'searchName', + title: 'Name', + type: 'short-input', + placeholder: 'Search by candidate name', + condition: { field: 'operation', value: 'search_candidates' }, + }, + { + id: 'searchEmail', + title: 'Email', + type: 'short-input', + placeholder: 'Search by candidate email', + condition: { field: 'operation', value: 'search_candidates' }, + }, + + // Get Job fields + { + id: 'jobId', + title: 'Job ID', + type: 'short-input', + required: { field: 'operation', value: ['get_job', 'create_application'] }, + placeholder: 'Enter job UUID', + condition: { field: 'operation', value: ['get_job', 'create_application'] }, + }, + + // Get Application fields + { + id: 'applicationId', + title: 'Application ID', + type: 'short-input', + required: { field: 'operation', value: 'get_application' }, + placeholder: 'Enter application UUID', + condition: { field: 'operation', value: 'get_application' }, + }, + + // Create Application fields + { + id: 'appCandidateId', + title: 'Candidate ID', + type: 'short-input', + required: { field: 'operation', value: 'create_application' }, + placeholder: 'Enter candidate UUID', + condition: { field: 'operation', value: 'create_application' }, + }, + { + id: 'interviewPlanId', + title: 'Interview Plan ID', + type: 'short-input', + placeholder: 'Interview plan UUID (defaults to job default)', + condition: { field: 'operation', value: 'create_application' }, + mode: 'advanced', + }, + { + id: 'interviewStageId', + title: 'Interview Stage ID', + type: 'short-input', + placeholder: 'Interview stage UUID (defaults to first Lead stage)', + condition: { field: 'operation', value: 'create_application' }, + mode: 'advanced', + }, + { + id: 'creditedToUserId', + title: 'Credited To User ID', + type: 'short-input', + placeholder: 'User UUID the application is credited to', + condition: { field: 'operation', value: 'create_application' }, + mode: 'advanced', + }, + { + id: 'appCreatedAt', + title: 'Created At', + type: 'short-input', + placeholder: 'e.g. 2024-01-01T00:00:00Z', + condition: { field: 'operation', value: 'create_application' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate an ISO 8601 timestamp based on the user's description. +Examples: +- "last week" -> One week ago from today at 00:00:00Z +- "January 1st 2024" -> 2024-01-01T00:00:00Z +- "30 days ago" -> 30 days before today at 00:00:00Z +- "start of this month" -> First day of current month at 00:00:00Z +Output only the ISO 8601 timestamp string, nothing else.`, + generationType: 'timestamp', + }, + }, + + // Create Note fields + { + id: 'note', + title: 'Note', + type: 'long-input', + required: { field: 'operation', value: 'create_note' }, + placeholder: 'Enter note content', + condition: { field: 'operation', value: 'create_note' }, + }, + { + id: 'noteType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'Plain Text', id: 'text/plain' }, + { label: 'HTML', id: 'text/html' }, + ], + value: () => 'text/plain', + condition: { field: 'operation', value: 'create_note' }, + mode: 'advanced', + }, + { + id: 'sendNotifications', + title: 'Send Notifications', + type: 'switch', + condition: { field: 'operation', value: 'create_note' }, + mode: 'advanced', + }, + + // List Applications filter fields + { + id: 'filterStatus', + title: 'Status Filter', + type: 'dropdown', + options: [ + { label: 'All', id: '' }, + { label: 'Active', id: 'Active' }, + { label: 'Hired', id: 'Hired' }, + { label: 'Archived', id: 'Archived' }, + { label: 'Lead', id: 'Lead' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_applications' }, + mode: 'advanced', + }, + { + id: 'filterJobId', + title: 'Job ID Filter', + type: 'short-input', + placeholder: 'Filter by job UUID', + 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', + type: 'short-input', + placeholder: 'e.g. 2024-01-01T00:00:00Z', + condition: { field: 'operation', value: 'list_applications' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate an ISO 8601 timestamp based on the user's description. +Examples: +- "last week" -> One week ago from today at 00:00:00Z +- "January 1st 2024" -> 2024-01-01T00:00:00Z +- "30 days ago" -> 30 days before today at 00:00:00Z +- "start of this month" -> First day of current month at 00:00:00Z +Output only the ISO 8601 timestamp string, nothing else.`, + generationType: 'timestamp', + }, + }, + + // List Jobs status filter + { + id: 'jobStatus', + title: 'Status Filter', + type: 'dropdown', + options: [ + { label: 'All', id: '' }, + { label: 'Open', id: 'Open' }, + { label: 'Closed', id: 'Closed' }, + { label: 'Archived', id: 'Archived' }, + { label: 'Draft', id: 'Draft' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_jobs' }, + mode: 'advanced', + }, + + // Pagination fields for list operations + { + id: 'cursor', + title: 'Cursor', + type: 'short-input', + placeholder: 'Pagination cursor from previous response', + condition: { + field: 'operation', + value: ['list_candidates', 'list_jobs', 'list_applications', 'list_notes', 'list_offers'], + }, + mode: 'advanced', + }, + { + id: 'perPage', + title: 'Per Page', + type: 'short-input', + placeholder: 'Results per page (default 100)', + condition: { + field: 'operation', + value: ['list_candidates', 'list_jobs', 'list_applications', 'list_notes', 'list_offers'], + }, + mode: 'advanced', + }, + ], + + tools: { + access: [ + 'ashby_create_application', + 'ashby_create_candidate', + 'ashby_create_note', + 'ashby_get_application', + 'ashby_get_candidate', + 'ashby_get_job', + 'ashby_list_applications', + 'ashby_list_candidates', + 'ashby_list_jobs', + 'ashby_list_notes', + 'ashby_list_offers', + 'ashby_search_candidates', + 'ashby_update_candidate', + ], + config: { + tool: (params) => `ashby_${params.operation}`, + params: (params) => { + const result: Record = {} + if (params.perPage) result.perPage = Number(params.perPage) + if (params.searchName) result.name = params.searchName + 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 + } + // Create Application params + if (params.appCandidateId) result.candidateId = params.appCandidateId + if (params.appCreatedAt) result.createdAt = params.appCreatedAt + // Update Candidate params + if (params.updateName) result.name = params.updateName + return result + }, + }, + }, + + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + apiKey: { type: 'string', description: 'Ashby API key' }, + candidateId: { type: 'string', description: 'Candidate UUID' }, + name: { type: 'string', description: 'Candidate full name' }, + email: { type: 'string', description: 'Email address' }, + emailType: { type: 'string', description: 'Email type (Personal, Work, Other)' }, + phoneNumber: { type: 'string', description: 'Phone number' }, + phoneType: { type: 'string', description: 'Phone type (Personal, Work, Other)' }, + linkedInUrl: { type: 'string', description: 'LinkedIn profile URL' }, + githubUrl: { type: 'string', description: 'GitHub profile URL' }, + websiteUrl: { type: 'string', description: 'Personal website URL' }, + sourceId: { type: 'string', description: 'Source UUID' }, + updateName: { type: 'string', description: 'Updated full name' }, + searchName: { type: 'string', description: 'Name to search for' }, + searchEmail: { type: 'string', description: 'Email to search for' }, + jobId: { type: 'string', description: 'Job UUID' }, + applicationId: { type: 'string', description: 'Application UUID' }, + appCandidateId: { type: 'string', description: 'Candidate UUID for application' }, + interviewPlanId: { type: 'string', description: 'Interview plan UUID' }, + interviewStageId: { type: 'string', description: 'Interview stage UUID' }, + creditedToUserId: { type: 'string', description: 'User UUID credited to' }, + appCreatedAt: { type: 'string', description: 'Application creation timestamp' }, + note: { type: 'string', description: 'Note content' }, + noteType: { type: 'string', description: 'Content type (text/plain or text/html)' }, + 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' }, + }, + + outputs: { + candidates: { type: 'json', description: 'List of candidates' }, + jobs: { type: 'json', description: 'List of jobs' }, + applications: { type: 'json', description: 'List of applications' }, + notes: { type: 'json', description: 'List of notes' }, + offers: { type: 'json', description: 'List of offers' }, + id: { type: 'string', description: 'Resource UUID' }, + name: { type: 'string', description: 'Resource name' }, + title: { type: 'string', description: 'Job title' }, + status: { type: 'string', description: 'Status' }, + content: { type: 'string', description: 'Note content' }, + moreDataAvailable: { type: 'boolean', description: 'Whether more pages exist' }, + nextCursor: { type: 'string', description: 'Pagination cursor for next page' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index eff25ffb1d..80794d6c09 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -10,6 +10,7 @@ import { ApifyBlock } from '@/blocks/blocks/apify' import { ApolloBlock } from '@/blocks/blocks/apollo' import { ArxivBlock } from '@/blocks/blocks/arxiv' import { AsanaBlock } from '@/blocks/blocks/asana' +import { AshbyBlock } from '@/blocks/blocks/ashby' import { AttioBlock } from '@/blocks/blocks/attio' import { BrowserUseBlock } from '@/blocks/blocks/browser_use' import { CalComBlock } from '@/blocks/blocks/calcom' @@ -191,6 +192,7 @@ export const registry: Record = { apify: ApifyBlock, apollo: ApolloBlock, arxiv: ArxivBlock, + ashby: AshbyBlock, asana: AsanaBlock, attio: AttioBlock, browser_use: BrowserUseBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 9e68974089..84f6977924 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -2962,6 +2962,19 @@ export function QdrantIcon(props: SVGProps) { ) } +export function AshbyIcon(props: SVGProps) { + return ( + + + + ) +} + export function ArxivIcon(props: SVGProps) { return ( diff --git a/apps/sim/tools/ashby/create_application.ts b/apps/sim/tools/ashby/create_application.ts new file mode 100644 index 0000000000..01fa4d467a --- /dev/null +++ b/apps/sim/tools/ashby/create_application.ts @@ -0,0 +1,205 @@ +import type { ToolConfig, ToolResponse } from '@/tools/types' + +interface AshbyCreateApplicationParams { + apiKey: string + candidateId: string + jobId: string + interviewPlanId?: string + interviewStageId?: string + sourceId?: string + creditedToUserId?: string + createdAt?: string +} + +interface AshbyCreateApplicationResponse 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 + createdAt: string + updatedAt: string + } +} + +export const createApplicationTool: ToolConfig< + AshbyCreateApplicationParams, + AshbyCreateApplicationResponse +> = { + id: 'ashby_create_application', + name: 'Ashby Create Application', + description: + 'Creates a new application for a candidate on a job. Optionally specify interview plan, stage, source, and credited user.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Ashby API Key', + }, + candidateId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The UUID of the candidate to consider for the job', + }, + jobId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The UUID of the job to consider the candidate for', + }, + interviewPlanId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'UUID of the interview plan to use (defaults to the job default plan)', + }, + interviewStageId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'UUID of the interview stage to place the application in (defaults to first Lead stage)', + }, + sourceId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'UUID of the source to set on the application', + }, + creditedToUserId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'UUID of the user the application is credited to', + }, + createdAt: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'ISO 8601 timestamp to set as the application creation date (defaults to now)', + }, + }, + + request: { + url: 'https://api.ashbyhq.com/application.create', + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/json', + Authorization: `Basic ${btoa(`${params.apiKey}:`)}`, + }), + body: (params) => { + const body: Record = { + candidateId: params.candidateId, + jobId: params.jobId, + } + 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.createdAt) body.createdAt = params.createdAt + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!data.success) { + throw new Error(data.errorInfo?.message || 'Failed to create 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, + createdAt: r.createdAt ?? null, + updatedAt: r.updatedAt ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Created 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' }, + }, +} diff --git a/apps/sim/tools/ashby/create_candidate.ts b/apps/sim/tools/ashby/create_candidate.ts new file mode 100644 index 0000000000..5eb9829f3a --- /dev/null +++ b/apps/sim/tools/ashby/create_candidate.ts @@ -0,0 +1,164 @@ +import type { ToolConfig } from '@/tools/types' +import type { AshbyCreateCandidateParams, AshbyCreateCandidateResponse } from './types' + +export const createCandidateTool: ToolConfig< + AshbyCreateCandidateParams, + AshbyCreateCandidateResponse +> = { + id: 'ashby_create_candidate', + name: 'Ashby Create Candidate', + description: 'Creates a new candidate record in Ashby.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Ashby API Key', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The candidate full name', + }, + email: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Primary email address for the candidate', + }, + emailType: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Email address type: Personal, Work, or Other (default Work)', + }, + phoneNumber: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Primary phone number for the candidate', + }, + phoneType: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Phone number type: Personal, Work, or Other (default Work)', + }, + linkedInUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'LinkedIn profile URL', + }, + githubUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'GitHub profile URL', + }, + sourceId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'UUID of the source to attribute the candidate to', + }, + }, + + request: { + url: 'https://api.ashbyhq.com/candidate.create', + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/json', + Authorization: `Basic ${btoa(`${params.apiKey}:`)}`, + }), + body: (params) => { + const body: Record = { + name: params.name, + } + if (params.email) { + body.primaryEmailAddress = { + value: params.email, + type: params.emailType || 'Work', + isPrimary: true, + } + } + if (params.phoneNumber) { + body.primaryPhoneNumber = { + value: params.phoneNumber, + type: params.phoneType || 'Work', + isPrimary: true, + } + } + if (params.linkedInUrl || params.githubUrl) { + const socialLinks: Array<{ url: string; type: string }> = [] + if (params.linkedInUrl) socialLinks.push({ url: params.linkedInUrl, type: 'LinkedIn' }) + if (params.githubUrl) socialLinks.push({ url: params.githubUrl, type: 'GitHub' }) + body.socialLinks = socialLinks + } + if (params.sourceId) body.sourceId = params.sourceId + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!data.success) { + 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, + }, + } + }, + + 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' }, + }, +} diff --git a/apps/sim/tools/ashby/create_note.ts b/apps/sim/tools/ashby/create_note.ts new file mode 100644 index 0000000000..21c2aa0a05 --- /dev/null +++ b/apps/sim/tools/ashby/create_note.ts @@ -0,0 +1,112 @@ +import type { ToolConfig } from '@/tools/types' +import type { AshbyCreateNoteParams, AshbyCreateNoteResponse } from './types' + +export const createNoteTool: ToolConfig = { + id: 'ashby_create_note', + name: 'Ashby Create Note', + description: + 'Creates a note on a candidate in Ashby. Supports plain text and HTML content (bold, italic, underline, links, lists, code).', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Ashby API Key', + }, + candidateId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The UUID of the candidate to add the note to', + }, + note: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'The note content. If noteType is text/html, supports: , , , ,
    ,
      ,
    1. , ,
      ',
      +    },
      +    noteType: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Content type of the note: text/plain (default) or text/html',
      +    },
      +    sendNotifications: {
      +      type: 'boolean',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Whether to send notifications to subscribed users (default false)',
      +    },
      +  },
      +
      +  request: {
      +    url: 'https://api.ashbyhq.com/candidate.createNote',
      +    method: 'POST',
      +    headers: (params) => ({
      +      'Content-Type': 'application/json',
      +      Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
      +    }),
      +    body: (params) => {
      +      const body: Record = {
      +        candidateId: params.candidateId,
      +        sendNotifications: params.sendNotifications ?? false,
      +      }
      +      if (params.noteType === 'text/html') {
      +        body.note = {
      +          type: 'text/html',
      +          value: params.note,
      +        }
      +      } else {
      +        body.note = params.note
      +      }
      +      return body
      +    },
      +  },
      +
      +  transformResponse: async (response: Response) => {
      +    const data = await response.json()
      +
      +    if (!data.success) {
      +      throw new Error(data.errorInfo?.message || 'Failed to create note')
      +    }
      +
      +    const r = data.results
      +
      +    return {
      +      success: true,
      +      output: {
      +        id: r.id ?? null,
      +        content: r.content ?? null,
      +        author: r.author
      +          ? {
      +              id: r.author.id ?? null,
      +              firstName: r.author.firstName ?? null,
      +              lastName: r.author.lastName ?? null,
      +              email: r.author.email ?? null,
      +            }
      +          : null,
      +        createdAt: r.createdAt ?? null,
      +      },
      +    }
      +  },
      +
      +  outputs: {
      +    id: { type: 'string', description: 'Created note UUID' },
      +    content: { type: 'string', description: 'Note content as stored' },
      +    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' },
      +      },
      +    },
      +    createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
      +  },
      +}
      diff --git a/apps/sim/tools/ashby/get_application.ts b/apps/sim/tools/ashby/get_application.ts
      new file mode 100644
      index 0000000000..2464986091
      --- /dev/null
      +++ b/apps/sim/tools/ashby/get_application.ts
      @@ -0,0 +1,177 @@
      +import type { ToolConfig, ToolResponse } from '@/tools/types'
      +
      +interface AshbyGetApplicationParams {
      +  apiKey: string
      +  applicationId: string
      +}
      +
      +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
      +  }
      +}
      +
      +export const getApplicationTool: ToolConfig<
      +  AshbyGetApplicationParams,
      +  AshbyGetApplicationResponse
      +> = {
      +  id: 'ashby_get_application',
      +  name: 'Ashby Get Application',
      +  description: 'Retrieves full details about a single application by its ID.',
      +  version: '1.0.0',
      +
      +  params: {
      +    apiKey: {
      +      type: 'string',
      +      required: true,
      +      visibility: 'user-only',
      +      description: 'Ashby API Key',
      +    },
      +    applicationId: {
      +      type: 'string',
      +      required: true,
      +      visibility: 'user-or-llm',
      +      description: 'The UUID of the application to fetch',
      +    },
      +  },
      +
      +  request: {
      +    url: 'https://api.ashbyhq.com/application.info',
      +    method: 'POST',
      +    headers: (params) => ({
      +      'Content-Type': 'application/json',
      +      Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
      +    }),
      +    body: (params) => ({
      +      applicationId: params.applicationId,
      +    }),
      +  },
      +
      +  transformResponse: async (response: Response) => {
      +    const data = await response.json()
      +
      +    if (!data.success) {
      +      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,
      +      },
      +    }
      +  },
      +
      +  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' },
      +  },
      +}
      diff --git a/apps/sim/tools/ashby/get_candidate.ts b/apps/sim/tools/ashby/get_candidate.ts
      new file mode 100644
      index 0000000000..023b8cb9f1
      --- /dev/null
      +++ b/apps/sim/tools/ashby/get_candidate.ts
      @@ -0,0 +1,134 @@
      +import type { ToolConfig } from '@/tools/types'
      +import type { AshbyGetCandidateParams, AshbyGetCandidateResponse } from './types'
      +
      +export const getCandidateTool: ToolConfig = {
      +  id: 'ashby_get_candidate',
      +  name: 'Ashby Get Candidate',
      +  description: 'Retrieves full details about a single candidate by their ID.',
      +  version: '1.0.0',
      +
      +  params: {
      +    apiKey: {
      +      type: 'string',
      +      required: true,
      +      visibility: 'user-only',
      +      description: 'Ashby API Key',
      +    },
      +    candidateId: {
      +      type: 'string',
      +      required: true,
      +      visibility: 'user-or-llm',
      +      description: 'The UUID of the candidate to fetch',
      +    },
      +  },
      +
      +  request: {
      +    url: 'https://api.ashbyhq.com/candidate.info',
      +    method: 'POST',
      +    headers: (params) => ({
      +      'Content-Type': 'application/json',
      +      Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
      +    }),
      +    body: (params) => ({
      +      candidateId: params.candidateId,
      +    }),
      +  },
      +
      +  transformResponse: async (response: Response) => {
      +    const data = await response.json()
      +
      +    if (!data.success) {
      +      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,
      +      },
      +    }
      +  },
      +
      +  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' },
      +  },
      +}
      diff --git a/apps/sim/tools/ashby/get_job.ts b/apps/sim/tools/ashby/get_job.ts
      new file mode 100644
      index 0000000000..016a774fe0
      --- /dev/null
      +++ b/apps/sim/tools/ashby/get_job.ts
      @@ -0,0 +1,83 @@
      +import type { ToolConfig } from '@/tools/types'
      +import type { AshbyGetJobParams, AshbyGetJobResponse } from './types'
      +
      +export const getJobTool: ToolConfig = {
      +  id: 'ashby_get_job',
      +  name: 'Ashby Get Job',
      +  description: 'Retrieves full details about a single job by its ID.',
      +  version: '1.0.0',
      +
      +  params: {
      +    apiKey: {
      +      type: 'string',
      +      required: true,
      +      visibility: 'user-only',
      +      description: 'Ashby API Key',
      +    },
      +    jobId: {
      +      type: 'string',
      +      required: true,
      +      visibility: 'user-or-llm',
      +      description: 'The UUID of the job to fetch',
      +    },
      +  },
      +
      +  request: {
      +    url: 'https://api.ashbyhq.com/job.info',
      +    method: 'POST',
      +    headers: (params) => ({
      +      'Content-Type': 'application/json',
      +      Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
      +    }),
      +    body: (params) => ({
      +      jobId: params.jobId,
      +    }),
      +  },
      +
      +  transformResponse: async (response: Response) => {
      +    const data = await response.json()
      +
      +    if (!data.success) {
      +      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,
      +      },
      +    }
      +  },
      +
      +  outputs: {
      +    id: { type: 'string', description: 'Job UUID' },
      +    title: { type: 'string', description: 'Job title' },
      +    status: { type: 'string', description: 'Job status (Open, Closed, Draft, Archived, On Hold)' },
      +    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' },
      +  },
      +}
      diff --git a/apps/sim/tools/ashby/index.ts b/apps/sim/tools/ashby/index.ts
      new file mode 100644
      index 0000000000..5a27818923
      --- /dev/null
      +++ b/apps/sim/tools/ashby/index.ts
      @@ -0,0 +1,27 @@
      +import { createApplicationTool } from '@/tools/ashby/create_application'
      +import { createCandidateTool } from '@/tools/ashby/create_candidate'
      +import { createNoteTool } from '@/tools/ashby/create_note'
      +import { getApplicationTool } from '@/tools/ashby/get_application'
      +import { getCandidateTool } from '@/tools/ashby/get_candidate'
      +import { getJobTool } from '@/tools/ashby/get_job'
      +import { listApplicationsTool } from '@/tools/ashby/list_applications'
      +import { listCandidatesTool } from '@/tools/ashby/list_candidates'
      +import { listJobsTool } from '@/tools/ashby/list_jobs'
      +import { listNotesTool } from '@/tools/ashby/list_notes'
      +import { listOffersTool } from '@/tools/ashby/list_offers'
      +import { searchCandidatesTool } from '@/tools/ashby/search_candidates'
      +import { updateCandidateTool } from '@/tools/ashby/update_candidate'
      +
      +export const ashbyCreateApplicationTool = createApplicationTool
      +export const ashbyCreateCandidateTool = createCandidateTool
      +export const ashbyCreateNoteTool = createNoteTool
      +export const ashbyGetApplicationTool = getApplicationTool
      +export const ashbyGetCandidateTool = getCandidateTool
      +export const ashbyGetJobTool = getJobTool
      +export const ashbyListApplicationsTool = listApplicationsTool
      +export const ashbyListCandidatesTool = listCandidatesTool
      +export const ashbyListJobsTool = listJobsTool
      +export const ashbyListNotesTool = listNotesTool
      +export const ashbyListOffersTool = listOffersTool
      +export const ashbySearchCandidatesTool = searchCandidatesTool
      +export const ashbyUpdateCandidateTool = updateCandidateTool
      diff --git a/apps/sim/tools/ashby/list_applications.ts b/apps/sim/tools/ashby/list_applications.ts
      new file mode 100644
      index 0000000000..f474c226df
      --- /dev/null
      +++ b/apps/sim/tools/ashby/list_applications.ts
      @@ -0,0 +1,193 @@
      +import type { ToolConfig } from '@/tools/types'
      +import type { AshbyListApplicationsParams, AshbyListApplicationsResponse } from './types'
      +
      +export const listApplicationsTool: ToolConfig<
      +  AshbyListApplicationsParams,
      +  AshbyListApplicationsResponse
      +> = {
      +  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.',
      +  version: '1.0.0',
      +
      +  params: {
      +    apiKey: {
      +      type: 'string',
      +      required: true,
      +      visibility: 'user-only',
      +      description: 'Ashby API Key',
      +    },
      +    cursor: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Opaque pagination cursor from a previous response nextCursor value',
      +    },
      +    perPage: {
      +      type: 'number',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Number of results per page (default 100)',
      +    },
      +    status: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Filter by application status: Active, Hired, Archived, or Lead',
      +    },
      +    jobId: {
      +      type: 'string',
      +      required: false,
      +      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,
      +      visibility: 'user-or-llm',
      +      description:
      +        'Filter to applications created after this ISO 8601 timestamp (e.g. 2024-01-01T00:00:00Z)',
      +    },
      +  },
      +
      +  request: {
      +    url: 'https://api.ashbyhq.com/application.list',
      +    method: 'POST',
      +    headers: (params) => ({
      +      'Content-Type': 'application/json',
      +      Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
      +    }),
      +    body: (params) => {
      +      const body: Record = {}
      +      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 = params.createdAfter
      +      return body
      +    },
      +  },
      +
      +  transformResponse: async (response: Response) => {
      +    const data = await response.json()
      +
      +    if (!data.success) {
      +      throw new Error(data.errorInfo?.message || 'Failed to list applications')
      +    }
      +
      +    return {
      +      success: true,
      +      output: {
      +        applications: (data.results ?? []).map(
      +          (
      +            a: Record & {
      +              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,
      +          })
      +        ),
      +        moreDataAvailable: data.moreDataAvailable ?? false,
      +        nextCursor: data.nextCursor ?? null,
      +      },
      +    }
      +  },
      +
      +  outputs: {
      +    applications: {
      +      type: 'array',
      +      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' },
      +        },
      +      },
      +    },
      +    moreDataAvailable: {
      +      type: 'boolean',
      +      description: 'Whether more pages of results exist',
      +    },
      +    nextCursor: {
      +      type: 'string',
      +      description: 'Opaque cursor for fetching the next page',
      +      optional: true,
      +    },
      +  },
      +}
      diff --git a/apps/sim/tools/ashby/list_candidates.ts b/apps/sim/tools/ashby/list_candidates.ts
      new file mode 100644
      index 0000000000..c643a96674
      --- /dev/null
      +++ b/apps/sim/tools/ashby/list_candidates.ts
      @@ -0,0 +1,136 @@
      +import type { ToolConfig } from '@/tools/types'
      +import type { AshbyListCandidatesParams, AshbyListCandidatesResponse } from './types'
      +
      +export const listCandidatesTool: ToolConfig<
      +  AshbyListCandidatesParams,
      +  AshbyListCandidatesResponse
      +> = {
      +  id: 'ashby_list_candidates',
      +  name: 'Ashby List Candidates',
      +  description: 'Lists all candidates in an Ashby organization with cursor-based pagination.',
      +  version: '1.0.0',
      +
      +  params: {
      +    apiKey: {
      +      type: 'string',
      +      required: true,
      +      visibility: 'user-only',
      +      description: 'Ashby API Key',
      +    },
      +    cursor: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Opaque pagination cursor from a previous response nextCursor value',
      +    },
      +    perPage: {
      +      type: 'number',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Number of results per page (default 100)',
      +    },
      +  },
      +
      +  request: {
      +    url: 'https://api.ashbyhq.com/candidate.list',
      +    method: 'POST',
      +    headers: (params) => ({
      +      'Content-Type': 'application/json',
      +      Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
      +    }),
      +    body: (params) => {
      +      const body: Record = {}
      +      if (params.cursor) body.cursor = params.cursor
      +      if (params.perPage) body.limit = params.perPage
      +      return body
      +    },
      +  },
      +
      +  transformResponse: async (response: Response) => {
      +    const data = await response.json()
      +
      +    if (!data.success) {
      +      throw new Error(data.errorInfo?.message || 'Failed to list candidates')
      +    }
      +
      +    return {
      +      success: true,
      +      output: {
      +        candidates: (data.results ?? []).map(
      +          (
      +            c: Record & {
      +              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,
      +          })
      +        ),
      +        moreDataAvailable: data.moreDataAvailable ?? false,
      +        nextCursor: data.nextCursor ?? null,
      +      },
      +    }
      +  },
      +
      +  outputs: {
      +    candidates: {
      +      type: 'array',
      +      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' },
      +        },
      +      },
      +    },
      +    moreDataAvailable: {
      +      type: 'boolean',
      +      description: 'Whether more pages of results exist',
      +    },
      +    nextCursor: {
      +      type: 'string',
      +      description: 'Opaque cursor for fetching the next page',
      +      optional: true,
      +    },
      +  },
      +}
      diff --git a/apps/sim/tools/ashby/list_jobs.ts b/apps/sim/tools/ashby/list_jobs.ts
      new file mode 100644
      index 0000000000..e205cce39a
      --- /dev/null
      +++ b/apps/sim/tools/ashby/list_jobs.ts
      @@ -0,0 +1,112 @@
      +import type { ToolConfig } from '@/tools/types'
      +import type { AshbyListJobsParams, AshbyListJobsResponse } from './types'
      +
      +export const listJobsTool: ToolConfig = {
      +  id: 'ashby_list_jobs',
      +  name: 'Ashby List Jobs',
      +  description:
      +    'Lists all jobs in an Ashby organization. By default returns Open, Closed, and Archived jobs. Specify status to filter.',
      +  version: '1.0.0',
      +
      +  params: {
      +    apiKey: {
      +      type: 'string',
      +      required: true,
      +      visibility: 'user-only',
      +      description: 'Ashby API Key',
      +    },
      +    cursor: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Opaque pagination cursor from a previous response nextCursor value',
      +    },
      +    perPage: {
      +      type: 'number',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Number of results per page (default 100)',
      +    },
      +    status: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Filter by job status: Open, Closed, Archived, or Draft',
      +    },
      +  },
      +
      +  request: {
      +    url: 'https://api.ashbyhq.com/job.list',
      +    method: 'POST',
      +    headers: (params) => ({
      +      'Content-Type': 'application/json',
      +      Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
      +    }),
      +    body: (params) => {
      +      const body: Record = {}
      +      if (params.cursor) body.cursor = params.cursor
      +      if (params.perPage) body.limit = params.perPage
      +      if (params.status) body.status = [params.status]
      +      return body
      +    },
      +  },
      +
      +  transformResponse: async (response: Response) => {
      +    const data = await response.json()
      +
      +    if (!data.success) {
      +      throw new Error(data.errorInfo?.message || 'Failed to list jobs')
      +    }
      +
      +    return {
      +      success: true,
      +      output: {
      +        jobs: (data.results ?? []).map((j: Record) => ({
      +          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,
      +        })),
      +        moreDataAvailable: data.moreDataAvailable ?? false,
      +        nextCursor: data.nextCursor ?? null,
      +      },
      +    }
      +  },
      +
      +  outputs: {
      +    jobs: {
      +      type: 'array',
      +      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' },
      +        },
      +      },
      +    },
      +    moreDataAvailable: {
      +      type: 'boolean',
      +      description: 'Whether more pages of results exist',
      +    },
      +    nextCursor: {
      +      type: 'string',
      +      description: 'Opaque cursor for fetching the next page',
      +      optional: true,
      +    },
      +  },
      +}
      diff --git a/apps/sim/tools/ashby/list_notes.ts b/apps/sim/tools/ashby/list_notes.ts
      new file mode 100644
      index 0000000000..d6f183f181
      --- /dev/null
      +++ b/apps/sim/tools/ashby/list_notes.ts
      @@ -0,0 +1,147 @@
      +import type { ToolConfig, ToolResponse } from '@/tools/types'
      +
      +interface AshbyListNotesParams {
      +  apiKey: string
      +  candidateId: string
      +  cursor?: string
      +  perPage?: number
      +}
      +
      +interface AshbyListNotesResponse extends ToolResponse {
      +  output: {
      +    notes: Array<{
      +      id: string
      +      content: string
      +      author: {
      +        id: string
      +        firstName: string
      +        lastName: string
      +        email: string
      +      } | null
      +      createdAt: string
      +    }>
      +    moreDataAvailable: boolean
      +    nextCursor: string | null
      +  }
      +}
      +
      +export const listNotesTool: ToolConfig = {
      +  id: 'ashby_list_notes',
      +  name: 'Ashby List Notes',
      +  description: 'Lists all notes on a candidate with pagination support.',
      +  version: '1.0.0',
      +
      +  params: {
      +    apiKey: {
      +      type: 'string',
      +      required: true,
      +      visibility: 'user-only',
      +      description: 'Ashby API Key',
      +    },
      +    candidateId: {
      +      type: 'string',
      +      required: true,
      +      visibility: 'user-or-llm',
      +      description: 'The UUID of the candidate to list notes for',
      +    },
      +    cursor: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Opaque pagination cursor from a previous response nextCursor value',
      +    },
      +    perPage: {
      +      type: 'number',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Number of results per page',
      +    },
      +  },
      +
      +  request: {
      +    url: 'https://api.ashbyhq.com/candidate.listNotes',
      +    method: 'POST',
      +    headers: (params) => ({
      +      'Content-Type': 'application/json',
      +      Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
      +    }),
      +    body: (params) => {
      +      const body: Record = {
      +        candidateId: params.candidateId,
      +      }
      +      if (params.cursor) body.cursor = params.cursor
      +      if (params.perPage) body.limit = params.perPage
      +      return body
      +    },
      +  },
      +
      +  transformResponse: async (response: Response) => {
      +    const data = await response.json()
      +
      +    if (!data.success) {
      +      throw new Error(data.errorInfo?.message || 'Failed to list notes')
      +    }
      +
      +    return {
      +      success: true,
      +      output: {
      +        notes: (data.results ?? []).map(
      +          (
      +            n: Record & {
      +              author?: { id?: string; firstName?: string; lastName?: string; email?: string }
      +            }
      +          ) => ({
      +            id: n.id ?? null,
      +            content: n.content ?? null,
      +            author: n.author
      +              ? {
      +                  id: n.author.id ?? null,
      +                  firstName: n.author.firstName ?? null,
      +                  lastName: n.author.lastName ?? null,
      +                  email: n.author.email ?? null,
      +                }
      +              : null,
      +            createdAt: n.createdAt ?? null,
      +          })
      +        ),
      +        moreDataAvailable: data.moreDataAvailable ?? false,
      +        nextCursor: data.nextCursor ?? null,
      +      },
      +    }
      +  },
      +
      +  outputs: {
      +    notes: {
      +      type: 'array',
      +      description: 'List of notes on the candidate',
      +      items: {
      +        type: 'object',
      +        properties: {
      +          id: { type: 'string', description: 'Note UUID' },
      +          content: { type: 'string', description: 'Note content' },
      +          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' },
      +            },
      +          },
      +          createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
      +        },
      +      },
      +    },
      +    moreDataAvailable: {
      +      type: 'boolean',
      +      description: 'Whether more pages of results exist',
      +    },
      +    nextCursor: {
      +      type: 'string',
      +      description: 'Opaque cursor for fetching the next page',
      +      optional: true,
      +    },
      +  },
      +}
      diff --git a/apps/sim/tools/ashby/list_offers.ts b/apps/sim/tools/ashby/list_offers.ts
      new file mode 100644
      index 0000000000..182e3dc0fe
      --- /dev/null
      +++ b/apps/sim/tools/ashby/list_offers.ts
      @@ -0,0 +1,155 @@
      +import type { ToolConfig, ToolResponse } from '@/tools/types'
      +
      +interface AshbyListOffersParams {
      +  apiKey: string
      +  cursor?: string
      +  perPage?: number
      +}
      +
      +interface AshbyListOffersResponse extends ToolResponse {
      +  output: {
      +    offers: Array<{
      +      id: string
      +      status: string
      +      candidate: {
      +        id: string
      +        name: string
      +      } | null
      +      job: {
      +        id: string
      +        title: string
      +      } | null
      +      createdAt: string
      +      updatedAt: string
      +    }>
      +    moreDataAvailable: boolean
      +    nextCursor: string | null
      +  }
      +}
      +
      +export const listOffersTool: ToolConfig = {
      +  id: 'ashby_list_offers',
      +  name: 'Ashby List Offers',
      +  description: 'Lists all offers with their latest version in an Ashby organization.',
      +  version: '1.0.0',
      +
      +  params: {
      +    apiKey: {
      +      type: 'string',
      +      required: true,
      +      visibility: 'user-only',
      +      description: 'Ashby API Key',
      +    },
      +    cursor: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Opaque pagination cursor from a previous response nextCursor value',
      +    },
      +    perPage: {
      +      type: 'number',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Number of results per page',
      +    },
      +  },
      +
      +  request: {
      +    url: 'https://api.ashbyhq.com/offer.list',
      +    method: 'POST',
      +    headers: (params) => ({
      +      'Content-Type': 'application/json',
      +      Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
      +    }),
      +    body: (params) => {
      +      const body: Record = {}
      +      if (params.cursor) body.cursor = params.cursor
      +      if (params.perPage) body.limit = params.perPage
      +      return body
      +    },
      +  },
      +
      +  transformResponse: async (response: Response) => {
      +    const data = await response.json()
      +
      +    if (!data.success) {
      +      throw new Error(data.errorInfo?.message || 'Failed to list offers')
      +    }
      +
      +    return {
      +      success: true,
      +      output: {
      +        offers: (data.results ?? []).map(
      +          (
      +            o: Record & {
      +              candidate?: { id?: string; name?: string }
      +              job?: { id?: string; title?: string }
      +            }
      +          ) => ({
      +            id: o.id ?? null,
      +            status: o.status ?? o.offerStatus ?? null,
      +            candidate: o.candidate
      +              ? {
      +                  id: o.candidate.id ?? null,
      +                  name: o.candidate.name ?? null,
      +                }
      +              : null,
      +            job: o.job
      +              ? {
      +                  id: o.job.id ?? null,
      +                  title: o.job.title ?? null,
      +                }
      +              : null,
      +            createdAt: o.createdAt ?? null,
      +            updatedAt: o.updatedAt ?? null,
      +          })
      +        ),
      +        moreDataAvailable: data.moreDataAvailable ?? false,
      +        nextCursor: data.nextCursor ?? null,
      +      },
      +    }
      +  },
      +
      +  outputs: {
      +    offers: {
      +      type: 'array',
      +      description: 'List of offers',
      +      items: {
      +        type: 'object',
      +        properties: {
      +          id: { type: 'string', description: 'Offer UUID' },
      +          status: { type: 'string', description: 'Offer status' },
      +          candidate: {
      +            type: 'object',
      +            description: 'Associated candidate',
      +            optional: true,
      +            properties: {
      +              id: { type: 'string', description: 'Candidate UUID' },
      +              name: { type: 'string', description: 'Candidate name' },
      +            },
      +          },
      +          job: {
      +            type: 'object',
      +            description: 'Associated job',
      +            optional: true,
      +            properties: {
      +              id: { type: 'string', description: 'Job UUID' },
      +              title: { type: 'string', description: 'Job title' },
      +            },
      +          },
      +          createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
      +          updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp' },
      +        },
      +      },
      +    },
      +    moreDataAvailable: {
      +      type: 'boolean',
      +      description: 'Whether more pages of results exist',
      +    },
      +    nextCursor: {
      +      type: 'string',
      +      description: 'Opaque cursor for fetching the next page',
      +      optional: true,
      +    },
      +  },
      +}
      diff --git a/apps/sim/tools/ashby/search_candidates.ts b/apps/sim/tools/ashby/search_candidates.ts
      new file mode 100644
      index 0000000000..fd272cc9af
      --- /dev/null
      +++ b/apps/sim/tools/ashby/search_candidates.ts
      @@ -0,0 +1,122 @@
      +import type { ToolConfig } from '@/tools/types'
      +import type { AshbySearchCandidatesParams, AshbySearchCandidatesResponse } from './types'
      +
      +export const searchCandidatesTool: ToolConfig<
      +  AshbySearchCandidatesParams,
      +  AshbySearchCandidatesResponse
      +> = {
      +  id: 'ashby_search_candidates',
      +  name: 'Ashby Search Candidates',
      +  description:
      +    'Searches for candidates by name and/or email with AND logic. Results are limited to 100 matches. Use candidate.list for full pagination.',
      +  version: '1.0.0',
      +
      +  params: {
      +    apiKey: {
      +      type: 'string',
      +      required: true,
      +      visibility: 'user-only',
      +      description: 'Ashby API Key',
      +    },
      +    name: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Candidate name to search for (combined with email using AND logic)',
      +    },
      +    email: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Candidate email to search for (combined with name using AND logic)',
      +    },
      +  },
      +
      +  request: {
      +    url: 'https://api.ashbyhq.com/candidate.search',
      +    method: 'POST',
      +    headers: (params) => ({
      +      'Content-Type': 'application/json',
      +      Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
      +    }),
      +    body: (params) => {
      +      const body: Record = {}
      +      if (params.name) body.name = params.name
      +      if (params.email) body.email = params.email
      +      return body
      +    },
      +  },
      +
      +  transformResponse: async (response: Response) => {
      +    const data = await response.json()
      +
      +    if (!data.success) {
      +      throw new Error(data.errorInfo?.message || 'Failed to search candidates')
      +    }
      +
      +    return {
      +      success: true,
      +      output: {
      +        candidates: (data.results ?? []).map(
      +          (
      +            c: Record & {
      +              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,
      +          })
      +        ),
      +      },
      +    }
      +  },
      +
      +  outputs: {
      +    candidates: {
      +      type: 'array',
      +      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' },
      +            },
      +          },
      +        },
      +      },
      +    },
      +  },
      +}
      diff --git a/apps/sim/tools/ashby/types.ts b/apps/sim/tools/ashby/types.ts
      new file mode 100644
      index 0000000000..22103a4f72
      --- /dev/null
      +++ b/apps/sim/tools/ashby/types.ts
      @@ -0,0 +1,192 @@
      +import type { ToolResponse } from '@/tools/types'
      +
      +export interface AshbyBaseParams {
      +  apiKey: string
      +}
      +
      +export interface AshbyContactInfo {
      +  value: string
      +  type: string
      +  isPrimary: boolean
      +}
      +
      +export interface AshbyListCandidatesParams extends AshbyBaseParams {
      +  cursor?: string
      +  perPage?: number
      +}
      +
      +export interface AshbyGetCandidateParams extends AshbyBaseParams {
      +  candidateId: string
      +}
      +
      +export interface AshbyCreateCandidateParams extends AshbyBaseParams {
      +  name: string
      +  email?: string
      +  emailType?: string
      +  phoneNumber?: string
      +  phoneType?: string
      +  linkedInUrl?: string
      +  githubUrl?: string
      +  sourceId?: string
      +}
      +
      +export interface AshbySearchCandidatesParams extends AshbyBaseParams {
      +  name?: string
      +  email?: string
      +}
      +
      +export interface AshbyListJobsParams extends AshbyBaseParams {
      +  cursor?: string
      +  perPage?: number
      +  status?: string
      +}
      +
      +export interface AshbyGetJobParams extends AshbyBaseParams {
      +  jobId: string
      +}
      +
      +export interface AshbyCreateNoteParams extends AshbyBaseParams {
      +  candidateId: string
      +  note: string
      +  noteType?: string
      +  sendNotifications?: boolean
      +}
      +
      +export interface AshbyListApplicationsParams extends AshbyBaseParams {
      +  cursor?: string
      +  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
      +    }>
      +    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
      +  }
      +}
      +
      +export interface AshbyCreateCandidateResponse extends ToolResponse {
      +  output: {
      +    id: string
      +    name: string
      +    primaryEmailAddress: AshbyContactInfo | null
      +    primaryPhoneNumber: AshbyContactInfo | null
      +    createdAt: string
      +  }
      +}
      +
      +export interface AshbySearchCandidatesResponse extends ToolResponse {
      +  output: {
      +    candidates: Array<{
      +      id: string
      +      name: string
      +      primaryEmailAddress: AshbyContactInfo | null
      +      primaryPhoneNumber: AshbyContactInfo | 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
      +    }>
      +    moreDataAvailable: boolean
      +    nextCursor: string | null
      +  }
      +}
      +
      +export interface AshbyGetJobResponse extends ToolResponse {
      +  output: {
      +    id: string
      +    title: string
      +    status: string
      +    employmentType: string | null
      +    departmentId: string | null
      +    locationId: string | null
      +    descriptionPlain: string | null
      +    isArchived: boolean
      +    createdAt: string
      +    updatedAt: string
      +  }
      +}
      +
      +export interface AshbyCreateNoteResponse extends ToolResponse {
      +  output: {
      +    id: string
      +    content: string
      +    author: {
      +      id: string
      +      firstName: string
      +      lastName: string
      +      email: string
      +    } | null
      +    createdAt: string
      +  }
      +}
      +
      +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
      +    }>
      +    moreDataAvailable: boolean
      +    nextCursor: string | null
      +  }
      +}
      diff --git a/apps/sim/tools/ashby/update_candidate.ts b/apps/sim/tools/ashby/update_candidate.ts
      new file mode 100644
      index 0000000000..d9af4c2655
      --- /dev/null
      +++ b/apps/sim/tools/ashby/update_candidate.ts
      @@ -0,0 +1,231 @@
      +import type { ToolConfig } from '@/tools/types'
      +import type { AshbyGetCandidateResponse } from './types'
      +
      +interface AshbyUpdateCandidateParams {
      +  apiKey: string
      +  candidateId: string
      +  name?: string
      +  email?: string
      +  emailType?: string
      +  phoneNumber?: string
      +  phoneType?: string
      +  linkedInUrl?: string
      +  githubUrl?: string
      +  websiteUrl?: string
      +  sourceId?: string
      +}
      +
      +export const updateCandidateTool: ToolConfig<
      +  AshbyUpdateCandidateParams,
      +  AshbyGetCandidateResponse
      +> = {
      +  id: 'ashby_update_candidate',
      +  name: 'Ashby Update Candidate',
      +  description: 'Updates an existing candidate record in Ashby. Only provided fields are changed.',
      +  version: '1.0.0',
      +
      +  params: {
      +    apiKey: {
      +      type: 'string',
      +      required: true,
      +      visibility: 'user-only',
      +      description: 'Ashby API Key',
      +    },
      +    candidateId: {
      +      type: 'string',
      +      required: true,
      +      visibility: 'user-or-llm',
      +      description: 'The UUID of the candidate to update',
      +    },
      +    name: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Updated full name',
      +    },
      +    email: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Updated primary email address',
      +    },
      +    emailType: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Email address type: Personal, Work, or Other (default Work)',
      +    },
      +    phoneNumber: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Updated primary phone number',
      +    },
      +    phoneType: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Phone number type: Personal, Work, or Other (default Work)',
      +    },
      +    linkedInUrl: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'LinkedIn profile URL',
      +    },
      +    githubUrl: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'GitHub profile URL',
      +    },
      +    websiteUrl: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'Personal website URL',
      +    },
      +    sourceId: {
      +      type: 'string',
      +      required: false,
      +      visibility: 'user-or-llm',
      +      description: 'UUID of the source to attribute the candidate to',
      +    },
      +  },
      +
      +  request: {
      +    url: 'https://api.ashbyhq.com/candidate.update',
      +    method: 'POST',
      +    headers: (params) => ({
      +      'Content-Type': 'application/json',
      +      Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
      +    }),
      +    body: (params) => {
      +      const body: Record = {
      +        candidateId: params.candidateId,
      +      }
      +      if (params.name) body.name = params.name
      +      if (params.email) {
      +        body.primaryEmailAddress = {
      +          value: params.email,
      +          type: params.emailType || 'Work',
      +          isPrimary: true,
      +        }
      +      }
      +      if (params.phoneNumber) {
      +        body.primaryPhoneNumber = {
      +          value: params.phoneNumber,
      +          type: params.phoneType || 'Work',
      +          isPrimary: true,
      +        }
      +      }
      +      if (params.linkedInUrl || params.githubUrl || params.websiteUrl) {
      +        const socialLinks: Array<{ url: string; type: string }> = []
      +        if (params.linkedInUrl) socialLinks.push({ url: params.linkedInUrl, type: 'LinkedIn' })
      +        if (params.githubUrl) socialLinks.push({ url: params.githubUrl, type: 'GitHub' })
      +        if (params.websiteUrl) socialLinks.push({ url: params.websiteUrl, type: 'Website' })
      +        body.socialLinks = socialLinks
      +      }
      +      if (params.sourceId) body.sourceId = params.sourceId
      +      return body
      +    },
      +  },
      +
      +  transformResponse: async (response: Response) => {
      +    const data = await response.json()
      +
      +    if (!data.success) {
      +      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,
      +      },
      +    }
      +  },
      +
      +  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' },
      +  },
      +}
      diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts
      index d8302770c3..beb652da27 100644
      --- a/apps/sim/tools/registry.ts
      +++ b/apps/sim/tools/registry.ts
      @@ -79,6 +79,21 @@ import {
         asanaSearchTasksTool,
         asanaUpdateTaskTool,
       } from '@/tools/asana'
      +import {
      +  ashbyCreateApplicationTool,
      +  ashbyCreateCandidateTool,
      +  ashbyCreateNoteTool,
      +  ashbyGetApplicationTool,
      +  ashbyGetCandidateTool,
      +  ashbyGetJobTool,
      +  ashbyListApplicationsTool,
      +  ashbyListCandidatesTool,
      +  ashbyListJobsTool,
      +  ashbyListNotesTool,
      +  ashbyListOffersTool,
      +  ashbySearchCandidatesTool,
      +  ashbyUpdateCandidateTool,
      +} from '@/tools/ashby'
       import {
         attioAssertRecordTool,
         attioCreateCommentTool,
      @@ -2133,6 +2148,19 @@ export const tools: Record = {
         a2a_send_message: a2aSendMessageTool,
         a2a_set_push_notification: a2aSetPushNotificationTool,
         airweave_search: airweaveSearchTool,
      +  ashby_create_application: ashbyCreateApplicationTool,
      +  ashby_create_candidate: ashbyCreateCandidateTool,
      +  ashby_create_note: ashbyCreateNoteTool,
      +  ashby_get_application: ashbyGetApplicationTool,
      +  ashby_get_candidate: ashbyGetCandidateTool,
      +  ashby_get_job: ashbyGetJobTool,
      +  ashby_list_applications: ashbyListApplicationsTool,
      +  ashby_list_candidates: ashbyListCandidatesTool,
      +  ashby_list_jobs: ashbyListJobsTool,
      +  ashby_list_notes: ashbyListNotesTool,
      +  ashby_list_offers: ashbyListOffersTool,
      +  ashby_search_candidates: ashbySearchCandidatesTool,
      +  ashby_update_candidate: ashbyUpdateCandidateTool,
         arxiv_search: arxivSearchTool,
         arxiv_get_paper: arxivGetPaperTool,
         arxiv_get_author_papers: arxivGetAuthorPapersTool,
      
      From 5eaa2b8c760e28db4044d9c272bd64bf5e6c9313 Mon Sep 17 00:00:00 2001
      From: Waleed Latif 
      Date: Thu, 26 Feb 2026 18:52:42 -0800
      Subject: [PATCH 2/2] fix(ashby): auto-fix lint formatting in docs files
      
      ---
       apps/docs/components/ui/icon-mapping.ts   | 12 ++++++------
       apps/docs/content/docs/en/tools/meta.json |  2 +-
       2 files changed, 7 insertions(+), 7 deletions(-)
      
      diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts
      index 5c2a3fd9d1..e81be616e0 100644
      --- a/apps/docs/components/ui/icon-mapping.ts
      +++ b/apps/docs/components/ui/icon-mapping.ts
      @@ -40,8 +40,8 @@ import {
         EyeIcon,
         FirecrawlIcon,
         FirefliesIcon,
      -  GitLabIcon,
         GithubIcon,
      +  GitLabIcon,
         GmailIcon,
         GongIcon,
         GoogleBigQueryIcon,
      @@ -77,9 +77,9 @@ import {
         LinearIcon,
         LinkedInIcon,
         LinkupIcon,
      -  MailServerIcon,
         MailchimpIcon,
         MailgunIcon,
      +  MailServerIcon,
         Mem0Icon,
         MicrosoftDataverseIcon,
         MicrosoftExcelIcon,
      @@ -112,8 +112,6 @@ import {
         ResendIcon,
         RevenueCatIcon,
         S3Icon,
      -  SQSIcon,
      -  STTIcon,
         SalesforceIcon,
         SearchIcon,
         SendgridIcon,
      @@ -125,17 +123,19 @@ import {
         SimilarwebIcon,
         SlackIcon,
         SmtpIcon,
      +  SQSIcon,
         SshIcon,
      +  STTIcon,
         StagehandIcon,
         StripeIcon,
         SupabaseIcon,
      -  TTSIcon,
         TavilyIcon,
         TelegramIcon,
         TextractIcon,
         TinybirdIcon,
         TranslateIcon,
         TrelloIcon,
      +  TTSIcon,
         TwilioIcon,
         TypeformIcon,
         UpstashIcon,
      @@ -146,11 +146,11 @@ import {
         WhatsAppIcon,
         WikipediaIcon,
         WordpressIcon,
      +  xIcon,
         YouTubeIcon,
         ZendeskIcon,
         ZepIcon,
         ZoomIcon,
      -  xIcon,
       } from '@/components/icons'
       
       type IconComponent = ComponentType>
      diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json
      index b67889770a..b5f77efb4a 100644
      --- a/apps/docs/content/docs/en/tools/meta.json
      +++ b/apps/docs/content/docs/en/tools/meta.json
      @@ -151,4 +151,4 @@
           "zep",
           "zoom"
         ]
      -}
      \ No newline at end of file
      +}