First-party app
Ashby

Ashby

Work an Ashby hiring pipeline — find and create candidates, move applications between stages, read interviews, feedback and offers, and sync it all incrementally.

stable Human ResourcesProductivity

About

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.

App id
io.w6w.ashby
Version
0.1.1
Author
w6w
Licence
MIT
Categories
Human Resources · Productivity

Overview

Ashby 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.

Build with Ashby

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.

Get this API key's permissions

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.

Move an application to a stage

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.

List interview feedback

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.

Get an application

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.

List applications

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.

Update an application

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.

List archive reasons

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.

Create a candidate

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.

Get a candidate

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.

List candidates

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.

Add a note to a candidate

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.

List a candidate's notes

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.

Search candidates

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.

Update a candidate

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.

List departments

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.

List interview events

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.

List interview schedules

interview-schedule-list

Bookings — one per candidate per stage, so a four-session onsite is ONE schedule. The times live on its events.

List interview stages

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.

Get a job

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.

List jobs

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.

Get a job posting

job-posting-get

One posting with its description, application questions and publicly-shown compensation — which is not necessarily the job's internal band.

List job postings

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.

List locations

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.

Get an offer

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.

List offers

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.

List sources

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.

List users

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>"
      }
    }
  ]
}

Here are some of the things you can do

  • Move an application to a stage

    perform
    application-change-stage
  • Get this API key's permissions

    read
    api-key-info
  • List interview feedback

    read
    application-feedback-list
  • Get an application

    read
    application-get
  • List applications

    read
    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.

Install
npm install @w6w/sdk
yarn add @w6w/sdk
pnpm add @w6w/sdk
deno add npm:@w6w/sdk
Code
import { 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);
Install the CLI
npm install -g @w6w/cli
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.

What the agent gets

Credentials it can't read

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.

A tool surface scoped to the caller

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.

A durable workflow in one call

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.

Health-aware discovery

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).

Request MCP access

Health checks

Ashby declares its own checks, so its health is a property of the app rather than something the host guesses at.

service

Ashby platform status

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.

dependency

API key scopes

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.

quota

API request headroom