feat(ashby): add ashby integration for candidate, job, and application management (#3362)

* feat(ashby): add ashby integration for candidate, job, and application management

* fix(ashby): auto-fix lint formatting in docs files
This commit is contained in:
Waleed
2026-02-26 19:10:06 -08:00
committed by GitHub
parent d4f8ac8107
commit bf0e25c9d0
23 changed files with 3182 additions and 0 deletions
+13
View File
@@ -2973,6 +2973,19 @@ export function QdrantIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function AshbyIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 254 260' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M76.07 250.537v9.16H.343v-9.16c19.618 0 27.465-4.381 34.527-23.498l73.764-209.09h34.92l81.219 209.09c7.847 19.515 11.77 23.498 28.642 23.498v9.16H134.363v-9.16c28.242 0 30.625-2.582 22.14-23.498l-21.58-57.35H69.399l-19.226 56.155c-5.614 18.997-4.387 24.693 25.896 24.693zm24.326-171.653l-26.681 78.459h56.5l-29.819-78.459z'
fill='currentColor'
/>
</svg>
)
}
export function ArxivIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} id='logomark' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 17.732 24.269'>
+2
View File
@@ -13,6 +13,7 @@ import {
ApolloIcon,
ArxivIcon,
AsanaIcon,
AshbyIcon,
AttioIcon,
BrainIcon,
BrowserUseIcon,
@@ -168,6 +169,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
apollo: ApolloIcon,
arxiv: ArxivIcon,
asana: AsanaIcon,
ashby: AshbyIcon,
attio: AttioIcon,
browser_use: BrowserUseIcon,
calcom: CalComIcon,
+473
View File
@@ -0,0 +1,473 @@
---
title: Ashby
description: Manage candidates, jobs, and applications in Ashby
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="ashby"
color="#5D4ED6"
/>
{/* 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: &lt;b&gt;, &lt;i&gt;, &lt;u&gt;, &lt;a&gt;, &lt;ul&gt;, &lt;ol&gt;, &lt;li&gt;, &lt;code&gt;, &lt;pre&gt; |
| `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 |
@@ -10,6 +10,7 @@
"apollo",
"arxiv",
"asana",
"ashby",
"attio",
"browser_use",
"calcom",
+460
View File
@@ -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<string, unknown> = {}
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' },
},
}
+2
View File
@@ -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'
@@ -195,6 +196,7 @@ export const registry: Record<string, BlockConfig> = {
apify: ApifyBlock,
apollo: ApolloBlock,
arxiv: ArxivBlock,
ashby: AshbyBlock,
asana: AsanaBlock,
attio: AttioBlock,
browser_use: BrowserUseBlock,
+13
View File
@@ -2973,6 +2973,19 @@ export function QdrantIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function AshbyIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 254 260' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M76.07 250.537v9.16H.343v-9.16c19.618 0 27.465-4.381 34.527-23.498l73.764-209.09h34.92l81.219 209.09c7.847 19.515 11.77 23.498 28.642 23.498v9.16H134.363v-9.16c28.242 0 30.625-2.582 22.14-23.498l-21.58-57.35H69.399l-19.226 56.155c-5.614 18.997-4.387 24.693 25.896 24.693zm24.326-171.653l-26.681 78.459h56.5l-29.819-78.459z'
fill='currentColor'
/>
</svg>
)
}
export function ArxivIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} id='logomark' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 17.732 24.269'>
+205
View File
@@ -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<string, unknown> = {
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' },
},
}
+164
View File
@@ -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<string, unknown> = {
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' },
},
}
+112
View File
@@ -0,0 +1,112 @@
import type { ToolConfig } from '@/tools/types'
import type { AshbyCreateNoteParams, AshbyCreateNoteResponse } from './types'
export const createNoteTool: ToolConfig<AshbyCreateNoteParams, AshbyCreateNoteResponse> = {
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: <b>, <i>, <u>, <a>, <ul>, <ol>, <li>, <code>, <pre>',
},
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<string, unknown> = {
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' },
},
}
+177
View File
@@ -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' },
},
}
+134
View File
@@ -0,0 +1,134 @@
import type { ToolConfig } from '@/tools/types'
import type { AshbyGetCandidateParams, AshbyGetCandidateResponse } from './types'
export const getCandidateTool: ToolConfig<AshbyGetCandidateParams, AshbyGetCandidateResponse> = {
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' },
},
}
+83
View File
@@ -0,0 +1,83 @@
import type { ToolConfig } from '@/tools/types'
import type { AshbyGetJobParams, AshbyGetJobResponse } from './types'
export const getJobTool: ToolConfig<AshbyGetJobParams, AshbyGetJobResponse> = {
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' },
},
}
+27
View File
@@ -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
+193
View File
@@ -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<string, unknown> = {}
if (params.cursor) body.cursor = params.cursor
if (params.perPage) body.limit = params.perPage
if (params.status) body.status = [params.status]
if (params.jobId) body.jobId = params.jobId
if (params.candidateId) body.candidateId = params.candidateId
if (params.createdAfter) body.createdAfter = 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<string, unknown> & {
candidate?: { id?: string; name?: string }
job?: { id?: string; title?: string }
currentInterviewStage?: { id?: string; title?: string; type?: string } | null
source?: { id?: string; title?: string } | null
}
) => ({
id: a.id ?? null,
status: a.status ?? null,
candidate: {
id: a.candidate?.id ?? null,
name: a.candidate?.name ?? null,
},
job: {
id: a.job?.id ?? null,
title: a.job?.title ?? null,
},
currentInterviewStage: a.currentInterviewStage
? {
id: a.currentInterviewStage.id ?? null,
title: a.currentInterviewStage.title ?? null,
type: a.currentInterviewStage.type ?? null,
}
: null,
source: a.source
? {
id: a.source.id ?? null,
title: a.source.title ?? null,
}
: null,
createdAt: a.createdAt ?? null,
updatedAt: a.updatedAt ?? null,
})
),
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,
},
},
}
+136
View File
@@ -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<string, unknown> = {}
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<string, unknown> & {
primaryEmailAddress?: { value?: string; type?: string; isPrimary?: boolean }
primaryPhoneNumber?: { value?: string; type?: string; isPrimary?: boolean }
}
) => ({
id: c.id ?? null,
name: c.name ?? null,
primaryEmailAddress: c.primaryEmailAddress
? {
value: c.primaryEmailAddress.value ?? '',
type: c.primaryEmailAddress.type ?? 'Other',
isPrimary: c.primaryEmailAddress.isPrimary ?? true,
}
: null,
primaryPhoneNumber: c.primaryPhoneNumber
? {
value: c.primaryPhoneNumber.value ?? '',
type: c.primaryPhoneNumber.type ?? 'Other',
isPrimary: c.primaryPhoneNumber.isPrimary ?? true,
}
: null,
createdAt: c.createdAt ?? null,
updatedAt: c.updatedAt ?? null,
})
),
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,
},
},
}
+112
View File
@@ -0,0 +1,112 @@
import type { ToolConfig } from '@/tools/types'
import type { AshbyListJobsParams, AshbyListJobsResponse } from './types'
export const listJobsTool: ToolConfig<AshbyListJobsParams, AshbyListJobsResponse> = {
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<string, unknown> = {}
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<string, unknown>) => ({
id: j.id ?? null,
title: j.title ?? null,
status: j.status ?? null,
employmentType: j.employmentType ?? null,
departmentId: j.departmentId ?? null,
locationId: j.locationId ?? null,
createdAt: j.createdAt ?? null,
updatedAt: j.updatedAt ?? null,
})),
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,
},
},
}
+147
View File
@@ -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<AshbyListNotesParams, AshbyListNotesResponse> = {
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<string, unknown> = {
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<string, unknown> & {
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,
},
},
}
+155
View File
@@ -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<AshbyListOffersParams, AshbyListOffersResponse> = {
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<string, unknown> = {}
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<string, unknown> & {
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,
},
},
}
+122
View File
@@ -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<string, unknown> = {}
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<string, unknown> & {
primaryEmailAddress?: { value?: string; type?: string; isPrimary?: boolean }
primaryPhoneNumber?: { value?: string; type?: string; isPrimary?: boolean }
}
) => ({
id: c.id ?? null,
name: c.name ?? null,
primaryEmailAddress: c.primaryEmailAddress
? {
value: c.primaryEmailAddress.value ?? '',
type: c.primaryEmailAddress.type ?? 'Other',
isPrimary: c.primaryEmailAddress.isPrimary ?? true,
}
: null,
primaryPhoneNumber: c.primaryPhoneNumber
? {
value: c.primaryPhoneNumber.value ?? '',
type: c.primaryPhoneNumber.type ?? 'Other',
isPrimary: c.primaryPhoneNumber.isPrimary ?? true,
}
: null,
})
),
},
}
},
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' },
},
},
},
},
},
},
}
+192
View File
@@ -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
}
}
+231
View File
@@ -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<string, unknown> = {
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' },
},
}
+28
View File
@@ -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,
@@ -2171,6 +2186,19 @@ export const tools: Record<string, ToolConfig> = {
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,