Skip to content

Account

An Account is an organisation stored in LeadHunter. It might be a prospect, a client, a partner, a reseller, a competitor, a journalist, an investor, a candidate, or part of your personal network — the same row serves all of those roles, with multi-select relationship types to label what each account is to you.

This is intentional. Most CRMs split “leads” and “customers” and “partners” into separate tables and then quietly accumulate duplicates across them. LeadHunter keeps one row per organisation and uses status + relationship type to describe context.

The standard fields, grouped by what they describe:

BucketFields
Identityname, business_type, specialization, language, logo_url, rating
Company profileyear_founded, employee_count_estimate
Locationaddress, city, state_province_region, postal_code, country, latitude, longitude, google_place_id
Contactphone, email, website (plus a separate Contacts list of people inside the account — see below)
Social profileslinkedin_url, instagram_url, x_url, facebook_url, youtube_url, tiktok_url
Statusstatus, status_changed_at, status_changed_by, status_source, status_history, external_contact_history
Classificationrelationship_types, acquisition_channel, acquisition_metadata, acquired_at
Provenancesource (how the row entered the system), is_verified, notes, merge_history
Cached contentwebsite_content (scraped text used by the AI), website_scraped_at, website_discovery_attempted
Customcustom_fields — per-project schema; see Custom fields.

Most fields have a sensible default and are optional. The only truly required field is name. Everything else can be populated incrementally — auto-enrichment fills website + content + language for free shortly after the row is created.

Open an account from any list. The page has a header and four tabs.

The header shows the name, location and website, and a Status chip (click it to change the status). Three buttons do the common things: Log message jumps to the composer, Add next step opens the next-step form, and Add to campaign puts the account in one of the project’s open campaigns. Adding checks the same guardrails as a bulk add, tells you when the account is blocked and which override applies, and then offers Open in Review and Open in Outreach. The ⋮ menu holds Edit details, Enrich with AI… (it asks first: it has a small cost) and All conversations.

TabWhat is on it
Activity (opens first)Next steps, Conversation (the composer and the latest messages) and Brief & notes. Beside them: Fit scores, Campaigns and, for clients, trials and accounts that sent usage, Client Pulse. A marketing partner’s stage and actions show here too.
ProfileCompany (contact details, business type, founded, employees, language, socials), custom fields, Relationship types, Source channel and the captured value.
PeopleThe contacts at the account — see Contacts live inside accounts.
AdminPer-purpose opt-outs, Status history (with Log past contact (from another system)), the Record (ids, data source, dates) and Archive or delete.

When an account lands in the database — by import, lookup, Google Maps discovery, or one-off entry — LeadHunter queues a background job that:

  1. Discovers the website when only a name + location was provided. Uses Gemini grounding plus a domain-guessing fallback.
  2. Scrapes the discovered URL and stashes the text content on the account (website_content). The cached content is what the scoring model reads later — it doesn’t re-fetch on every score.
  3. Extracts structured fields from the scraped content — the company’s logo, phone, email, address, the per-platform social URLs, founding year, employee-count bucket, primary language, and the business name when the row arrived without one.

Auto-enrichment is idempotent at three levels — so manual edits and partial fills always survive a re-enrich:

  • Row level: a row counts as already enriched when it has cached website content and the scrape is recent (within the cache window). Stale rows re-enter the queue automatically so the data doesn’t go cold; fresh rows are skipped entirely so a re-enrich doesn’t redo work.
  • Field level: every structured field is only written when the current value is blank. If you manually corrected the LinkedIn URL or fixed a phone number, that value wins forever — re-enrichment can fill other blanks but never overwrites yours. The same rule applies to merge survivors: every field already populated from one of the absorbed rows stays put.
  • Discovery level: when website discovery runs once and finds nothing, the row is flagged “discovery attempted” so subsequent passes don’t loop on the same dead-ends. You can still set a website manually at any time; the next enrich picks it up from there.

