Skip to content

API for agents

Everything the app does goes through the same REST API, so an AI agent or a script can do it too: import and rank a list, shortlist the top of a campaign, log a WhatsApp message and its reply, add a next step, read Today. This page maps the endpoints by area. Every write follows the same rules as the app: you only see the projects you belong to, and the actions that keep a history (status, opt-outs, review decisions) have their own endpoints.

  • Base URL: https://leadhunter.api.humans2agents.com/api
  • Authentication: POST /api/auth-token/ with {"username": "<your email>", "password": "…"} returns {"token": "…"}; send it as Authorization: Token <token>. POST /api/auth-token/logout/ revokes it. Your email must be confirmed.
  • Projects: most lists take ?project= with the project’s slug or id. An unknown project, or one you don’t belong to, is a 400 (never an empty list).
  • Pagination: lists are paginated (count, next, previous, results); pass page and page_size (up to 100).
  • Errors: a bad parameter is a 400 naming the parameter; a request body must be a JSON object.
  • Limits: 200 requests per minute per user, 60 per minute for actions that spend AI or data-provider money (scoring, ranking, generating fit criteria, drafting, translating, looking up and enriching accounts, starting a research), 10 sign-ins per minute per IP. Past a limit you get 429 with the seconds to wait. See FAQ → Can I use LeadHunter from a script or an AI agent?
  • Messages and next steps take an external_id (the id your tool already has: a WhatsApp message id, an email Message-ID). Sending the same one again returns the stored record with 200 instead of a copy.
  • Accounts from your own system go through POST /api/accounts/upsert-by-imported-id/: it updates the account with that imported_id, or creates it.
  • Bulk changes that act on a set (add to a campaign, bulk review, attribute accounts, start a ranking run) accept dry_run to count first and expected_count to refuse with 409 when the set changed since you counted it.
EndpointWhat it does
GET /api/accounts/The accounts list with every filter the app has (search, country, status, relationship types, saved_filter, cf_<key>, marketing…), plus import_research=<import id> (exactly the accounts an import created) and ranking_run=<run id> (the accounts a ranking run took on).
GET /api/accounts/?fit_product=<id>&fit_goal=salesAdds each account’s fit score for that product and purpose (fit); filter with min_fit / max_fit / fit=scored|unscored, sort with ordering=-fit_score.
GET /api/accounts/{id}/One account, with fit_scores (every fit score, its reasons and whether it’s out of date).
POST / PATCH /api/accounts/Create or update. Creating runs the duplicate check.
POST /api/accounts/upsert-by-imported-id/Create or update by your own id.
POST /api/accounts/{id}/change-status/Change the status, with history.
POST /api/accounts/{id}/record-dnc/Record a per-purpose opt-out or opt-in.
POST /api/accounts/{id}/add-note/Append a dated line to the notes.
POST /api/accounts/{id}/change-partner-stage/Move a marketing partner’s stage.
POST /api/accounts/lookup-url/Look up a business from a website, a domain or a Google Maps URL.
/api/contacts/The people at each account. Deleting archives.
EndpointWhat it does
POST /api/accounts/import_preview/Upload a CSV or XLSX: headers, sample values and a mapping suggested from the header names.
POST /api/accounts/import_suggest_mapping/Optional: AI suggestions for the columns you name.
POST /api/accounts/import_mapped/Run the import with your mapping (dry_run first). Large files run in the background.
GET /api/research/{id}/import-report.csv/?kind=invalid|duplicatesDownload the rows an import didn’t take.
POST /api/research/{id}/revert-import/Undo an import.
EndpointWhat it does
POST /api/accounts/find-duplicates/Duplicate groups, mode exact or fuzzy (similar names), paged, any project size.
POST /api/accounts/merge/Merge one group into a golden record.
POST /api/accounts/bulk-merge/Merge many groups; dry_run: true previews what each merged account will hold.
EndpointWhat it does
GET /api/fit-criteria/?project=&product=&goal=The fit criteria, one row per product and purpose, with version and status.
POST /api/fit-criteria/generate/Generate (or regenerate) fit criteria for a product and purpose, with optional hints. Answers 202; poll the list until the row appears or its version moves.
PATCH /api/fit-criteria/{id}/Edit them. A content change is a new version.
POST /api/fit-criteria/{id}/approve/Approve, optionally with edits.
GET /api/fit-criteria/{id}/versions/What each version said.
POST /api/ranking-runs/estimate/How many accounts a run would score and its estimated cost, for a list_query (the accounts-list filters) or a list of account_ids.
POST /api/ranking-runs/Start a ranking run: the same selection, max_cost_usd (the budget cap, required), optional sample_size and force, and expected_count from the estimate.
GET /api/ranking-runs/?project=Runs with progress, spend against the cap and how they ended.
GET /api/ranking-runs/{id}/One run, with its score bands and the accounts-list query that shows its results best first.
POST /api/ranking-runs/{id}/cancel/Stop a run; what it scored is kept.

See Rank a list without a campaign and Fit criteria and fit score.

