← all tools
Recruiting CRM / ATS

Recruit CRM

Write it all back to your CRM, so nothing ends up in a spreadsheet.

Cost per callBYOK · vendor billed
Your own keyrequired
CategoryRecruiting CRM / ATS

Recruit CRM is where the work lands. Your agent pushes candidates, companies and contacts into it, reads your open roles, and moves people through hiring stages. Every write is real, so it does one record first and shows you the result before the rest.

Recruit CRM is the destination. Whatever the pipeline produced, this is where it gets filed.

things you can ask for

  • Push this shortlist in as candidates, the ones Prospeo enriched
  • What roles am I working? Build a longlist for the newest one with AI Ark
  • Move these three to first interview and log the Fathom notes on each
  • Sync the enriched contacts onto their company records

works well with Recruit CRM

  • Prospeo

    the enrichment that fills the record

  • Fathom

    interview notes written onto the candidate automatically

  • Instantly

    outreach off a Recruit CRM shortlist

  • Aircall

    calls logged against the right person

Skills that use Recruit CRM

A skill is a whole pipeline your agent already knows. These ones call Recruit CRM as part of the run.

re-engage4 steps

Stale CRM → re-engagement sweep

Re-qualify cold Recruit CRM candidates against the current ICP. Warm leads back into active flow.

coming soon

What your agent reads before it touches Recruit CRM

Every tool ships with a written playbook, and the agent loads it before the first call. Auth, rate limits, what each call costs, which actions need your approval, and the mistakes worth avoiding. It is the difference between an agent that knows the tool and one that guesses at it.

Read the Recruit CRM docs →
Show the raw playbookwritten for the agent

Confidence: highest of the CRMs — every path is taken straight from Recruit CRM's own live docs (20 endpoint groups; cache: reference/docs/recruitcrm/raw/). Base https://api.recruitcrm.io/v1, Bearer.

Routing layer for all Recruit CRM API work. This file tells you WHERE to go and the guardrails to respect; the HOW lives in the reference playbook, the recipes, and the Python client. Read the matching doc BEFORE making any call.

