Source into Recruiterflow, and move people through the pipeline.
Recruiterflow gets the full treatment: candidates in, attached to jobs, moved between stages, notes added. Your agent reads and writes the client and contact side too, so business development lands in the same system as delivery.
Recruiterflow is a destination and a starting point. The open roles already in it can kick off a sourcing run.
Ask for the outcome and your agent composes the run itself. Or start from a skill, a whole pipeline it already knows end to end.
browse all skills →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 Recruiterflow docs →HOW in lib/recruiterflow.py.
https://recruiterflow.com/api/external · Auth: header RF-Api-Key (env RECRUITERFLOW_API_KEY; never hardcode). get_info() = safe read-only pilot.reference/docs/recruiterflow/raw/endpoints.md. RF terminology: client = company, plus candidate/contact/job/deal/placement.get_info() → POST /info (there is no /account); candidate read is get_candidate() → GET /candidate/get; get_job's param is job_id (not id); job statuses is GET /job-status/list (hyphenated); there is no resume-parse method (/resume/parse is not in the spec).request())Candidates: list/get/search/add/update_candidate, add_candidate_to_job, move_candidate_to_stage, disqualify_candidate, add_candidate_note. Jobs: list/get/search/create/update_job, close_job, open_job, get_job_pipeline, list_job_statuses. Clients(companies): list/get/add/update/search_clients. Contacts: list/get/add/update/search_contacts, add_contact_note. Campaigns: list_campaigns, add_to_campaign, remove_from_campaign. Also get_info, list_users, get_user, create_placement. Everything else (activities, calls, emails, tasks, custom-fields, files, deals, placements, reference lists, locations, tags) → request(); all listed in the cached index.
Writes go to a production ATS — pilot one record, show payload+response, confirm before bulk.
ATS write target: sourced+enriched candidates → add_candidate (with job_id) → review in Recruiterflow.
lib/recruiterflow.pyImport: from lib.recruiterflow import Recruiterflow → instantiate Recruiterflow() (reads key from env). Base: https://api.recruiterflow.com/api/external. Generic passthrough: request(method, path, *, params, json).
add_candidate(payload: dict) -> Any — POST /candidate/add — create a candidate (custom_fields as [{id,value}]).add_candidate_note(payload: dict) -> Any — POST /candidate/notes/add — body {id, created_by, value, mentions[]}.add_candidate_to_job(payload: dict) -> Any — POST /candidate/add-to-job — body {id, job_id, applied, added_time}; lands under Sourced.add_client(payload: dict) -> Any — POST /client/add — create a client company.add_contact(payload: dict) -> Any — POST /contact/add.add_contact_note(payload: dict) -> Any — POST /contact/notes/add.add_to_campaign(payload: dict) -> Any — POST /campaign/add-to-campaign — {campaign_id, id:[...], user_id}. Drives outreach — gate.close_job(payload: dict) -> Any — POST /job/close — {job_id, user_id, close_reasons[]}.create_job(payload: dict) -> Any — POST /job/create — {about_position, client_company_id, created_by, department_id, employment_type_id, locations[], title, ...}.create_placement(payload: dict) -> Any — POST /placement-record/create — {user_id, placements:[{job,prospect,...}]}. See cached index for engagement-type variants.disqualify_candidate(payload: dict) -> Any — POST /candidate/disqualify — body {id, job_id, reason, user_id}.get_candidate(candidate_id: str | int) -> Any — GET /candidate/get?id= — read one candidate by id.get_client(client_id: int) -> Any — GET /client/get?id=.get_contact(contact_id: str | int) -> Any — GET /contact/get?id=.get_info() -> Any — POST /info — external-API display-name info (safe read-only pilot). (There is no /account.)get_job(job_id: str | int, *, include_stages: int | None = None) -> Any — GET /job?job_id= (param is job_id, not id). Optional include_stages=1.get_job_pipeline(job_id: str | int) -> Any — GET /job/pipeline?job_id= — stage summary + detail.get_user(*, id: int | None = None, email: str | None = None) -> Any — GET /user/get?id= or ?email=.list_campaigns(**params) -> Any — GET /campaign/list.list_candidates(**params) -> Any — GET /candidate/list (items_per_page, current_page, include_files, include_notes, include_count).list_clients(**params) -> Any — GET /client/list — client companies.list_contacts(**params) -> Any — GET /contact/list (include_files, include_notes, include_count).list_job_statuses(**params) -> Any — GET /job-status/list (hyphenated).list_jobs(**params) -> Any — GET /job/list (items_per_page, current_page, include_count, include_notes, include_description, only_open).list_users(**params) -> Any — GET /user/list.move_candidate_to_stage(payload: dict) -> Any — POST /candidate/move-to-stage — body {id, job_id, stage:{id,name}, user_id}.open_job(payload: dict) -> Any — POST /job/open — {job_id, user_id}.remove_from_campaign(payload: dict) -> Any — POST /campaign/remove-from-campaign — {campaign_id, id:[...]}.search_candidates(payload: dict) -> Any — POST /candidate/search — filter body {conjunction, filters[], items_per_page, current_page, include_count}.search_clients(payload: dict) -> Any — POST /client/search.search_contacts(payload: dict) -> Any — POST /contact/search.search_jobs(payload: dict) -> Any — POST /job/search — filter body (keys: job_client_company, job_department, job_id, name, custom_field.N).update_candidate(payload: dict) -> Any — POST /candidate/update — full update (include id; provided keys replace existing data).update_client(payload: dict) -> Any — POST /client/update — include id.update_contact(payload: dict) -> Any — POST /contact/update — include id.update_job(payload: dict) -> Any — POST /job/update — include job_id.