EndpointWhat it does
/api/campaigns/Create, list, update and archive campaigns. scoring_brief on a campaign gives it its own scores.
POST /api/campaigns/{id}/bulk-add-from-filter/Add exactly the accounts an accounts-list query matches (list_query), with dry_run and expected_count.
GET /api/campaigns/{id}/scoring-estimate/Accounts to score, batches, model and estimated cost before a run.
POST /api/campaigns/{id}/score_leads/Score the campaign’s accounts that have no score (force: true rescores all); pass the estimate’s count as expected_count.
GET /api/campaigns/scoring-estimate-remaining/?project= / POST /api/campaigns/batch-score-remaining/?project=The same for every campaign of a project with accounts left to score.
POST /api/campaigns/{id}/cancel-scoring/Stop a campaign scoring run.
GET /api/campaigns/{id}/score-bands/How many accounts in each fit score band (8–10, 6–8, 4–6, 0–4, not scored yet) and how many of each are decided.
GET /api/campaign-accounts/?campaign=<id>The campaign’s accounts, filterable by review_status, score_band, score_label, min_score / max_score, and sortable with ordering=-effective_score.
POST /api/campaign-accounts/{id}/approve|defer|reject|reset/One decision: Shortlist, Defer, Not a fit (optional reason), back to review. These are the decisions that calibrate the scoring.
POST /api/campaigns/{id}/bulk-review/One decision for selected ids, or for everything matching filters (optionally the top_n best), with dry_run, expected_count and a reason. Bulk decisions never calibrate the scoring.
POST /api/campaigns/{id}/undo-review/ / GET /api/campaigns/{id}/review-batches/Undo a bulk change; list the latest ones.
GET /api/campaigns/{id}/export-accounts/Download the campaign’s accounts as CSV.

The review decisions are stored as approved (Shortlist), deferred, rejected (Not a fit) and pending (to review). See Review thousands of accounts.

EndpointWhat it does
POST /api/messages/Log a message (sent or received), with channel, direction and optional external_id and purpose. Do-not-contact rules are checked on outbound.
GET /api/messages/?lead=<account id>An account’s whole conversation (the parameter keeps its old name); newest first with ordering=-sent_at,-created_at,-id and before= to page back.
POST /api/messages/propose/Three AI drafts for a campaign account.
POST /api/messages/translate/Translate a text.
/api/next-steps/Next steps with a due date, ours or theirs; complete, cancel, reopen and reschedule actions; GET /api/next-steps/reminders/ for what’s due.
/api/next-steps/templates/The project’s quick-add templates.

See Log a conversation and Next steps and reminders.

EndpointWhat it does
GET /api/dashboard/today/?project=&scope=mine|team&today=YYYY-MM-DDThe Today page: due next steps, replies waiting, clients to contact, best accounts to work next.
GET /api/dashboard/campaign-work/?project=What is waiting in each campaign. project is required (slug or id); an unknown project is a 400 keyed project. Answers {project: {id, slug}, count, truncated, results} with one row per non-archived campaign, at most 100, ordered by name: campaign_id, name, goal, status, has_board, to_review (waiting for a review decision and scored), not_scored (waiting and not scored yet), shortlisted_not_contacted (shortlisted, outreach not started, leaving out do-not-contact accounts), awaiting_reply (the same accounts as the inbox’s Awaiting reply tab) and replied_unanswered (the latest sent message in the campaign thread is theirs; archived and do-not-contact accounts are left out). truncated is true when the project has more than 100 campaigns.
GET /api/dashboard/scoring-runs/?project=Campaign scoring runs, running and recent.
GET /api/research/?project=Imports, enrichment passes and searches.
GET /api/dashboard/stats/?project=The outreach funnel (initiated, clicked, responded, closed), totals and daily.
GET /api/dashboard/breakdown/?project=The funnel per product, campaign and channel, with costs.
GET /api/api-costs/overview/?project=AI and data-provider spend.
EndpointWhat it does
/api/channels/Acquisition channels and marketing actions (an action is a channel with an action_type), with their results (reach, clicks, accounts, trials, clients, revenue, cost, ROI).
POST /api/channels/{id}/change-action-stage/Move an action’s stage.
POST /api/channels/{id}/attribute-accounts/Attribute accounts to an action or channel by ids, names or an accounts-list query. Dry run by default.
GET /api/channels/{id}/attributed-accounts/The accounts attributed to it.
GET /api/marketing/summary/?project=&period_start=&period_end=The Marketing dashboard: totals, by action type, by partner kind, top actions.
GET /api/marketing/partner-results/?project=&accounts=Each partner’s actions and results.
/api/channel-expenses/Costs of a channel or action, in-kind included.

See Marketing partners.

  • Webhooks push account and conversation events to your tools: Webhooks.
  • Client Pulse takes usage events from your own product on its own key: Client Pulse.
  • Tracked links (/api/short-links/) count the clicks on links you send.

A campaign’s quiet follow-up queue: in the app, the Follow-up view of the campaign’s Outreach tab. Reading it creates nothing, and its actions create no next steps, notifications or messages. See Review your outreach.

GET /api/outreach-review/?campaign=<campaign id> lists the campaign’s contacted accounts. Filters: queue (due, waiting, replied, confirm_reply, limit_reached, snoozed, closed, delivery_problem, needs_data), attempts (1, 2, or 3 for three or more), owner (me, none or a user id), search, ordering, page, page_size (up to 100). Each row has id, version, state, reason, attempts, next_review_on, actions and more. GET /api/outreach-review/{id}/history/ returns what was done to a row.

POST /api/outreach-review/{id}/{action}/ takes command_id (any unique string, so a retry is safe) and version (the row’s, a mismatch answers 409 with the current row), plus:

ActionBody
confirm-attemptoccurred_on, occurred_at or date_unknown: true; optional channel, note
correct-attemptattempt, the date as above, reason
void-attemptattempt, reason
link-messageattempt, message (or null to unlink)
snoozeuntil (or null)
set-ownerowner (or null)
closeoptional reason. People only
reopenreason. People only

A person’s confirmation of a send is a statement, not a message: never invent a body, a time or a message id. When the real message is logged, link it.