Wrong-kind URLs are auto-rediscovered. If the website value you imported turns out not to be a website at all — an audio stream URL from a radio export, a PDF link from a press pack, a download URL — enrichment now detects that on the first byte (no 15-second timeout, no wasted bandwidth) and tries to find the company’s real website automatically. When the stream carries a hint (Shoutcast/Icecast servers often include a homepage URL in their headers, for free), that hint wins. Otherwise the discovery agent runs and proposes a candidate URL, which is then verified to actually serve a webpage before the swap happens. The original URL is never lost — it lands in the account’s notes field with a small audit block explaining what kind of resource it was and why it got replaced, so you can always go back.

You’ll see an account grow website content, a language tag, socials, founding year, and a headcount bucket within minutes. Each row carries its own auto-tracked enrichment cost (USD, per model) so you can answer “how much did this account cost to fill in?” — the API returns it with the account (api_cost). See Usage and costs.

You can also re-enrich on demand: one account with Enrich with AI… in its page’s ⋮ menu, or many with Enrich in the Accounts page’s More menu — it offers the rows you ticked, only the accounts not enriched yet, the first 50 as a test run, or what the list currently shows. The progress bar on the Runs page reflects real work done, and large runs survive a redeploy without losing the chunks that already landed.

Company profile — year founded, employees, socials

Section titled “Company profile — year founded, employees, socials”

Three structured signals beyond the free-form business_type make accounts easier to segment and easier to pitch.

  • year_founded — the year the business started, when known. Filterable in saved views with the usual numeric operators (“founded since 2020”, “established 10+ years”). The auto-enrichment agent fills it when the website states it explicitly (“since 1985”, “founded in 2007”); otherwise it stays blank and you can enter it manually.
  • employee_count_estimate — a coarse headcount bucket: 1-10, 11-50, 51-200, 201-1000, or 1000+. The strongest single fit signal for B2B targeting, and the only headcount shape that survives across data sources (LinkedIn ranges, manual entry, scraped about-pages all map onto these five buckets). The scoring agent reads it when computing fit, so a 5-person shop and a 5,000-person enterprise won’t get scored against the same rubric.
  • Per-platform social URLs — linkedin_url, instagram_url, x_url, facebook_url, youtube_url, tiktok_url. One URL per platform, filled by enrichment from the website’s <a href> links (the first canonical match wins; share-intent links like “share on Facebook” buttons are skipped). They show as links under Socials on the account’s Profile tab so jumping into a DM thread is one click away. Manual edits are preserved on re-enrich.

All seven are optional and skipped silently when the website doesn’t mention them. They’re available as targets in the CSV import column-mapper too — common headers (LinkedIn, IG, Twitter, Founded, Employees, Company Size, …) are auto-recognised.

These fields are different from the Contact-level social fields. A Contact’s linkedin_url and twitter_handle point at a specific person inside the account (the marketing director, the CEO); the Account-level URLs point at the company’s own profiles. Both layers can be populated at once and they describe different things.

Fit scores — how well it matches what you’re after

Section titled “Fit scores — how well it matches what you’re after”

Each account can carry a fit score per product and purpose: a 0–10 rating against that product’s fit criteria, with a label and two to four reasons. The Fit scores card on the account’s Activity tab lists every score the account has (product, purpose, score, its band (Strong fit, Good fit, Weak fit or Poor fit), date, and Why this score for the reasons), or says Not scored yet. Under the list you can score the account with one of the project’s campaigns; it says first that it uses one AI call and, when the account isn’t in that campaign, that it adds it. A score made with an older version of the fit criteria carries an old criteria badge.

On the Accounts list, Fit for picks a product and purpose and adds a sortable Fit column; Rank scores any filtered list without a campaign (see Rank a list without a campaign). The best-scored accounts nobody is working yet show on Today as Best accounts to work next.

Each account has a single status:

