← all tools
Recruiting CRM

Zoho Recruit

Read Zoho Recruit (recruiting ATS/CRM) via its API v2 — search/get Candidates (dedup + criteria search before paying for external people data), list/search Clients (the agency's customer companies — an outbound exclusion list), and read Job Openings (the JD a search is built from). Use when the user mentions Zoho Recruit, checking the ATS before sourcing externally, or turning a job opening into search filters. Keys: self-serve OAuth connect — pick your data center and click Connect.

Cost per callfree
Your own keyoptional
CategoryRecruiting CRM

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 →

What your agent reads before it touches Zoho Recruit

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 Zoho Recruit docs →
Show the raw playbookwritten for the agent

HOW in lib/zoho_recruit.py.

Auth & config

  • Connect (self-serve OAuth): dashboard Integrations → Zoho Recruit. Where Hyreflow's own Zoho app is enabled, click Connect and approve. Otherwise register your own Server-based Application at api-console.zoho.com, paste its Client ID and Client Secret, pick your data center (us, eu, in, au, jp, ca, sa or cn) and click Connect. No refresh token is ever pasted — Hyreflow stores the connection and refreshes it automatically.
  • Read-only adapter: Candidates, Clients and Job Openings, plus get_fields to read an account's real field names (they vary per account).
  • Paths CONFIRMED from the official Zoho Recruit API v2 reference. Module API names: Candidates, Clients, Job_Openings, Contacts.

Operations

Generic (any module): list_records, get_record, search_records, get_fields. Candidates: search_candidates, get_candidate. Clients: list_clients, search_clients. Job Openings: list_job_openings, search_job_openings, get_job_opening.

Notes & guardrail

  • Search takes exactly one of criteria, email, phone or word. criteria looks like ((Field:operator:value)and(Field:operator:value)) (≤10 conditions); escape a comma or paren inside a value with a backslash.
  • A search with no match returns an empty result, not an error.
  • per_page caps at 200; page on the response's paging info.
  • Field names vary per account (custom fields, and the job-description field isn't standardized) — call get_fields(module=...) rather than guessing.

Handoff

Read source, not a write target: dedup + search Candidates before sourcing externally, build the outbound exclusion list from Clients, and turn a Job Opening's description into people_search filters.

Callable surface — lib/zoho_recruit.py

Import: from lib.zoho_recruit import ZohoRecruit → instantiate ZohoRecruit() (reads key from env).

  • get_candidate(candidate_id: str) -> Any — GET /Candidates/{id}.
  • get_fields(module: str) -> Any — GET /settings/fields?module= — the account's field API names for a module (custom fields included). Needs scope ZohoRecruit.settings.fields.READ.
  • get_job_opening(job_opening_id: str) -> Any — GET /Job_Openings/{id} — the full record, including the job description field.
  • get_record(module: str, record_id: str) -> Any — GET /{module}/{id} — one full record.
  • list_clients(*, page: int | None = None, per_page: int | None = None, fields: str | None = None) -> Any — GET /Clients — the agency's client companies (Client_Name, Website, …), one page ≤200.
  • list_job_openings(*, page: int | None = None, per_page: int | None = None, fields: str | None = None) -> Any — GET /Job_Openings — one page of job openings (Job_Opening_Name, Job_Opening_Status, Client_Name…).
  • list_records(module: str, *, page: int | None = None, per_page: int | None = None, fields: str | None = None, sort_by: str | None = None, sort_order: str | None = None) -> Any — GET /{module} — one page of records (per_page ≤200). fields = comma-separated API names.
  • search_candidates(*, criteria: str | None = None, email: str | None = None, phone: str | None = None, word: str | None = None, page: int | None = None, per_page: int | None = None) -> Any — GET /Candidates/search — e.g. criteria ((Current_Job_Title:contains:engineer)and(City:equals:Austin)), or email=[email protected] for an exact dedup check, or word= for a keyword across the record.
  • search_clients(*, criteria: str | None = None, word: str | None = None, page: int | None = None, per_page: int | None = None) -> Any — GET /Clients/search — e.g. criteria (Website:contains:acme.com) or (Client_Name:equals:Acme).
  • search_job_openings(*, criteria: str | None = None, word: str | None = None, page: int | None = None, per_page: int | None = None) -> Any — GET /Job_Openings/search — e.g. criteria (Job_Opening_Status:equals:In-progress).
  • search_records(module: str, *, criteria: str | None = None, email: str | None = None, phone: str | None = None, word: str | None = None, page: int | None = None, per_page: int | None = None) -> Any — GET /{module}/search — exactly one of criteria | email | phone | word. No match → {"data": []}.