← all tools
Recruiting CRM

Loxo

Push candidates into Loxo and attach them to the right job.

Cost per callBYOK · vendor billed
Your own keyrequired
CategoryRecruiting CRM

Loxo is where the work gets recorded. Your agent creates candidate records, reads your live jobs, and links people to the roles they are up for. These are real writes to a production system, so it does one and shows you before it does the rest.

Loxo is the destination. Everything the pipeline produced ends up filed here.

things you can ask for

  • Add these candidates and attach them to the job
  • What roles are open? Source a longlist for the hardest one with AI Ark
  • Is this person already in the system before I spend a credit on Lusha?
  • Attach this shortlist to the role and file the Granola notes

works well with Loxo

  • AI Ark

    source against the roles already open in Loxo

  • Lusha

    enrich only the candidates Loxo does not already hold

  • Granola

    meeting notes filed onto the candidate

  • HeyReach

    run LinkedIn outreach off a Loxo shortlist

Skills that use Loxo

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

Sourcing

Ask the people you placed for referrals into a live role

use whenyou have years of placements in your database and no habit of asking those people who else is good

JobAdderSpottLoxoLeadMagicFullEnrichEnrichleyhyreflow AgentLemlist
starts from your database · 7 stepsget the prompt →
Sourcing

Find the candidates already sitting in your own ATS

use whena role comes in and you want to know who you already have before you pay to source anyone new

Recruit CRMBullhornVincereLoxoRecruiterflowJobAdderAtlashyreflow Agent
starts from a job spec · 6 stepsget the prompt →
Sourcing

Score one candidate against every open role in your ATS

use whena strong applicant comes in for the wrong job, and your team is working dozens of other roles they might fit

Recruit CRMBullhornVincereLoxoRecruiterflowJobAdderAtlashyreflow AgentAI Ark
starts from a candidate · 7 stepsget the prompt →
Signals

Find the people you placed years ago who hire for their own team today

use whenthe people you placed years ago are running teams, and your database still files them as candidates

Recruit CRMJobAdderRecruiterflowLoxoLushaLeadMagicProspeoEnrichleyhyreflow Agent
starts from your database · 8 stepsget the prompt →
Signals

Get back in touch with candidates who were promoted or changed job

use whenyour database is full of people you have not spoken to in years, and you do not know which of them have been promoted or changed employer since

LushaLeadMagicFullEnrichLemlistRecruit CRMBullhornLoxoVincereJobAdder
starts from your database · 8 stepsget the prompt →
Database

Fill the missing emails and mobiles in your CRM

use whenyour ATS or CRM is full of people you cannot reach because the email, mobile or LinkedIn field is empty

Recruit CRMLoxoJobAdderRecruiterflowAtlasLeadMagicFullEnrichProspeoEnrichley
starts from your database · 8 stepsget the prompt →

See all 16 skills that use Loxo

What your agent reads before it touches Loxo

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

HOW in lib/loxo.py.

Auth & config (agency-scoped)

  • Base URL: https://app.loxo.co/api/{LOXO_AGENCY_SLUG} — Loxo is agency-scoped. The host is fixed; an agency subdomain 404s.
  • Env: LOXO_API_KEY (Bearer), LOXO_AGENCY_SLUG (bare slug, e.g. acme — a pasted URL/domain is reduced to its first label). Never hardcode.
  • Paths CONFIRMED from loxo.readme.io (2026-06-01). Jobs paginate via page/per_page; people and companies paginate via scroll_id (+ a Lucene query). Index: reference/docs/loxo/raw/endpoints.md.

Operations

Jobs: list_jobs, get_job, create_job, update_job. People: list_people, get_person, create_person, update_person. Companies (accounts): list_companies, iter_companies, get_company, create_company, update_company, plus list_dynamic_fields + company_custom_fields for the agency's custom fields. Job↔candidate: list_job_candidates (GET /jobs/{id}/candidates), apply_to_job (POST /jobs/{id}/apply).

Notes & guardrail

  • Adding a candidate to a job is apply_to_job (POST /jobs/{job_id}/apply) — multipart/form-data with REQUIRED email+name+phone (+ optional resume file). create_job_candidate is a deprecated alias.
  • Production ATS writes — pilot one, confirm, then bulk.
  • list_companies paginates by cursor only: pass the scroll_id each response returns, with a per_page up to 100 — there is no page param. iter_companies(max_records=…) walks the whole account list.
  • A company's agency custom fields arrive as custom_hierarchy_1..6 {id, value} pairs, and each slot holds a different field per agency. Read the definitions once with list_dynamic_fields, then pass a {"custom_hierarchy_3": "Therapeutic area"} map to company_custom_fields(company, labels).

Handoff

ATS write target: enriched candidates → create_person / apply_to_job (assign to a job). Company-first BD: iter_companies → each account's url (website) → hyreflow_native career-page scraping for their open roles → contacts via the people_search waterfall.

Callable surface — lib/loxo.py

Import: from lib.loxo import Loxo → instantiate Loxo() (reads key from env). Generic passthrough: request(method, path, *, params, json).

  • apply_to_job(job_id: str, *, email: str, name: str, phone: str, resume: tuple | None = None, **fields) -> Any — POST /jobs/{job_id}/apply — add a candidate to a job (CONFIRMED).
  • company_custom_fields(company: dict, labels: dict | None = None) -> dict — Local (no HTTP): a company's custom_hierarchy_1..6 slots as {field_name: value}.
  • create_company(payload: dict) -> Any — POST /companies — create a company (form-encoded, company[...] params).
  • create_job(payload: dict) -> Any — POST /jobs — create a job.
  • create_job_candidate(job_id: str, payload: dict) -> Any — DEPRECATED alias → apply_to_job (POST /jobs/{id}/apply, multipart).
  • create_person(payload: dict) -> Any — POST /people — create a person. (Resume upload needs multipart via request().)
  • get_company(company_id: str) -> Any — GET /companies/{id} — one company, with its website (url) and custom fields.
  • get_job(job_id: str) -> Any — GET /jobs/{id}.
  • get_person(person_id: str) -> Any
  • iter_companies(*, per_page: int = 100, max_records: int = 1000, **params) -> Iterator[dict] — GET /companies — follow the scroll cursor until the account list is exhausted.
  • list_companies(**params) -> Any — GET /companies — search/list companies (accounts, employers).
  • list_dynamic_fields() -> Any — GET /dynamic_fields — the agency's custom (dynamic) field definitions.
  • list_job_candidates(job_id: str, **params) -> Any — GET /jobs/{job_id}/candidates — candidates on a job, with pipeline stage (CONFIRMED).
  • list_jobs(**params) -> Any — GET /jobs — paginated (page, per_page).
  • list_people(**params) -> Any — GET /people — paginated people/candidates.
  • update_company(company_id: str, payload: dict) -> Any — PUT /companies/{id} (form-encoded, company[...] params).
  • update_job(job_id: str, payload: dict) -> Any — PUT /jobs/{id}.
  • update_person(person_id: str, payload: dict) -> Any