Skip to content

Commit f330fe2

Browse files
waleedlatif1claude
andauthored
refactor(ashby): align tools, block, and triggers with Ashby API (#4288)
* refactor(ashby): align tools, block, and triggers with Ashby API Audit-driven refactor to destructure rich fields per Ashby's API docs, centralize output shapes via shared mappers in tools/ashby/utils.ts, and align webhook provider handler with trigger IDs via a shared action map. Removes stale block outputs left over from prior flat response shapes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(ashby): remove stale noteId output and reject ping events - Remove stale `noteId` output descriptor from block (create_note now returns `id` at the top level via the shared note mapper). - Explicitly reject `ping` events in the webhook matchEvent before falling back to the generic triggerId check, so webhook records missing triggerId cannot execute workflows on Ashby ping probes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(ashby): trim optional ID params in create/update tools Optional ID params in create_application, change_application_stage, and update_candidate were passed through to the request body without .trim(), unlike their required ID siblings. Normalize to prevent copy-paste whitespace errors. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(ashby): add subblock migration for removed filterCandidateId Add SUBBLOCK_ID_MIGRATIONS entry so deployed workflows that previously used the `filterCandidateId` subBlock on `list_applications` don't break after the field was removed (Ashby's application.list doesn't filter by candidateId). Also regenerate docs to sync noteId removal. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(ashby): final API alignment from parallel validation - create_candidate: email is optional per Ashby docs (only name is required); tool, types, and block all made non-required. - list_applications: guard NaN when createdAfter can't be parsed so we don't send a bad value to Ashby's API. - webhook provider: replace createHmacVerifier with explicit fail-closed verifyAuth that 401s when secretToken, signature header, or signature match is missing (was previously fail-open on missing secret). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(ashby): preserve input.data path in webhook formatInput Restore the explicit `data` key alongside the spread so deployed workflows that reference `input.data.application.*`, `input.data.candidate.*`, etc. keep working. The spread alone dropped those paths. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(ashby): drop legacy input.data key from webhook formatInput Keep formatInput aligned with the advertised trigger outputs schema (flat top-level entities) and drop the legacy input.data.* compat path. Every field declared in each trigger's outputs is now populated 1:1 by the data spread plus the explicit action key — no undeclared keys. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(ashby): trim remaining ID body params for parity Add .trim() on sourceId (create_candidate), jobId (list_applications), applicationId and interviewStageId (list_interviews) to match the trim-on-IDs pattern used across the rest of the Ashby tools and guard against copy-paste whitespace. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * update docs --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent efc8682 commit f330fe2

37 files changed

Lines changed: 2850 additions & 1376 deletions

apps/docs/content/docs/en/tools/ashby.mdx

Lines changed: 431 additions & 188 deletions
Large diffs are not rendered by default.

apps/docs/content/docs/en/triggers/ashby.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ Trigger workflow when a candidate is hired
9797
|`job` | object | job output from the tool |
9898
|`id` | string | Job UUID |
9999
|`title` | string | Job title |
100+
| `offer` | object | offer output from the tool |
101+
|`id` | string | Accepted offer UUID |
102+
|`applicationId` | string | Associated application UUID |
103+
|`acceptanceStatus` | string | Offer acceptance status |
104+
|`offerStatus` | string | Offer process status |
105+
|`decidedAt` | string | Offer decision timestamp \(ISO 8601\) |
106+
|`latestVersion` | object | latestVersion output from the tool |
107+
|`id` | string | Latest offer version UUID |
100108

101109

102110
---

apps/sim/app/(landing)/integrations/data/integrations.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,7 +1031,7 @@
10311031
},
10321032
{
10331033
"name": "List Applications",
1034-
"description": "Lists all applications in an Ashby organization with pagination and optional filters for status, job, candidate, and creation date."
1034+
"description": "Lists all applications in an Ashby organization with pagination and optional filters for status, job, and creation date."
10351035
},
10361036
{
10371037
"name": "Get Application",
@@ -1051,11 +1051,11 @@
10511051
},
10521052
{
10531053
"name": "Add Candidate Tag",
1054-
"description": "Adds a tag to a candidate in Ashby."
1054+
"description": "Adds a tag to a candidate in Ashby and returns the updated candidate."
10551055
},
10561056
{
10571057
"name": "Remove Candidate Tag",
1058-
"description": "Removes a tag from a candidate in Ashby."
1058+
"description": "Removes a tag from a candidate in Ashby and returns the updated candidate."
10591059
},
10601060
{
10611061
"name": "Get Offer",

apps/sim/blocks/blocks/ashby.ts

Lines changed: 111 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ export const AshbyBlock: BlockConfig = {
113113
id: 'email',
114114
title: 'Email',
115115
type: 'short-input',
116-
required: { field: 'operation', value: 'create_candidate' },
117116
placeholder: 'Email address',
118117
condition: { field: 'operation', value: ['create_candidate', 'update_candidate'] },
119118
},
@@ -308,14 +307,6 @@ Output only the ISO 8601 timestamp string, nothing else.`,
308307
condition: { field: 'operation', value: 'list_applications' },
309308
mode: 'advanced',
310309
},
311-
{
312-
id: 'filterCandidateId',
313-
title: 'Candidate ID Filter',
314-
type: 'short-input',
315-
placeholder: 'Filter by candidate UUID',
316-
condition: { field: 'operation', value: 'list_applications' },
317-
mode: 'advanced',
318-
},
319310
{
320311
id: 'createdAfter',
321312
title: 'Created After',
@@ -366,6 +357,7 @@ Output only the ISO 8601 timestamp string, nothing else.`,
366357
'list_openings',
367358
'list_users',
368359
'list_interviews',
360+
'list_candidate_tags',
369361
],
370362
},
371363
mode: 'advanced',
@@ -386,10 +378,43 @@ Output only the ISO 8601 timestamp string, nothing else.`,
386378
'list_openings',
387379
'list_users',
388380
'list_interviews',
381+
'list_candidate_tags',
389382
],
390383
},
391384
mode: 'advanced',
392385
},
386+
{
387+
id: 'syncToken',
388+
title: 'Sync Token',
389+
type: 'short-input',
390+
placeholder: 'Sync token for incremental updates',
391+
condition: { field: 'operation', value: 'list_candidate_tags' },
392+
mode: 'advanced',
393+
},
394+
{
395+
id: 'includeArchived',
396+
title: 'Include Archived',
397+
type: 'switch',
398+
condition: {
399+
field: 'operation',
400+
value: ['list_candidate_tags', 'list_archive_reasons'],
401+
},
402+
mode: 'advanced',
403+
},
404+
{
405+
id: 'expandApplicationFormDefinition',
406+
title: 'Include Application Form Definition',
407+
type: 'switch',
408+
condition: { field: 'operation', value: 'get_job_posting' },
409+
mode: 'advanced',
410+
},
411+
{
412+
id: 'expandSurveyFormDefinitions',
413+
title: 'Include Survey Form Definitions',
414+
type: 'switch',
415+
condition: { field: 'operation', value: 'get_job_posting' },
416+
mode: 'advanced',
417+
},
393418
{
394419
id: 'tagId',
395420
title: 'Tag ID',
@@ -476,11 +501,25 @@ Output only the ISO 8601 timestamp string, nothing else.`,
476501
if (params.searchEmail) result.email = params.searchEmail
477502
if (params.filterStatus) result.status = params.filterStatus
478503
if (params.filterJobId) result.jobId = params.filterJobId
479-
if (params.filterCandidateId) result.candidateId = params.filterCandidateId
480504
if (params.jobStatus) result.status = params.jobStatus
481505
if (params.sendNotifications === 'true' || params.sendNotifications === true) {
482506
result.sendNotifications = true
483507
}
508+
if (params.includeArchived === 'true' || params.includeArchived === true) {
509+
result.includeArchived = true
510+
}
511+
if (
512+
params.expandApplicationFormDefinition === 'true' ||
513+
params.expandApplicationFormDefinition === true
514+
) {
515+
result.expandApplicationFormDefinition = true
516+
}
517+
if (
518+
params.expandSurveyFormDefinitions === 'true' ||
519+
params.expandSurveyFormDefinitions === true
520+
) {
521+
result.expandSurveyFormDefinitions = true
522+
}
484523
if (params.appCandidateId) result.candidateId = params.appCandidateId
485524
if (params.appCreatedAt) result.createdAt = params.appCreatedAt
486525
if (params.updateName) result.name = params.updateName
@@ -515,11 +554,20 @@ Output only the ISO 8601 timestamp string, nothing else.`,
515554
sendNotifications: { type: 'boolean', description: 'Send notifications' },
516555
filterStatus: { type: 'string', description: 'Application status filter' },
517556
filterJobId: { type: 'string', description: 'Job UUID filter' },
518-
filterCandidateId: { type: 'string', description: 'Candidate UUID filter' },
519557
createdAfter: { type: 'string', description: 'Filter by creation date' },
520558
jobStatus: { type: 'string', description: 'Job status filter' },
521559
cursor: { type: 'string', description: 'Pagination cursor' },
522560
perPage: { type: 'number', description: 'Results per page' },
561+
syncToken: { type: 'string', description: 'Sync token for incremental updates' },
562+
includeArchived: { type: 'boolean', description: 'Include archived records' },
563+
expandApplicationFormDefinition: {
564+
type: 'boolean',
565+
description: 'Include application form definition in job posting',
566+
},
567+
expandSurveyFormDefinitions: {
568+
type: 'boolean',
569+
description: 'Include survey form definitions in job posting',
570+
},
523571
tagId: { type: 'string', description: 'Tag UUID' },
524572
offerId: { type: 'string', description: 'Offer UUID' },
525573
jobPostingId: { type: 'string', description: 'Job posting UUID' },
@@ -530,93 +578,113 @@ Output only the ISO 8601 timestamp string, nothing else.`,
530578
candidates: {
531579
type: 'json',
532580
description:
533-
'List of candidates (id, name, primaryEmailAddress, primaryPhoneNumber, createdAt, updatedAt)',
581+
'List of candidates with rich fields (id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses[], phoneNumbers[], socialLinks[], linkedInUrl, githubUrl, profileUrl, position, company, school, timezone, location with locationComponents[], tags[], applicationIds[], customFields[], resumeFileHandle, fileHandles[], source with sourceType, creditedToUser, fraudStatus, createdAt, updatedAt)',
534582
},
535583
jobs: {
536584
type: 'json',
537585
description:
538-
'List of jobs (id, title, status, employmentType, departmentId, locationId, createdAt, updatedAt)',
586+
'List of jobs (id, title, confidential, status, employmentType, locationId, departmentId, defaultInterviewPlanId, interviewPlanIds[], customFields[], jobPostingIds[], customRequisitionId, brandId, hiringTeam[], author, createdAt, updatedAt, openedAt, closedAt, location with address, openings[] with latestVersion, compensation with compensationTiers[])',
539587
},
540588
applications: {
541589
type: 'json',
542590
description:
543-
'List of applications (id, status, candidate, job, currentInterviewStage, source, createdAt, updatedAt)',
591+
'List of applications (id, status, customFields[], candidate summary, currentInterviewStage, source with sourceType, archiveReason with customFields[], archivedAt, job summary, creditedToUser, hiringTeam[], appliedViaJobPostingId, submitterClientIp, submitterUserAgent, createdAt, updatedAt)',
544592
},
545593
notes: {
546594
type: 'json',
547-
description: 'List of notes (id, content, author, createdAt)',
595+
description: 'List of notes (id, content, author, isPrivate, createdAt)',
548596
},
549597
offers: {
550598
type: 'json',
551599
description:
552-
'List of offers (id, offerStatus, acceptanceStatus, applicationId, startDate, salary, openingId)',
600+
'List of offers (id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion with id/startDate/salary/createdAt/openingId/customFields[]/fileHandles[]/author/approvalStatus)',
553601
},
554602
archiveReasons: {
555603
type: 'json',
556-
description: 'List of archive reasons (id, text, reasonType, isArchived)',
604+
description:
605+
'List of archive reasons (id, text, reasonType [RejectedByCandidate/RejectedByOrg/Other], isArchived)',
557606
},
558607
sources: {
559608
type: 'json',
560-
description: 'List of sources (id, title, isArchived)',
609+
description: 'List of sources (id, title, isArchived, sourceType {id, title, isArchived})',
561610
},
562611
customFields: {
563612
type: 'json',
564-
description: 'List of custom fields (id, title, fieldType, objectType, isArchived)',
613+
description:
614+
'List of custom field definitions (id, title, isPrivate, fieldType, objectType, isArchived, isRequired, selectableValues[] {label, value, isArchived})',
565615
},
566616
departments: {
567617
type: 'json',
568-
description: 'List of departments (id, name, isArchived, parentId)',
618+
description:
619+
'List of departments (id, name, externalName, isArchived, parentId, createdAt, updatedAt)',
569620
},
570621
locations: {
571622
type: 'json',
572-
description: 'List of locations (id, name, isArchived, isRemote, address)',
623+
description:
624+
'List of locations (id, name, externalName, isArchived, isRemote, workplaceType, parentLocationId, type, address with addressCountry/Region/Locality/postalCode/streetAddress)',
573625
},
574626
jobPostings: {
575627
type: 'json',
576628
description:
577-
'List of job postings (id, title, jobId, locationName, departmentName, employmentType, isListed, publishedDate)',
629+
'List of job postings (id, title, jobId, departmentName, teamName, locationName, locationIds, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensationTierSummary, shouldDisplayCompensationOnJobBoard, updatedAt)',
578630
},
579631
openings: {
580632
type: 'json',
581-
description: 'List of openings (id, openingState, isArchived, openedAt, closedAt)',
633+
description:
634+
'List of openings (id, openedAt, closedAt, isArchived, archivedAt, closeReasonId, openingState, latestVersion with identifier/description/authorId/createdAt/teamId/jobIds[]/targetHireDate/targetStartDate/isBackfill/employmentType/locationIds[]/hiringTeam[]/customFields[])',
582635
},
583636
users: {
584637
type: 'json',
585-
description: 'List of users (id, firstName, lastName, email, isEnabled, globalRole)',
638+
description:
639+
'List of users (id, firstName, lastName, email, globalRole, isEnabled, updatedAt, managerId)',
586640
},
587641
interviewSchedules: {
588642
type: 'json',
589643
description:
590-
'List of interview schedules (id, applicationId, interviewStageId, status, createdAt)',
644+
'List of interview schedules (id, applicationId, interviewStageId, interviewEvents[] with interviewerUserIds/startTime/endTime/feedbackLink/location/meetingLink/hasSubmittedFeedback, status, scheduledBy, createdAt, updatedAt)',
591645
},
592646
tags: {
593647
type: 'json',
594648
description: 'List of candidate tags (id, title, isArchived)',
595649
},
596-
stageId: { type: 'string', description: 'Interview stage UUID after stage change' },
597-
success: { type: 'boolean', description: 'Whether the operation succeeded' },
598-
offerStatus: {
599-
type: 'string',
600-
description: 'Offer status (e.g. WaitingOnCandidateResponse, CandidateAccepted)',
650+
id: { type: 'string', description: 'Resource UUID' },
651+
name: { type: 'string', description: 'Resource name' },
652+
title: { type: 'string', description: 'Job title or job posting title' },
653+
status: { type: 'string', description: 'Status' },
654+
candidate: {
655+
type: 'json',
656+
description:
657+
'Candidate details (id, name, primaryEmailAddress, primaryPhoneNumber, emailAddresses[], phoneNumbers[], socialLinks[], customFields[], source, creditedToUser, createdAt, updatedAt)',
658+
},
659+
job: {
660+
type: 'json',
661+
description:
662+
'Job details (id, title, status, employmentType, locationId, departmentId, hiringTeam[], author, location, openings[], compensation, createdAt, updatedAt)',
601663
},
602-
acceptanceStatus: {
603-
type: 'string',
604-
description: 'Acceptance status (e.g. Accepted, Declined, Pending)',
664+
application: {
665+
type: 'json',
666+
description:
667+
'Application details (id, status, customFields[], candidate, currentInterviewStage, source, archiveReason, job, hiringTeam[], createdAt, updatedAt)',
605668
},
606-
applicationId: { type: 'string', description: 'Associated application UUID' },
607-
openingId: { type: 'string', description: 'Opening UUID associated with the offer' },
608-
salary: {
669+
offer: {
609670
type: 'json',
610-
description: 'Salary details from latest version (currencyCode, value)',
671+
description:
672+
'Offer details (id, decidedAt, applicationId, acceptanceStatus, offerStatus, latestVersion)',
673+
},
674+
jobPosting: {
675+
type: 'json',
676+
description:
677+
'Job posting details (id, title, descriptionPlain, descriptionHtml, descriptionSocial, descriptionParts, departmentName, teamName, teamNameHierarchy[], jobId, locationName, locationIds, linkedData, address, isRemote, workplaceType, employmentType, isListed, publishedDate, applicationDeadline, externalLink, applyLink, compensation, updatedAt)',
611678
},
612-
startDate: { type: 'string', description: 'Offer start date from latest version' },
613-
id: { type: 'string', description: 'Resource UUID' },
614-
name: { type: 'string', description: 'Resource name' },
615-
title: { type: 'string', description: 'Job title' },
616-
status: { type: 'string', description: 'Status' },
617-
noteId: { type: 'string', description: 'Created note UUID' },
618679
content: { type: 'string', description: 'Note content' },
680+
author: {
681+
type: 'json',
682+
description: 'Note author (id, firstName, lastName, email, globalRole, isEnabled)',
683+
},
684+
isPrivate: { type: 'boolean', description: 'Whether the note is private' },
685+
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
619686
moreDataAvailable: { type: 'boolean', description: 'Whether more pages exist' },
620687
nextCursor: { type: 'string', description: 'Pagination cursor for next page' },
688+
syncToken: { type: 'string', description: 'Sync token for incremental updates' },
621689
},
622690
}

0 commit comments

Comments
 (0)