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.
Basics
Section titled “Basics”- Base URL:
https://leadhunter.api.humans2agents.com/api - Authentication:
POST /api/auth-token/with{"username": "<your email>", "password": "…"}returns{"token": "…"}; send it asAuthorization: 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); passpageandpage_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?
Writes that are safe to retry
Section titled “Writes that are safe to retry”- 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 thatimported_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_runto count first andexpected_countto refuse with 409 when the set changed since you counted it.
Accounts
Section titled “Accounts”| Endpoint | What 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=sales | Adds 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. |
Import
Section titled “Import”| Endpoint | What 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|duplicates | Download the rows an import didn’t take. |
POST /api/research/{id}/revert-import/ | Undo an import. |
Duplicates
Section titled “Duplicates”| Endpoint | What 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. |
Fit criteria and ranking
Section titled “Fit criteria and ranking”| Endpoint | What 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.
Campaigns, scoring and review
Section titled “Campaigns, scoring and review”| Endpoint | What 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.
Conversations and next steps
Section titled “Conversations and next steps”| Endpoint | What 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.
Today, runs and insights
Section titled “Today, runs and insights”| Endpoint | What it does |
|---|---|
GET /api/dashboard/today/?project=&scope=mine|team&today=YYYY-MM-DD | The 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. |
Marketing
Section titled “Marketing”| Endpoint | What 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.
Integrations
Section titled “Integrations”- 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.
Outreach review
Section titled “Outreach review”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:
| Action | Body |
|---|---|
confirm-attempt | occurred_on, occurred_at or date_unknown: true; optional channel, note |
correct-attempt | attempt, the date as above, reason |
void-attempt | attempt, reason |
link-message | attempt, message (or null to unlink) |
snooze | until (or null) |
set-owner | owner (or null) |
close | optional reason. People only |
reopen | reason. 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.