StatusMeaning
prospectDefault. Not yet contacted.
contactedYou’ve reached out — at least one outbound message has been logged.
in_negotiationActive deal conversation.
in_trialTrying your product (shown as In trial). Blocked from outreach campaigns wherever clients are.
customerThey bought. In the app, a Client always means an account with this status.
lostDeal fell through.
do_not_contactHard opt-out. GDPR / CAN-SPAM stickiness — survives merges.

Status transitions are recorded with their source — manual, pre_existing (see below), campaign_outreach (auto-promoted when you log a first outbound message), import_exact / import_fuzzy / import_llm_verified (set during import dedupe), or inbound_acquisition (auto-promoted when the account is created with an inbound acquisition channel). Every change appends to status_history, so you have a permanent audit trail of who set what when, and why.

On the account page, click the Status chip in the header. The Change status dialog shows the status now, lets you pick the new one and add a reason (kept in the status history), and nothing changes until you click Apply. Do not contact and Client carry more weight (the first blocks every campaign and outbound message, the second blocks most outreach campaigns and records the account’s value), so the dialog says what each does before you apply it. Past changes are listed under Status history on the Admin tab. Through the API, status changes go through POST /api/accounts/{id}/change-status/; a plain account update ignores the status field, so no change can skip the audit trail.

Status moves forward without operator action in two cases:

  • Inbound channel on create — when an account is logged with an acquisition_channel that’s an inbound type (Adwords, Instagram DM, referral, …), it skips prospect and starts at contacted. The account has already reached out to you.
  • First outbound on a prospect — when you log a first outbound message in any campaign, the account advances from prospect to contacted.

Sometimes the account is already a customer when you add them to LeadHunter — a relationship that predates your funnel, an existing client you’re loading from a spreadsheet, or a long-standing renewal. You want them in the system as customer, but you don’t want the funnel to claim them as a new win you just closed.

In the Change status dialog, when you pick Client, tick Mark as pre-existing client. The transition is still saved with the full audit trail (you can see when and by whom), but the Closed (won) count on Insights → Funnel skips it. Cost-per-customer math (CAC) skips it too — these accounts weren’t paid for by the campaigns you’re running today.

The Closed (won) metric on Insights → Funnel counts a status transition only when both are true:

  • The account’s current status is customer or lost. A transition that was later flipped back to prospect or contacted is treated as a correction, not a real win, and drops out of the count.
  • The transition wasn’t tagged pre_existing.

Click Closed (won) on the funnel to jump straight to the matching accounts list, filtered to customer + lost.

Relationship types — what they are to you

Section titled “Relationship types — what they are to you”

Orthogonal to status, an account can carry one or more relationship types (multi-select — one row can be both client and partner, or reseller and press).

The fifteen values, with their campaign-outreach guardrail policy in a Sales campaign (other purposes admit the types they target — see Outreach reasons and targets):

TypeUse when…On bulk-add to a sales campaign
clientThey pay you.Allowed (the customer status separately gates outbound — see below).
prospectSales target — the default classification on most rows.Allowed.
resellerSells your product downstream.Allowed.
affiliateSends you referrals on commission. Marketing partner.Allowed.
partnerStrategic, co-marketing, integration. Marketing partner.Allowed.
influencerHas audience reach you want to borrow. Marketing partner.Soft block — flip include_influencers=true.
communityClub, association, forum or group. Marketing partner.Soft block — flip include_communities=true.
event_organizerOrganizes a fair or event you exhibit at or sponsor. Marketing partner.Soft block — flip include_event_organizers=true.
supplierYou buy from them.Soft block — flip include_suppliers=true to override.
investorCurrent or potential investor.Soft block — flip include_investors=true.
pressJournalist, publication, podcaster. Marketing partner.Soft block — flip include_press=true.
analystIndustry analyst (Gartner-style). Marketing partner.Soft block — flip include_analysts=true.
candidateHiring pipeline.Soft block — flip include_candidates=true.
competitorDirect competition.Hard block — never overridable.
personal_networkFriends, family, ex-colleagues.Hard block — never overridable.

