ashby-sandbox
v0.1.0
Published
Local Ashby API sandbox: a coherent, seeded fake ATS serving Ashby's real API surface. Point ashby-ts, MCP servers, or integration tests at it — no customer account needed.
Maintainers
Readme
ashby-sandbox
A local, fake Ashby API you can develop against without a customer account.
Ashby has no self-serve sandbox: the data endpoints need a customer API key, the MCP beta connects through a customer user's OAuth to live recruiting data, and partner sandboxes are granted only after partner-program approval. Even customers have no safe place to point integration tests or rehearse agent behavior — write endpoints hit the production ATS. This serves Ashby's real API surface on localhost instead.
bunx ashby-sandbox
# ashby-sandbox listening on http://localhost:3690 (seed 1)
# world: 8 jobs · 120 candidates · 134 applications · 14 users
# endpoints: 28 stateful · 145 spec-example stubsPoint any Ashby client at it, for example ashby-ts:
import { Ashby } from "ashby-ts";
const ashby = new Ashby({ apiKey: "anything", baseUrl: "http://localhost:3690" });
for await (const app of ashby.application.list({ jobId })) {
// a coherent pipeline, not random noise
}Or curl it exactly like api.ashbyhq.com:
curl -s -u any-key: http://localhost:3690/job.list -d '{}' | jq '.results[].title'Why not a generic OpenAPI mocker
Prism and friends can mock Ashby's spec, but they generate schema-random data: applications pointing at jobs that don't exist, candidates with no applications, pagination that never pages. Anything doing real work against an ATS (an agent analyzing a pipeline, an integration syncing candidates) needs the joins to hold. The sandbox generates a coherent org: jobs with interview plans and stages, candidates with applications, applications with stage history, feedback from real users, offers for late-stage candidates. Every id resolves.
- Seeded and deterministic.
--seed 42produces the same org every run. CI can assert against it. - Stateful core. The 28 endpoints of the recruiting loop actually work:
application.changeStagemoves the candidate, writes history, and shows up in the next incremental sync.candidate.create,application.create, notes, and source changes all mutate the world. - Real envelope semantics.
success/resultsenvelopes, cursor pagination withnextCursor/moreDataAvailable, andsyncTokenincremental sync that returns only what changed since your last token, exactly the flow Ashby's sync guide describes. - Spec-example stubs for the rest. The other 145 endpoints return the success examples from Ashby's own published OpenAPI spec, not invented data. Six endpoints without spec examples return empty results. Endpoints that aren't in the spec 404.
- Auth behaves like the real thing. Missing Basic auth is a 401. Any key is accepted (it's a sandbox), and
apiKey.infosays so.
Usage
ashby-sandbox [--port 3690] [--seed 1]GET / describes the running sandbox: seed, world counts, and which endpoints are stateful vs stubbed.
Programmatic use (integration tests):
import { createSandbox } from "ashby-sandbox";
const sandbox = await createSandbox({ port: 0, seed: 42, quiet: true });
// sandbox.url -> http://localhost:<port>
await sandbox.close();This package's own test suite runs the published ashby-ts client against the sandbox: pagination, incremental sync, stage changes, and error envelopes are all exercised through a real client.
Example: a pipeline health report
examples/pipeline-report.ts is the direct use case end to end: per-job totals, a stage-conversion funnel, median time in stage, and stuck candidates, computed from history joins with ashby-ts.
bun run reportWith no configuration it reports on an in-process sandbox. The same script runs against a live org (ASHBY_API_KEY=...) or any running sandbox (ASHBY_BASE_URL=...), because there is nothing sandbox-specific in the analysis — that's the point.
The world
One fake company (a ~14-person recruiting org), 8 jobs across 6 departments, ~120 candidates, ~134 applications distributed across a 5-stage pipeline with realistic skew toward early stages, plus archived and hired outcomes, interview feedback with scores, candidate notes, and pending offers. Timestamps span the 90 days before a fixed base date, so the world is fully reproducible.
How it's built
Generated from the same vendored copy of Ashby's OpenAPI spec that generates ashby-ts: the spec defines the client on one side and the fake server on the other, and the client's test suite meets the sandbox in the middle. Stub responses are extracted from the spec's own examples at build time (bun run extract-examples).
Zero runtime dependencies; the server is node:http. Works in Node 18+ and Bun.
Notes
- This is a development tool, not an emulator. Core-loop behavior is faithful (envelopes, pagination, sync, the common filters); long-tail endpoint behavior and validation are not. When your integration graduates to a real Ashby org, expect the real API to be stricter.
- Not affiliated with Ashby.
MIT
