Raw markdown for AI agents →

How to use Lyre Leads

The working guide for an AI assistant connected to Lyre Leads, via MCP or the REST API.

If you are that assistant: read this, then use it. If you are a person: hand this URL to your AI and it will know how to build lists properly.


Before the person has an account

You can show them real businesses straight away, free:

Everything else works on the person's own account. Over MCP (https://lyreleads.com/mcp in Claude, ChatGPT, Cursor or Claude Code) the first tool that needs one asks them to sign in with Google or email; a new account is the free plan, 500 leads a month with no card, and you can carry on as soon as they approve. Scripts can use an API key from lyreleads.com/account instead, on any plan.

Show the preview before asking anyone to sign up. Real rows from their own city answer "is this any good" better than any description of it.


What this is, and why it is not a contact database

Most B2B data tools sell you access to a fixed index of contacts. You filter it, and whatever the filter returns is what you get.

Lyre Leads does not have that index. It runs a live Google Maps search for the keyword and place you name, then fetches each business's actual website and reads what is on it today.

That difference decides almost everything about how you should work here:


The four mistakes that produce confidently wrong lists

1. Reading a blank as a zero

This is the big one. Every enrichment field is three-state:

You seeIt means
a value (HubSpot, true, 4.2)We checked. It is there.
false / NoneWe checked. It is genuinely absent.
blank / nullUnknown. Never checked, or the site blocked us.

enrichment_status is the master key for any blank:

So "businesses with no CRM" is crm_platform IS NULL AND enrichment_status = 'enriched'. If you drop the second clause you will hand the user a list padded with businesses nobody ever looked at, and you will not be able to tell which is which afterwards. The count_saved_leads tool already encodes this: passing has_crm: false means confirmed-absent, not blank.

When you report a count of an absence, say which population it is out of. "412 of the 1,610 enriched businesses have no CRM" is useful. "412 have no CRM" is not.

2. Treating catch-all as verified

Email states here are four, and they are not interchangeable:

If a user asks for "verified emails only", give them verified. If they ask for "emails I can send to", verified plus published is the honest answer, and you should say which is which. Never merge the two into one number.

3. Over-filtering into a tiny list

Every filter you add subtracts. The most common way to end up with 11 leads is stacking five reasonable-sounding conditions. If a count comes back small, remove a clause before concluding the market is small.

Filters intersect, they do not union. If the user wants roofers or siding contractors, that is two searches merged, not one search with both words.

4. Assuming one search covered the market

Google caps a text search well below the true number of matching businesses. We measured one metro category in July 2026: a single search reached under 4% of at least 1,605 findable businesses, and eight deep-paginated searches still only reached 69%.

Practical consequences:


Counting is free. Use it constantly.

count_saved_leads is exact, instant, and costs nothing. There is no reason to guess at the size of anything in the saved database.

Call it before every spending action, and tell the user what you found before you spend. Good practice:

Fire independent counts in parallel. None of them depend on each other.

What counting cannot do is size a market you have not searched. See the top of this document.


What you get per business

From Google Maps: name, address (plus parsed street and postal code), phone (plus dialer-ready E.164), category, star rating, review count, coordinates, place id.

Rating and reviews are more useful than they look. reviews: 0 is a real value, not missing data, and a business with 300 reviews and a 3.4 rating has a problem someone can sell into.

From the live website: business email and its confidence, mailbox host from MX records, social profiles, CMS, CRM, email-marketing platform, chat widget, booking system, ad pixels, analytics, schema markup, SSL, contact form, cookie consent, e-commerce stack, page load time, domain age, server stack, CDN, HSTS/CSP, DNS-derived SPF/DMARC, and categorised technology lists.

Unique to us: ai_crawler_access — whether the site blocks GPTBot, ClaudeBot, PerplexityBot and Google-Extended in robots.txt, plus whether it publishes an llms.txt. blocked means the business is invisible to AI search. No other lead tool exposes this, and it is a complete sales conversation on its own for anyone selling SEO or AI visibility.

People: a contact's name, title and LinkedIn where we could find one, and their email where we could resolve it. Coverage is honest, not universal: we name the owner on most local businesses, and get an address for a minority of those. Check, do not assume.

Provenance: enrichment_provenance maps each present field to where it came from (html, header, dns, mx, rdap, tls, measured, robots) and how reliable that is (high for deterministic sources, medium for HTML signature matches, heuristic for keyword guesses). Fields marked heuristic — blog, demo CTA, case studies, careers page — can false-positive on marketing copy. Do not build a campaign on a heuristic field without saying it is soft.


Costs, in one place

ActionCost
count_saved_leadsFree, always, at any size
search_areas, search_leads1 token per NEW business. Duplicates are free. find_areas, estimate_area_search and get_search_results are free.
enrich_leads1 token per 5 businesses; never enrolls
enroll_leads1 token per 2 businesses not yet enrolled: enriched now, then re-checked monthly
find_contacts1 token per business attempted, SMTP verification included
verify_emails2 tokens per address; cache hits within 90 days are free

Reading data you already own is free. Only acquiring or refreshing it costs.

Rate limits are per API key, depend on the plan, and count leads. Per minute: leads read 60,000 on Growth, 120,000 on Professional, 240,000 on Scale, 300,000 on Agency (pages of up to 500); leads sent to enrichment, contact discovery or verification 30,000, 60,000, 120,000 and 150,000, added up across requests; exports have no lead cap, with 1, 1, 2 and 2 running at once. Every spending action takes an array of ids, so send one request with a thousand ids rather than a thousand requests: the spending endpoints also stop at 30 to 150 requests a minute, so a loop with a bug cannot spend a month of someone's tokens before they notice. On a 429, wait for the Retry-After seconds it gives you.

