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.
A skill is a whole pipeline your agent already knows. These ones call Loxo as part of the run.
use whenyou have years of placements in your database and no habit of asking those people who else is good
use whena role comes in and you want to know who you already have before you pay to source anyone new
use whena strong applicant comes in for the wrong job, and your team is working dozens of other roles they might fit
use whenthe people you placed years ago are running teams, and your database still files them as candidates
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
use whenyour ATS or CRM is full of people you cannot reach because the email, mobile or LinkedIn field is empty
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 →HOW in lib/loxo.py.
https://app.loxo.co/api/{LOXO_AGENCY_SLUG} — Loxo is agency-scoped. The host is fixed; an agency subdomain 404s.LOXO_API_KEY (Bearer), LOXO_AGENCY_SLUG (bare slug, e.g. acme — a pasted URL/domain is reduced to its first label). Never hardcode.page/per_page; people and companies paginate via scroll_id (+ a Lucene query). Index: reference/docs/loxo/raw/endpoints.md.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).
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.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.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).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.
lib/loxo.pyImport: 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) -> Anyiter_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