Search results, candidate records, outreach sequences, inbox threads, and interviews — read from and written to the same data your recruiters see in the app.
The FastHire API is organized around the same objects recruiters work with in the app: searches (an AI sourcing run against a role), the candidates it surfaces, lists recruiters curate from results, sequences that run outreach across LinkedIn, email and in-app steps, the inbox those sequences populate, and interviews scheduled from a reply. Every object is scoped to one organization — your API key determines which org’s data you see.
The API accepts and returns application/json, uses standard HTTP response codes, and is versioned in the URL.
https://api.thefasthire.com/v1Authenticate by sending a secret API key as a bearer token. Generate keys from Settings → Developer in the FastHire app — each key is scoped to one org and inherits no more access than the role that created it (an ORG_ADMIN key can manage sequences and connections; a RECRUITER key cannot).
curl https://api.thefasthire.com/v1/searches \
-H "Authorization: Bearer fh_live_51H8x...cQ2k"fh_test_… keys against the sandbox org while integrating; they behave identically but never send real LinkedIn, email, or WhatsApp messages.| Convention | Detail |
|---|---|
| Identifiers | All object ids are opaque strings (e.g. cnd_9mK2pQ...). Don’t parse them. |
| Timestamps | ISO 8601, UTC, e.g. 2026-08-20T09:30:00Z. Fields ending in At follow this format. |
| Money | Salary fields are free-text strings (currentSalary, expectedSalary) — candidates self-report these in whatever format they reply with, so the API does not coerce them into a number/currency pair. |
| Idempotency | POST requests that create an object accept an Idempotency-Key header; replaying the same key returns the original response instead of creating a duplicate. |
| Expansion | Where noted, pass ?expand=field to inline a related object instead of just its id. |
Errors return a 4xx/5xx status and a JSON body describing what went wrong.
{
"error": {
"type": "invalid_request",
"code": "missing_field",
"message": "criteria.mandatory is required for sourceType=MANUAL",
"field": "criteria.mandatory"
}
}| Status | Meaning |
|---|---|
400 invalid_request | Malformed body, or a field failed validation. |
401 unauthenticated | Missing, malformed, or revoked API key. |
403 permission_denied | Key's role can't perform this action (e.g. a RECRUITER key managing connections). |
404 not_found | Object doesn't exist, or belongs to a different org. |
409 conflict | State doesn't allow the action (e.g. activating an ARCHIVED sequence). |
429 rate_limited | Too many requests — see rate limit headers below. |
500 api_error | Something failed on FastHire's side. Safe to retry. |
List endpoints are cursor-paginated. Pass limit (default 25, max 100) and cursor from the previous page’s next_cursor.
{
"data": [ /* … */ ],
"has_more": true,
"next_cursor": "eyJpZCI6ImNuZF85..."
}Every response carries rate-limit headers; the default org limit is 300 requests/minute, shared across all keys on that org.
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed in the current window. |
X-RateLimit-Remaining | Requests left in the current window. |
X-RateLimit-Reset | Unix timestamp when the window resets. |
A search is one AI sourcing run for a role — a natural-language brief, a pasted job description, or a manually-built candidate set. Activating one spins up an agent that scores candidates against mandatory and desirable criteria.
List searches for the org, most recent first.
| Field | Type | Description |
|---|---|---|
| statusoptional | string | Filter by agent status. DRAFTRUNNINGCOMPLETEFAILED |
| owner_idoptional | string | Only searches owned by this user. |
{
"data": [
{
"id": "src_7bWmA1...",
"name": "Senior Backend Engineer — Sydney",
"source_type": "JOB_DESCRIPTION",
"status": "COMPLETE",
"result_count": 143,
"owner_id": "usr_2kTQm...",
"created_at": "2026-08-12T04:11:00Z"
}
],
"has_more": false,
"next_cursor": null
}Create a search and queue its agent. RUNNING is returned immediately; poll the search or listen for the search.completed webhook.
| Field | Type | Description |
|---|---|---|
| namerequired | string | Internal label for this search. |
| source_typerequired | string | NATURAL_LANGUAGEJOB_DESCRIPTIONMANUAL |
| criteria.mandatoryrequired | array | Must-have criteria the agent will not compromise on. |
| criteria.desirableoptional | array | Nice-to-have criteria used to rank, not filter. |
| candidate_capoptional | integer | Stop sourcing once this many matches are found. Default 200. |
curl https://api.thefasthire.com/v1/searches \
-X POST -H "Authorization: Bearer $FASTHIRE_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Senior Backend Engineer — Sydney",
"source_type": "JOB_DESCRIPTION",
"criteria": {
"mandatory": ["5+ years backend", "Right to work in Australia"],
"desirable": ["Distributed systems", "Fintech background"]
}
}'Retrieve one search, including its agent’s status and criteria.
List a search’s matched candidates, ranked by score.
| Field | Type | Description |
|---|---|---|
| tieroptional | string | TOP_MATCHGOOD_MATCHPOSSIBLE_MATCH |
{
"data": [
{
"candidate_id": "cnd_9mK2pQ...",
"name": "Priya Nair",
"headline": "Staff Engineer at Canva",
"match_score": 0.94,
"match_tier": "TOP_MATCH",
"match_reasoning": "7 yrs backend, distributed systems at scale, AU-based."
}
],
"has_more": true,
"next_cursor": "eyJpZCI6InNy..."
}A candidate record is deduplicated per org by source profile, and accumulates results, list memberships, sequence history, and interviews as it moves through your pipeline.
Retrieve a candidate’s full profile.
{
"id": "cnd_9mK2pQ...",
"name": "Priya Nair",
"headline": "Staff Engineer at Canva",
"email": "priya.nair@example.com",
"phone": null,
"linkedin_url": "https://linkedin.com/in/priyanair",
"location": "Sydney, Australia",
"education": [ { "school": "UNSW", "degree": "BSc Computer Science" } ],
"experience": [ { "company": "Canva", "title": "Staff Engineer", "years": 3 } ],
"skills": ["Go", "Kubernetes", "PostgreSQL"],
"first_seen_at": "2026-08-12T04:12:31Z"
}Fetch the details a candidate supplied after replying with interest — resume link, experience, compensation and availability. Returns 404 if FastHire hasn’t asked this candidate yet (no intake exists until a reply signals interest).
| Field | Type | Description |
|---|---|---|
| status | string | REQUESTEDCOMPLETED |
| resume | string | Free text or a link — however the candidate provided it. No file storage. |
| total_experience, current_salary, expected_salary, notice_period, interview_availability | string | Self-reported, filled in as replies arrive. |
Lists are recruiter-curated candidate sets, independent of any one search — the unit sequences target for outreach.
List the org’s lists.
| Field | Type | Description |
|---|---|---|
| namerequired | string | List name. |
Add one or more candidates to a list.
| Field | Type | Description |
|---|---|---|
| candidate_idsrequired | array | Up to 100 candidate ids per call. |
Remove one candidate from a list. Returns 204.
A sequence is an outreach campaign: an ordered set of LinkedIn, email, and in-app steps run against one or more target lists.
| Field | Type | Description |
|---|---|---|
| statusoptional | string | DRAFTACTIVEPAUSEDARCHIVED |
Create a sequence in DRAFT. It only starts sending once activated.
| Field | Type | Description |
|---|---|---|
| namerequired | string | Campaign name. |
| channel_strategyrequired | string | LINKEDIN_FIRSTINMAIL_FIRSTEMAIL_FIRSTFASTHIRE_FIRST |
| target_list_idsrequired | array | Lists to enroll candidates from. Active sequences pick up candidates added later, too. |
| stepsrequired | array | Ordered steps: channel LINKEDINEMAILIN_APP , step_type CONNECTMESSAGEFOLLOW_UP , subject (email only), template_body. |
Update a sequence’s status — the usual way to activate or pause a campaign.
curl https://api.thefasthire.com/v1/sequences/seq_4LpN.../ \
-X PATCH -H "Authorization: Bearer $FASTHIRE_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "ACTIVE" }'List candidates enrolled in a sequence and where each stands.
| Field | Type | Description |
|---|---|---|
| status | string | NOT_STARTEDIN_PROGRESSREPLIEDBOUNCEDCOMPLETEDPAUSED |
| next_action_at | timestamp | When this candidate's next step is due to send. |
Every send and reply across LinkedIn, email, and WhatsApp lands in one per-candidate thread.
List conversations, most recently active first.
| Field | Type | Description |
|---|---|---|
| unreadoptional | boolean | Only conversations with an unread inbound message. |
{
"data": [
{
"id": "msg_1QvD...",
"channel": "EMAIL",
"direction": "INBOUND",
"body": "Thanks for reaching out — happy to chat this week.",
"status": "REPLIED",
"sent_at": "2026-08-19T02:04:00Z",
"read_at": null
}
]
}Send a reply into an existing thread, on the same channel and connection the candidate last replied on.
| Field | Type | Description |
|---|---|---|
| bodyrequired | string | Message text. |
| channeloptional | string | Defaults to the channel of the candidate's last inbound message. |
Interviews link a candidate, a recruiter, and a time — optionally backed by a real Calendar connection.
| Field | Type | Description |
|---|---|---|
| statusoptional | string | PROPOSEDCONFIRMEDCANCELLEDCOMPLETED |
| Field | Type | Description |
|---|---|---|
| candidate_idrequired | string | |
| recruiter_idrequired | string | Must have an active Calendar connection to auto-create a meeting link. |
| scheduled_atrequired | timestamp | Stored as UTC. |
| timezoneoptional | string | IANA zone for display, e.g. Asia/Kolkata. Defaults to the org’s zone. |
| meeting_linkoptional | string | Skip auto-generation and set one manually. |
Reschedule or update status. A CONFIRMED → CANCELLED transition cancels the linked calendar event, if any.
Cancel an interview. Equivalent to PATCH { "status": "CANCELLED" }; returns 204.
Subscribe to an endpoint from Settings → Developer → Webhooks to be notified as events happen, instead of polling.
| Event | Fires when |
|---|---|
search.completed | An agent finishes scoring and its status moves to COMPLETE or FAILED. |
candidate.replied | An inbound message arrives on any channel. |
candidate.intake_completed | All six intake fields are captured. |
sequence.candidate_completed | A candidate finishes, bounces, or pauses in a sequence. |
interview.scheduled | An interview is created or confirmed. |
{
"id": "evt_8pXeC...",
"type": "candidate.replied",
"created_at": "2026-08-19T02:04:00Z",
"data": { /* the object, shaped like its REST response */ }
}Each request carries an X-FastHire-Signature header — an HMAC-SHA256 of the raw body using your endpoint’s signing secret. Verify it before trusting the payload, and return a 2xx within 10 seconds or FastHire retries with backoff for up to 24 hours.
This document is the working spec for FastHire’s public API, generated from the live recruiter data model as of August 2026. Object and field names here are intended to match the eventual implementation exactly, so this page can move from spec to shipped documentation with no renaming pass.
Not yet scoped: candidate CSV import via API (currently UI-only), managing team members/invitations, and org-level billing endpoints. Flag priorities to the FastHire team before the v1 build starts.