1) What this skill governs

  • Routing, auth, and safety gates for every Recruit CRM API call.
  • Canonical execution surface: the Python client at lib/recruit_crm.py (Python-only stack).
  • Per-endpoint detail lives in reference/endpoints.md; task playbooks in recipes/*.md.

2) Auth & config (confirmed from public docs)

  • Base URL: https://api.recruitcrm.io/v1/
  • Auth header: Authorization: Bearer <API_TOKEN>
  • Token source: Recruit CRM → Admin Settings → API & Integrations → enable toggle → copy key.
  • Secret handling — MANDATORY: the API token is read from the environment variable RECRUITCRM_API_KEY. NEVER hardcode the key in any file, recipe, or commit. The Python client raises if the env var is missing.

3) Read behavior — before any execution

STOP. Before writing or running a call:

When the task involves... Read this first
Any endpoint shape, params, pagination, field names reference/endpoints.md
Pushing enriched leads (companies + contacts) from a CSV recipes/push-enriched-leads.md
Calling the API from Python lib/recruit_crm.py (docstrings)

If the exact payload for an operation is marked TODO (paste schema) in reference/endpoints.md, do NOT guess it — ask the user to paste that section of https://docs.recruitcrm.io first. (The docs site is a JS-rendered SPA and cannot be scraped.)

4) Approval gates (writes & bulk operations)

Recruit CRM writes are real, irreversible changes to a production ATS. Before any create/update/delete or bulk push:

  1. Dry-run first. For bulk loads, process --rows 0:1 (one record) and show the user the exact payload + response before continuing.
  2. Confirm field mapping. Custom-field IDs and hiring-stage IDs are account-specific (see reference/endpoints.md → "Account-specific IDs"). Confirm the mapping before writing.
  3. Get explicit go-ahead before writing more than one record.

5) Resource groups covered

  • Candidates — create / search / update
  • Companies & contacts — create / update (primary target for enriched-lead pushes)
  • Jobs — list / jobs/search, assign candidates
  • Pipeline & custom fields — hiring-stage moves, deals, read/write custom fields

6) Handoff from the sourcing/enrichment layer

The canonical flow: the sourcing + enrichment adapters produce an enriched CSV → this skill loads that CSV into Recruit CRM. Inspect the CSV (row count, columns, sample values) first before mapping columns to Recruit CRM fields. Never read large CSVs into context.

Callable surface — lib/recruit_crm.py

Import: from lib.recruit_crm import RecruitCRM → instantiate RecruitCRM() (reads key from env). Base: https://api.recruitcrm.io/v1. Generic passthrough: request(method, path, *, params, json).

  • apply_candidate_to_job(candidate_slug: str, payload: dict) -> Any — POST /v1/candidates/{candidate}/apply.
  • assign_candidate_to_job(candidate_slug: str, payload: dict) -> Any — POST /v1/candidates/{candidate}/assign — assign candidate to a job (job slug in body).
  • create_candidate(payload: dict) -> dict — POST /v1/candidates — create a candidate. Required: first_name.
  • create_company(payload: dict) -> dict — POST /v1/companies — create a company. Required: company_name.
  • create_contact(payload: dict) -> dict — POST /v1/contacts — create a contact. Required: first_name, last_name.
  • create_job(payload: dict) -> dict — POST /v1/jobs — create a job. Required: name, number_of_openings, company_slug,
  • delete_candidate(slug: str) -> Any — DELETE /v1/candidates/{slug}.
  • delete_company(slug: str) -> Any
  • delete_contact(slug: str) -> Any
  • delete_job(slug: str) -> Any
  • get_candidate(slug: str) -> dict — GET /v1/candidates/{slug} — single candidate by slug.
  • get_candidate_hiring_pipelines() -> Any — GET /v1/hiring-pipelines — pipelines + stage IDs (needed for hiring-stage moves).
  • get_candidate_hiring_stages(slug: str) -> Any — GET /v1/candidates/{slug}/hiring-stages — all jobs+stages for the candidate.
  • get_company(slug: str) -> dict
  • get_contact(slug: str) -> dict
  • get_job(slug: str) -> dict
  • list_candidates(sort_by: str = 'updatedon', sort_order: str = 'desc', limit: int = 100) -> Iterator[dict] — GET /v1/candidates — iterate all candidates. sort_by: createdon|updatedon.
  • list_companies(sort_by: str = 'updatedon', sort_order: str = 'desc', limit: int = 100) -> Iterator[dict] — GET /v1/companies — iterate all companies.
  • list_contacts(sort_by: str = 'updatedon', sort_order: str = 'desc', limit: int = 100) -> Iterator[dict] — GET /v1/contacts — iterate all contacts.
  • list_custom_fields(entity: str = '') -> Any — GET /v1/custom-fields[/{entity}] — custom field IDs. entity e.g.
  • list_jobs(sort_by: str = 'updatedon', sort_order: str = 'desc', limit: int = 100) -> Iterator[dict] — GET /v1/jobs — iterate all jobs.
  • search_candidates(*, custom_fields: list[dict] | None = None, **filters) -> Iterator[dict] — GET /v1/candidates/search — query filters + optional custom_fields body.
  • search_companies(*, custom_fields: list[dict] | None = None, **filters) -> Iterator[dict] — GET /v1/companies/search. Filters: company_name, company_slug, owner_email,
  • search_contacts(*, custom_fields: list[dict] | None = None, **filters) -> Iterator[dict] — GET /v1/contacts/search — query filters + optional custom_fields body.
  • search_jobs(*, custom_fields: list[dict] | None = None, **filters) -> Iterator[dict] — GET /v1/jobs/search — query filters + optional custom_fields body.
  • update_candidate(slug: str, payload: dict) -> dict — POST /v1/candidates/{slug} — edit a candidate (POST, not PUT).
  • update_candidate_hiring_stage(candidate_slug: str, job_slug: str, payload: dict) -> Any — POST /v1/candidates/{candidate}/hiring-stages/{job} — move candidate's stage in a job.
  • update_company(slug: str, payload: dict) -> dict — POST /v1/companies/{slug} — edit (POST, not PUT). company_name required again.
  • update_contact(slug: str, payload: dict) -> dict — POST /v1/contacts/{slug} — edit a contact (POST, not PUT).
  • update_job(slug: str, payload: dict) -> dict — POST /v1/jobs/{slug} — edit a job (POST, not PUT).