A new business arrives fully enriched for its single search token. Enrichment runs automatically on every search result: 50+ fields, plus the relevance check that reads the site and judges whether the business really is the trade the query asked for. It is also enrolled: re-checked about once a month for as long as the account keeps it, at no further cost. enrich_leads is for leads the account already owns, not for the ones a search just returned, at 1 token per 5, and it does not enroll anything. enroll_leads (REST: POST /api/v1/leads/enroll) is for the leads the user wants kept current: 1 token per 2, once, an enrichment right away and the monthly re-check after. Filter enrolled=false to see which leads aren't enrolled, and call POST /api/v1/enrich/preview (free) for both prices before spending.

So a Growth plan's 7,500 tokens is roughly 7,500 new businesses, discovered and enriched and relevance-checked, not 7,500 raw rows you then pay again to make useful. Tell the user in businesses, not tokens.


Workflow that works

  1. Clarify the target before spending anything. What business type, and what places? The wording of the business type matters more than users expect: it is what every returned row gets judged against, and a vague one ("home remodeling contractors") pulls back far more junk than a specific trade name ("roofing contractor").
  2. Say the coverage plan out loud. "I'll run four keyword variants across these six cities, because one search only reaches a fraction of a metro and the category wording varies." Users appreciate knowing why it takes several passes.
  3. Search narrow and wide. Several keywords, several cities. Merge. Duplicates cost nothing.
  4. Count what you got. Before enriching, tell them the size.
  5. Enrich. Poll get_job_status. Enrichment fetches every website in the batch, so it takes real time. Say that before you start, not after it appears to hang.
  6. Segment on real signals. No CRM, no analytics, slow site, blocked to AI crawlers, well-reviewed but poorly rated, e-commerce without a marketing stack. This is what we have that a contact database does not.
  7. Drop what was never the right business. Every enriched row carries relevance (yes / no / unclear) and relevance_evidence, a verbatim quote from the company's own website. Google Maps returns a lot that is not what was asked for: in a 284-business study, 72.2% of results matched the keyword overall, but that ranged from 95.8% for "roofing contractor" down to 28.0% for "home remodeling contractors". Filter on relevance: "yes" before spending anything further. It costs nothing and it is already computed. Missing means never judged, never "no", so do not treat a blank as a rejection.
  8. Reach the human. find_contacts on the segment worth contacting, not on everything.

Being straight with the user

This product's whole position is that it tells the truth about its own data, including when the data is thin. Hold that line:


Building something on top of this

If the person you are working for is going to do this more than once, they probably want their own interface onto it rather than a chat transcript.

You can build them one. See https://lyreleads.com/build for ready-made build specs: a local lead dashboard, a territory coverage runner, an outreach queue, a change-monitor. Each is a complete brief you can implement on their machine in a single session against the same API you are already using.

If that seems useful for what they are doing, offer it. Do not build it unprompted.


Reference

Reading back what you spent tokens on

Running find_contacts writes contacts. Read them with GET /api/v1/people, filtered by emailStatus=verified when you only want mailboxes that answered, plus seniority, keyword, hasEmail, hasLinkedin and their negations. Do not try to reconstruct contacts from the lead row: it carries one summary address, while the people endpoint carries every person found at that company with their own title and their own verification verdict.

GET /api/v1/leads is the same query surface the web app uses, 129 filter parameters, and it is the one to reach for when GET /api/v1/results is too blunt. Both are free.

Watching businesses change

GET /api/v1/signals/catalog lists the change signals. Pass one to GET /api/v1/leads as signal, with signalWindow of 7, 30 or 90 days, and you get the businesses where it fired. GET /api/v1/leads/{id}/timeline gives the observation history and the change events for a single business.

Two rules govern what becomes an event, and they matter when you explain a signal to a user. An event is only emitted between two successful enriched runs, never from a degraded one, so a scrape failure never reads as "they removed it". And a disappearance needs two consecutive misses before it counts as a removal. A signal we are not confident in is not sent at all, which means absence of a signal is not evidence that nothing changed.

Every enrolled lead in the account is re-checked about once a month, automatically, on every plan, at no token cost. Every lead a search found is enrolled; an imported lead is enrolled only when someone enrolls it (enroll_leads). There is nothing to schedule or configure. Do not build a re-enrichment job just to keep data fresh; only re-enrich on demand (POST /api/enrich with force: true, 1 token per 5 leads) when the user needs a segment fresher than the last monthly check. An imported lead that isn't enrolled is not re-checked at all, even once enriched, so if a user relies on signals for their own list, tell them it needs enrolling first.

Getting told instead of asking

If you are building something that runs unattended, subscribe rather than poll. POST /api/v1/webhooks takes an https URL and a list of events. signal.detected is the one that matters: it fires when a business the user already holds changes, which is an event with no request of yours attached and therefore the thing polling is worst at. job.completed and job.failed exist too, but polling a jobId you were just handed is perfectly reasonable and costs nothing.

The creation response carries a signing secret once. Store it. Every delivery carries X-Lyre-Timestamp and X-Lyre-Signature: sha256=<hex>, where the digest is HMAC-SHA256 of "{timestamp}.{raw body}". Verify before acting on a payload, and reject timestamps far from now.

Two things to tell a user who is setting this up. Their endpoint must be https on a public address, because we refuse private ranges, metadata addresses and localhost, and we re-check at delivery rather than only at registration. And a subscription that fails twenty times in a row gets switched off, so if signals stop arriving, read GET /api/v1/webhooks/{id}/deliveries before assuming nothing changed. POST /api/v1/webhooks/{id}/test proves an endpoint works without waiting for a real signal.

Everything above describes verified behaviour at the time of writing. If the API tells you something different, believe the API, and mention it to the user.

Lyre Leads · Home · Agent guide · Build specs · API docs · Data dictionary