Accounts with a type marked marketing partner appear under Marketing → Partners, with their own partner stage and audience size. When they carry no other type and aren’t a client or in trial, they stay out of the Accounts list (unless you tick Include marketing partners in its filter) and the client stats. You set the types on the account’s Profile tab, under Relationship types. See Marketing partners.

Two separate guardrails apply at bulk-add time too:

  • do_not_contact (status) — blanket opt-out. Blocks every campaign regardless of purpose. Hard, never overridable. Survives merges (GDPR / CAN-SPAM sticky).
  • customer (status, shown as Client) — soft block for sales-shaped purposes. Tick Include existing clients (for upsell) when adding (include_customers=true in the API) to re-engage existing clients in a campaign. (Customer-expansion / renewal / event / research / partnership campaigns admit clients by default — see Outreach reasons and targets.)

These rules only apply at campaign bulk-add. Logging an individual message to an account already in a campaign doesn’t re-run these checks — opt-outs are the exception: both the blanket do_not_contact status and a per-purpose opt-out matching the campaign’s purpose are enforced at message-send time too, so an opt-out recorded after the account joined a campaign still blocks that campaign’s composer. A message logged without a campaign on an account with per-purpose opt-outs must say which purpose it serves, and an opted-out purpose is refused the same way (see Messaging → DNC and per-purpose opt-outs).

Real-world consent is purpose-scoped. An account that opted out of sales emails may still be fine receiving research interview requests; a press contact who asked to be removed from your media list may still attend events. LeadHunter exposes this via do_not_contact_purposes — a list of campaign purposes the account has opted out of, separate from the blanket do_not_contact status.

A client who wrote in “please stop selling to me but we’d love to participate in your customer research” gets do_not_contact_purposes=['sales']. Sales campaigns are blocked from contacting them; research, customer-expansion, renewal, and event campaigns can still reach them.

Recording an opt-out is a click on the Per-purpose opt-outs panel on the account’s Admin tab. The panel renders current opt-outs as removable chips (removing one asks you to confirm first, since the account becomes reachable again by campaigns of that purpose), has a purpose picker for new opt-outs (with source and reason fields), and shows the most recent audit entries inline. Behind it: POST /api/accounts/{id}/record-dnc/ writes the purpose to do_not_contact_purposes and appends an entry to dnc_history — same shape as status_history. The audit trail covers opt_out and opt_in events, with source (manual / inbound_request / import) and an optional reason. This is the GDPR / CASL paper-trail surface — keep it accurate.

The two layers compose: an account is blocked from a campaign if either status='do_not_contact' (blanket) or the campaign’s purpose is in do_not_contact_purposes (purpose-specific). Neither layer is overridable from the bulk-add include flags. Through the API, opt-outs change via record-dnc or a sync push to upsert-by-imported-id (which never reverses an opt-out the person made themselves — see Pushing data back in); a plain account update ignores do_not_contact_purposes.

Before an account is created, LeadHunter looks for the same business in the project using six detection levels: Google Place ID and imported ID (100%), name + city after normalisation (95%), phone (90%), website domain (85%), and a similar name in the same city (85–94%).

A match never merges anything by itself. It stops the new account instead: a file import skips rows that match on Place ID, imported ID or name + city (without copying their values over), the new account form refuses any match and shows you the existing account, and research runs drop results that already exist. Rows that only share a phone, a website or a similar name are imported, so duplicates can still get in; you resolve them with Find duplicates, which proposes groups and merges the ones you confirm. Each path is spelled out in What happens on a match.

When a merge runs, the survivor becomes the golden record: every unique field from every duplicate is preserved (non-generic email beats info@, the company’s own domain beats an aggregator URL, the longer scrape wins, custom-field arrays union, and so on). The survivor’s merge_history JSONB grows an entry recording the absorbed rows’ full snapshots — returned with the account by the API, so you can always read what was on each side before the merge.

