Work an Ashby hiring pipeline — find and create candidates, move applications between stages, read interviews, feedback and offers, and sync it all incrementally.
Ashby ships in the w6w first-party pack. It declares 27 actions, 3 health checks, and the host runs its code in a sandbox that never sees the credential.
io.w6w.ashbyAshby connects a workflow to a company’s hiring pipeline — finding and creating candidates, moving applications through interview stages, and reading feedback, offers and postings as they change. It’s built for the sync case: most list actions accept a token from the previous run and return only what changed, so a nightly export moves a handful of records instead of walking the whole ATS every time.
Beyond straightforward reads and writes, the app understands where an ATS integration usually breaks. A candidate-search action answers “do we already know this person” by email in one call, so a workflow can check before creating and avoid the duplicate records that split a candidate’s interview history. Advancing an application can also mean rejecting it, depending on the stage picked, and job postings are tracked separately from the internal jobs behind them, since a hiring team often runs several public listings off one role.
Ashby’s API returns business errors as HTTP 200 with a success flag rather than a 4xx, so straightforward status-code handling would silently treat a missing candidate or a permission refusal as a success — this app checks that flag first, keeping the failure visible to whatever depends on it. Personal data such as candidate names, notes and offer terms is deliberately kept out of logs.
The app covers candidates, applications, jobs, job postings, interviews, offers and the reference lists behind them — departments, locations, sources, archive reasons — and does not perform destructive operations like deleting or anonymizing a candidate record, which stay in Ashby’s own admin tools.
Three routes to the same 27 actions. The Workflow tab is generated from Ashby's own manifest and carries its real ids, so it is copy-pasteable; the Code and CLI examples are the same call for any action on any app, so every app-specific value in them is a blank you fill in.
api-key-info The scopes granted to the key this connection uses — so a workflow can check before it acts, rather than failing on step four. Requires the `apiKeysRead` scope itself.
application-change-stage Advance or archive an application. Moving to an archived stage IS a rejection and requires a reason; emailing the candidate is off unless you ask for it.
application-feedback-list Scorecards for an application. Selections come back as stored VALUES, not the labels shown in Ashby — so this resolves the labels from the form definition for you.
application-get One application, by its id or by the form-submission id a custom careers page returns — the second removes an id-mapping step. If both are given, Ashby uses the application id.
application-list One candidate considered for one job — the record most recruiting questions are about. Filter by status: unfiltered counts every rejection since the role opened.
application-update Set an application's source and credited user — the fields every sourcing report is built from. Setting `createdAt` also moves the first history event, which fixes migrated data.
archive-reason-list Why candidates get rejected — required to archive an application, and the categories every funnel report groups by. One generic reason for everything destroys that analysis.
candidate-create Add a person to Ashby. It does NOT deduplicate — creating twice with the same email splits one person's history across two records. Search first.
candidate-get One candidate, by Ashby id or by an id your own system assigned — the second removes the need for a mapping table nobody maintains.
candidate-list Walk every candidate, or — with a sync token — only those changed since the last run. The token arrives on the LAST page, so a truncated run does not get one.
candidate-note-create Write on a candidate's profile — how an automation tells the recruiter something where they will actually see it. Private notes need a key permission that is off by default.
candidate-note-list The notes on one candidate. Ashby accepts a sync token here and IGNORES it — this endpoint has no incremental sync — so the parameter is deliberately not offered.
candidate-search Find a person by email or name. Unpaginated and capped at 100 — the right call for 'do we already know this person', where a full list is thousands of records for a yes/no.
candidate-update Correct a candidate's details. `socialLinks` REPLACES the existing list, and notifications default to off here because a bulk correction should not email everyone.
department-list Departments, for grouping and for the case-sensitive name filter on job postings. They are hierarchical, so grouping on the immediate department splits a division into its teams.
interview-event-list The sessions inside one schedule, with their times, panels and links. There is no way to ask for every event this week — list schedules first, then this per schedule.
interview-schedule-list Bookings — one per candidate per stage, so a four-session onsite is ONE schedule. The times live on its events.
interview-stage-list Stage names to ids for one interview plan — what `application-change-stage` needs. Stages typed `Archived` are rejections, so they are separated out.
job-get One role, optionally with its openings — how many seats, approved by whom, targeted at which start date, which is the difference between a job and a headcount plan.
job-list The internal roles being hired for — not the public postings. Confidential jobs are hidden unless the API key has that permission, which is off by default.
job-posting-get One posting with its description, application questions and publicly-shown compensation — which is not necessarily the job's internal band.
job-posting-list The public advertisements, as a careers page or aggregator would see them. Location and department filters are case-SENSITIVE, so a typo looks like an empty result.
location-list Where roles are based, optionally with Ashby's regions. Without the hierarchy a 'roles in EMEA' report has to hard-code its cities, and is wrong the day an office opens.
offer-get One offer with its terms — salary, equity, start date. Exclude the form definition when the workflow only needs to know whether it was accepted.
offer-list Offers, with their three INDEPENDENT statuses — where it is in your process, what the candidate answered, and whether it cleared approval. Reading one for another misreports hires.
source-list Where candidates come from — the ids behind every sourcing report. Create the source in Ashby first; a workflow that cannot find one usually sends none, and that is unrecoverable.
user-list Ashby users, for attribution and for matching a hiring manager to their account elsewhere. Deactivated users are excluded by default — include them to resolve historical credits.
A workflow step names the app and the action, and the editor fills in the
connection when you pick one. This is the Step shape from the
workflow spec, carrying Ashby's real ids.
{
"manifestVersion": "2",
"name": "ashby-example",
"steps": [
{
"id": "application-change-stage",
"uses": {
"app": "io.w6w.ashby",
"action": "application-change-stage",
"connection": "conn_YOUR_CONNECTION_ID"
},
"with": {
"applicationId": "<applicationId>",
"interviewStageId": "<interviewStageId>"
}
}
]
}application-change-stage api-key-info application-feedback-list application-get application-list +22 more actions available
Every app-specific value here is a blank you have to fill in. An
app action is reached through the connection that authenticates it, so the
address is a connection id, not the app id — and connections belong to your account,
so a public page cannot know yours. Create one for Ashby, then fill in
the three blanks: conn_YOUR_CONNECTION_ID, the action key, and the
parameters that action declares. The call itself is real — the shape is transcribed
from the studio's own snippet builder, which prints the same kind of blanks — but
nothing in it is specific to Ashby. The Workflow tab is where this app's
real ids are.
npm install @w6w/sdkyarn add @w6w/sdkpnpm add @w6w/sdkdeno add npm:@w6w/sdkimport { W6wClient, isActionRun } from "@w6w/sdk";
// Reads W6W_BASE_URL and W6W_TOKEN from the environment when omitted.
const client = new W6wClient();
const envelope = await client.run({
urn: "conn_YOUR_CONNECTION_ID",
action: "application-change-stage",
payload: {
applicationId: "<value>",
interviewStageId: "<value>",
// archiveReasonId: "<value>",
// archiveEmail: "<value>",
},
});
if (isActionRun(envelope)) console.log(envelope.value); npm install -g @w6w/cli w6w run conn_YOUR_CONNECTION_ID --action application-change-stage --payload '{"applicationId":"<value>","interviewStageId":"<value>"}' Give an AI agent Ashby — without giving it Ashby's credentials. One MCP endpoint exposes every app, function and workflow the caller is entitled to, as tools it can discover and run. Access is granted per team while we onboard.
One tool call{
"name": "w6w_invoke",
"arguments": {
"ref": "app:io.w6w.ashby#application-change-stage",
"input": {
"applicationId": "<applicationId>",
"interviewStageId": "<interviewStageId>"
}
}
}
Every tool names its target with a single ref. The
app: form above doesn't name a connection at all — the
host resolves which of the caller's Ashby connections to sign
with, and refuses rather than guesses when the answer is ambiguous.
The token is attached host-side, at the moment of the call. It is never a tool argument, never in the model's context, and never in a transcript — so a prompt injection has nothing to exfiltrate.
Tools are derived per end user from what that person has actually connected and is entitled to — not one shared bot identity carrying the union of everyone's access.
Multi-step work runs on the workflow engine and returns a run handle the agent can poll — retries, branching and state survive the conversation that started them.
Ashby's declared health checks are on the surface too, so an agent can tell "the vendor is down" from "your credential expired" before it burns a retry on either.
The MCP surface is part of the hosted platform. Ashby itself is MIT, and the runtime that executes it is source-available (FSL).
Ashby declares its own checks, so its health is a property of the app rather than something the host guesses at.
Ashby's own services, chiefly the API. Third-party components on the same status page (Google, Slack, Zoom, SendGrid) are reported but do not make Ashby down.
Which Ashby modules this key may read and write. A read-only key authenticates perfectly and is refused by every write, which no credential check can see.