For agents and operators
Let your agent work your leads
leadhoundapi (leadhoundapi.com) finds local businesses whose public reviews show a service gap. This page is for the agent that works them. Three calls: list the markets you hold, read a market's leads, post back what you did. Reads are free and never start a scan.
Already on the dashboard? The Send to agent button beside Download CSV builds a ready-to-paste brief with your leads and these endpoints already filled in.
Quickstart
- Open the dashboard, go to the Account tab, and copy your API key. The full key is shown once, at activation or when you rotate it; after that only the prefix is displayed.
- Put it in your shell.
export LEADHOUND_KEY=lh_live_your_key_here
curl -s https://api.leadhoundapi.com/v1/markets \
-H "Authorization: Bearer $LEADHOUND_KEY"
That is the whole integration. An environment variable and curl work in every tool below.
Auth
One bearer header on every call. Same key, same header, same JSON error shape as the rest of the API. A missing or unknown key gets 401 and nothing else.
Authorization: Bearer lh_live_your_key_here
A key only ever reaches its own account. Another account's market answers 404 market_not_held, and a lead that is not in one of your markets answers 403 invalid_or_unentitled_lead. Neither response tells you anything about the account that does hold it.
The three endpoints
Everything an agent needs to run the loop. Base URL is https://api.leadhoundapi.com.
GET/v1/markets
Which markets this account holds, with a lead count and status breakdown for each. A market is one trade in one city.
curl -s https://api.leadhoundapi.com/v1/markets \
-H "Authorization: Bearer $LEADHOUND_KEY"
{
"schema": "leadhoundapi.agent_markets.v1",
"account": { "email": "[email protected]", "plan": "local", "offering": "websites" },
"territory": { "used": 1, "limit": 1, "cycle": "2026-07" },
"counts": { "new": 11, "contacted": 2, "replied": 1, "won": 0 },
"markets": [
{
"market_key": "dallas,tx::roofing",
"label": "Dallas, TX / Roofing",
"city": "dallas,tx",
"trade": "roofing",
"scanned_at": 1753747200000,
"lead_count": 14,
"directory_count": 51,
"fresh_count": 6,
"status_counts": { "new": 11, "contacted": 2, "replied": 1, "won": 0 },
"leads_url": "/v1/markets/dallas,tx::roofing/leads"
}
],
"billing": { "cost": "free" }
}
GET/v1/markets/{market_key}/leads
The leads in one market, exactly as the dashboard renders them: identity, contact, the public review evidence, the current opener, the status you last set, and whether the latest scan is where the business first appeared. Add ?status=new|contacted|replied|won to narrow it.
Market keys look like dallas,tx::roofing. Commas and colons are legal in a URL path, so it can go in unescaped; percent-encoding works too.
curl -s "https://api.leadhoundapi.com/v1/markets/dallas,tx::roofing/leads?status=new" \
-H "Authorization: Bearer $LEADHOUND_KEY"
{
"schema": "leadhoundapi.agent_leads.v1",
"market": { "market_key": "dallas,tx::roofing", "label": "Dallas, TX / Roofing", "scanned_at": 1753747200000 },
"leads": [
{
"lead_id": "ChIJ_example_place_id",
"name": "Acme Roofing",
"category": "Roofing",
"address": "1 Main St, Dallas, TX",
"phone": "555-0100",
"website": "https://acme.example",
"rating": 4.1,
"review_count": 62,
"status": "new",
"opener": "Saw the callback pattern.",
"evidence": ["Called twice and never got a callback."],
"source_dates": [1748736000000],
"confidence": 0.82,
"fresh": true,
"why_now": null,
"caution": false
}
],
"directory": [
{ "lead_id": "ChIJ_other", "name": "Northside Roofing", "phone": "555-0188", "judged": false }
],
"update_url": "/v1/markets/dallas,tx::roofing/leads/update",
"billing": { "cost": "free" }
}
leads is the ranked set: businesses with grounded evidence behind them. directory is everything else the same scan mapped, with no evidence attached. Both come from storage. Neither costs anything or triggers a scan.
POST/v1/markets/{market_key}/leads/update
Write back what you did. Send a status, an opener, or both. Status must be one of new, contacted, replied, won. Openers are capped at 1000 characters. This is the same storage the dashboard writes, so a status you post here is the status your customer sees on the board.
curl -s -X POST "https://api.leadhoundapi.com/v1/markets/dallas,tx::roofing/leads/update" \
-H "Authorization: Bearer $LEADHOUND_KEY" \
-H "content-type: application/json" \
-d '{"lead_id":"ChIJ_example_place_id","status":"contacted","opener":"Noticed two recent reviews mention calls going unanswered."}'
{
"schema": "leadhoundapi.agent_lead_update.v1",
"ok": true,
"market_key": "dallas,tx::roofing",
"lead_id": "ChIJ_example_place_id",
"status": "contacted",
"billing": { "cost": "free" }
}
A status-only post keeps whatever opener is already saved, and an opener-only post leaves the status where it was. Closing a deal takes {"status":"won","won_value_cents":180000} if you want the realized ROI number on the dashboard to be true.
The loop
What working a lead means, in the order an agent should do it.
- Read. Pull the market's leads. Start with
?status=new, and prefer fresh: true: those are what the latest scan surfaced.
- Personalize. Rewrite the opener from the
evidence quote, naming the one specific gap in plain language. The quote is a public review, not proof anyone wants to buy, so do not write as if it were.
- Reach out. Use the
phone or website on the lead. Keep a human on send.
- Write back. POST
contacted when you reach out, replied when they answer, won when they buy. Post the opener you actually used, so the next brief is true.
What not to do
Do not send anything without a human approving it. leadhoundapi gives evidence for outreach, not proof of intent, and a review complaint is a conversation opener, not a diagnosis.
Do not post contacted for outreach that never went out. The statuses feed the customer's own scoreboard and their realized ROI number.
If a lead carries "caution": true, its evidence alleges fraud or a takeover. Read it yourself before repeating any of it.
What costs money
Honest version: the three endpoints above are storage reads and one storage write. They are free, they are rate limited with the rest of the API by your plan, and nothing on this page will surprise you on a bill. Money is spent when you ask for fresh vendor data.
| Call | Cost | Notes |
| GET /v1/markets | Free | Storage read. No vendor call, no scan. |
| GET /v1/markets/{market_key}/leads | Free | Storage read. Returns the last scan on record, however old it is; scanned_at tells you. |
| POST /v1/markets/{market_key}/leads/update | Free | Storage write against your own account. |
| POST /v1/markets/scan | Metered | Fresh vendor data for a market you hold. This is the paid path. See pricing. |
| POST /v1/signals/*, /v1/changes | Metered | Pay as you go against your Signals balance, priced per call. See the API reference. |
Coverage plans price geography, not scan count: refreshes inside markets you already hold are part of the plan. Rate limits are per minute and per plan; a burst over the line answers 429 and costs nothing.
There is nothing to install. Every agent below can run curl, so every agent below already works. What helps is telling the agent, once, where the key lives and what the loop is. These snippets do that. If your tool prefers MCP over REST, the MCP server is the same account behind a different transport.
Claude Code
Put your key in the environment, then paste the block below into your project's CLAUDE.md so every session starts knowing the loop.
## leadhoundapi
Key is in $LEADHOUND_KEY. Auth: Authorization: Bearer $LEADHOUND_KEY
List markets: GET /v1/markets
Read leads: GET /v1/markets/{market_key}/leads
Update a lead: POST /v1/markets/{market_key}/leads/update
body: {"lead_id":"...","status":"contacted","opener":"..."}
Reads are free. Never call /v1/markets/scan without asking me.
Docs: https://leadhoundapi.com/agents
Codex
Same block, in AGENTS.md at the repo root. Codex reads it on every task, so the loop and the spend rule travel with the project.
echo 'export LEADHOUND_KEY=lh_live_...' >> ~/.zshrc
# then paste the leadhoundapi block into AGENTS.md
Hermes
Give the key to the machine Hermes runs on and hand it the same three calls. It is plain HTTP, so no adapter is involved.
curl -s "https://api.leadhoundapi.com/v1/markets/$MARKET/leads?status=new" \
-H "Authorization: Bearer $LEADHOUND_KEY" | jq '.leads[] | {lead_id,name,phone,evidence}'
OpenClaw
Store the key as a secret in your workspace and reference it from the task. The update call is the only write, so it is the only one worth a confirmation step.
curl -s -X POST "https://api.leadhoundapi.com/v1/markets/$MARKET/leads/update" \
-H "Authorization: Bearer $LEADHOUND_KEY" -H "content-type: application/json" \
-d "{\"lead_id\":\"$LEAD\",\"status\":\"contacted\"}"
Anything else
A shell and an environment variable is the whole contract. If it can run curl or issue an HTTPS request with a header, it works. There is no SDK to wait for.
From the dashboard
Send to agent, beside Download CSV in your leads view, writes the brief for you: the leads on screen, the endpoints above, and your key prefix as a hint. Copy, paste, go.
Errors
JSON on every path, with an error code and a plain message. Nothing here ever returns HTML.
| Status | Error | Meaning |
| 401 | invalid or missing API key | No bearer header, or a key that is revoked or unknown. |
| 403 | no_coverage_plan | The account holds no plan, so it holds no markets. |
| 403 | invalid_or_unentitled_lead | That market or that lead id is not on this account. |
| 404 | market_not_held | This account does not hold that market. GET /v1/markets lists the ones it does. |
| 400 | invalid_status | Status was not one of new, contacted, replied, won. |
| 400 | opener_too_long | Opener was over 1000 characters. |
| 400 | nothing_to_update | The post carried neither a status nor an opener. |
| 429 | rate limited | Per-minute plan limit. Back off and retry; it costs nothing. |
Same agent, same workflow
The same agent that built your workflow can research public service-gap reports, draft the opener, and record the outcome. What it must not do is decide on its own to spend: scans are the metered path, and they stay a deliberate call.
User: Pull the new roofing leads in Dallas and draft openers.
Agent: GET /v1/markets/dallas,tx::roofing/leads?status=new returned 11 leads. Free read, nothing spent.
Agent: Drafted 11 openers from the review quotes. Nothing sent.
User: I called the first three. Mark them contacted.
Agent: POST update x3. Statuses are on your dashboard.