Status escalates and never demotes. do_not_contact survives every merge. The AI picks a canonical name when the duplicates disagree (“Joe’s Pizza” beats “Joe’s Pizza Restaurant LLC Holdings 2014”).

Merges aren’t reversible — the absorbed accounts are deleted at the end. The merge_history snapshot is an audit trail, not an undo button. See Merge duplicates for the full operator workflow.

Two different ways to remove an account from your day-to-day view — pick by intent.

Archive soft-hides the account from default lists while keeping every reference intact: campaign rows, scores, conversations, status history, expenses, audit trail. Use it when the row still belongs in your history but you don’t want to see it any more — the business closed, merged into a competitor, pivoted out of your market, or simply stopped being relevant. You can attach a free-text reason when archiving (recommended — “business closed Q1 2026”, “merged into X”, “out of geography”), which shows on the Archived chip in the account header and on the Admin tab. Archived accounts can be unarchived at any time, with a single click, no data loss.

A common case for archive: the statuses (lost, do_not_contact) describe intent toward the account — we tried to sell and didn’t close, or they opted out. Archive describes the account’s own state — the organisation no longer exists or is no longer relevant. Both can be true at once (you can archive a do_not_contact account if the business also closed), and the two flags are independent.

Delete permanently removes the row and cascades to every campaign row, score, conversation, contact, and expense reference. There is no undo. Delete account… requires you to type the exact account name to confirm — the typo gate is intentional, since the operation can’t be reversed. Use this only when you’re sure the row should never have existed (a test row, a manual data-entry mistake that the merge flow can’t fix). For everything else — even “definitely not coming back” — archive is the right answer.

Both actions live in the Archive or delete card on the account’s Admin tab.

Source channel — where the account came from

Section titled “Source channel — where the account came from”

Each account also carries a source channel (acquisition_channel in the API) — the marketing channel that brought them in: outbound (default — you found them), adwords, meta_ads, linkedin_ads, organic_search, organic_social (Instagram DM, X, …), referral, event, partner, influencer (creator promotion), cold_inbound, or other.

Inbound channels (everything except outbound and other) automatically start the account at contacted instead of prospect, because they’ve already reached out to you. The change is recorded in the audit trail with the channel as the reason, so it’s clear why the status skipped a step.

Channel-specific details — UTM parameters, ad campaign id, Instagram thread URL, the referrer’s email — go in acquisition metadata, a free-form key/value bag attached to the account.

On the account’s Profile tab, the Source channel card sets the channel, and its Action or channel picker can also credit the account to a specific acquisition channel or to a marketing action (a reel, an article, a fair booth with a partner); the channel type follows the one you pick. See Acquisition channels and Marketing partners → Actions.

See Track inbound accounts for examples on logging Adwords clicks, Instagram DMs, and referrals.

Your business probably tracks things that don’t fit standard fields — store size, vehicle count, license tier, contract expiry, whatever. Define them once as custom fields on your project; they then appear on every account’s Profile tab and can be used in saved views and campaign targeting.

An Account is the organisation. A Contact is a person sitting inside it — first / last name, email, phone, mobile, job title, department, seniority (c_level, vp, director, manager, senior, entry), LinkedIn URL, twitter handle, plus flags for primary contact and decision-maker. One account can have many contacts (or zero if you’ve only logged the org so far).

The two are different things — don’t confuse them. Filters, scoring, and campaigns all operate on Accounts. A campaign account can name the person you’re writing to (the campaign-account’s contact_* fields in the API; the campaign panel shows that name in its header), so the person is per-campaign even though the organisation is global. Next steps can name a contact too (Their contact).

One Contact per account can be flagged primary.

On the account page, contacts live on the People tab (the tab shows how many there are). Add person creates one; each row has Edit, Set as primary and Archive, and shows Primary and Decision maker badges. Archiving hides the person from the account and from the contact pickers without deleting anything: tick Show archived and click Restore to bring them back.

Only the first name is required. At a trade fair or on a first call you often get just “Marc” — leave the last name empty and add it when you learn it; the contact shows as its first name until then.

Over the API, contacts have their own endpoint, /api/contacts/: list them (filter by account, project or email), create one on an account, PATCH it as you learn more (a surname, a mobile number, a better email), or delete it. Deleting a contact archives it: it disappears from the account and from lists, but nothing is lost, and POST /api/contacts/{id}/restore/ brings it back (?archived=true lists archived contacts). Because an archived contact still owns its email on the account, re-adding the same person means restoring them, not creating a new contact.

When you merge two accounts, a contact on each with the same email is treated as the same person and folded into one: the surviving contact keeps what it has and picks up whatever it was missing (surname, phone, title), and both sets of notes are kept. Contacts with different emails always stay separate people. A contact stays on the account it was created on, and each account can hold a given email address once.

The notes field on an account is your team’s brief: pricing context, internal politics, “Maria says don’t email after 6pm”. The AI reads the start of the notes (about the first 500 characters) when it scores the account and when it drafts a message, so a short summary at the top helps both; keep anything you’d rather not have in an AI draft further down, or in a custom field, which the AI doesn’t read.

To add a remark without rewriting the whole field, POST /api/accounts/{id}/add-note/ with {"text": "…"}. It appends a line stamped with the date and your email, and it can’t overwrite a teammate who is editing the notes at the same moment — which is what reading the notes, adding a line and saving them back can do.

On the account page the notes show in the Brief & notes card on the Activity tab, rendered as Markdown: headings, bold, links, numbered and nested lists, - [ ] to-do checkboxes, tables, > quotes and code blocks. Every new line is a line break, so line-by-line notes stay as you wrote them. Lines starting with [YYYY-MM-DD name] (what the endpoint above appends) show as a Log: one entry per line, with its date and author, in the order they were written; lines below an entry belong to it. When the notes are long, the panel shows the start of the brief and the two newest log entries, and Show all opens the rest. Links only open http(s), mailto: and tel: addresses, HTML is shown as plain text, and images appear as a link instead of loading.

If you want a piece of context to influence scoring, put it in the account’s business_type / specialization or at the top of the notes; custom fields are for filtering and aren’t read by the scoring model. If you want it to influence outbound drafting, put it in the conversation history (every prior message is read when AI: continue thread runs) or in the campaign’s AI outreach brief (on its Outreach tab).

Status, types, and channel — what each one answers

Section titled “Status, types, and channel — what each one answers”

Three orthogonal fields describe an account’s place in your world, and it’s worth keeping them straight:

FieldAnswersSingle or multi?
statusWhere in the pipeline?Single
relationship_typesWhat kinds of relationship does this account have to me?Multi
acquisition_channel (source channel)How did this account first reach me?Single

A customer (status) tagged as both client and reseller (relationship_types) that arrived via adwords (acquisition_channel) is a perfectly coherent row. Each field answers a different question; none of them subsumes the others.

The account’s Activity tab has a Campaigns (N) card listing every campaign this account is part of. Each row shows the campaign name, its purpose, and where the account stands in that campaign (its review decision — To review, Shortlisted, Deferred or Not a fit — and its outreach status, such as Not contacted, Contacted or Replied), with archived campaigns sorted to the bottom. Add on the card (or Add to campaign in the header) puts the account in another campaign.

There are two links per row:

  • The campaign name opens the campaign’s Overview.
  • The Outreach button jumps straight into that campaign’s Outreach inbox with this account already open — so you go from “looking at the account” to “writing the next message” in one click. It opens the account even when it’s filtered out of, or sits past, the inbox’s current page.

If the account isn’t in any campaign yet, the card says so. You don’t need a campaign to log a conversation or to get a fit score. See Campaign for how accounts move into outreach, and Log a conversation for working a thread.