# Nose for Leads: agent integration guide

Nose for Leads runs local-business lead campaigns and validates each lead's
email against the live mailbox provider before it's delivered. It's built for
agencies, SDRs, and founder-led sales teams. This page is the reference for
connecting an agent (or writing your own integration) directly against the
MCP server or the underlying REST API.

## Connect

- MCP endpoint: `https://api.noseforleads.com/mcp`
- Transport: streamable HTTP
- Sign up: https://app.noseforleads.com/signup
- OpenAPI schema (REST surface, no credential needed to fetch): https://api.noseforleads.com/openapi.json

### Claude Code (recommended: OAuth, no key to paste)

Run these two commands once:

```
claude mcp add --transport http nose-for-leads https://api.noseforleads.com/mcp
claude mcp login nose-for-leads
```

`add` writes the client config. `login` opens your browser to approve access and set a
spending budget. There's no client to register and no client id or secret to paste: the
server supports dynamic client registration, so the plain URL is enough. The connection
is one-time: the token persists across sessions and refreshes itself, so you won't see
this again unless you revoke it (Keys page, https://app.noseforleads.com/keys).

### Codex CLI

Add the server to `~/.codex/config.toml` (or run `codex mcp add nose-for-leads --url
https://api.noseforleads.com/mcp`), then log in:

```toml
[mcp_servers.nose-for-leads]
url = "https://api.noseforleads.com/mcp"
```

```
codex mcp login nose-for-leads
```

The login flow is the same browser consent as Claude Code (standard MCP OAuth). If
login gives you trouble, the API-key header below works in Codex too:
`env_http_headers = { "X-API-Key" = "YOUR_ENV_VAR" }` under the same config block.

### Cursor, Windsurf, other MCP clients

Point your client's HTTP/MCP server config at `https://api.noseforleads.com/mcp`
(the `.mcp.json` shape below is what Cursor's `.cursor/mcp.json` expects). Clients
without OAuth support use the API-key header form instead.

### Alternative: API key

For scripts, CI, or a client that doesn't do OAuth, mint a key on the Keys page
(https://app.noseforleads.com/keys) and pass it as a header instead:

```
claude mcp add --transport http nose-for-leads https://api.noseforleads.com/mcp --header "X-API-Key: <your key>"
```

Any MCP client can use this same header form: `X-API-Key: <your key>` (or
`Authorization: Bearer <your key>`).

### Team setup: `.mcp.json`

Commit this to a repo so teammates get the server automatically when they clone it.
Each teammate still authenticates once with their own login:

```json
{
  "mcpServers": {
    "nose-for-leads": { "type": "http", "url": "https://api.noseforleads.com/mcp" }
  }
}
```

### Spend caps

Mint a separate, budget-capped key for your agent rather than reusing a
human key. A key can carry a total credit budget and/or a daily credit
budget, so a runaway loop can't overspend past the limit you set. Once a
key's budget is spent, it stops submitting new campaigns with a
`budget_exhausted` error, before it ever touches the account-wide credit
balance.

## Tools

The MCP server exposes 9 tools, all requiring the auth set up above (OAuth token or API key).

| Tool | What it does |
|---|---|
| `translate_icp` | Turns a free-text ICP description (e.g. "plumbers in phoenix with no website") into the structured `query` dict `start_campaign` requires. Call this first: `start_campaign` never accepts free text. |
| `start_campaign` | Submits a lead campaign for a vertical + geo. Costs 1 credit per validated lead and cannot overspend: if discovered leads exceed your balance, it delivers what your credits cover and the campaign ends `done_partial`. Accepts an `idempotency_key` for safe retries. Returns `{job_id, idempotent_replay}`. |
| `get_campaign_status` | Checks a campaign's status by `job_id`: `queued`, `running`, `done`, `done_partial`, or `failed`. |
| `fetch_results` | Pages through a campaign's leads (`offset`/`limit`). `kept_only=true` returns only leads marked kept in review: the export set. Rows carry verification receipts when a live verification exists. |
| `get_icp_pack` | Lists saved vertical/geo templates (ICP packs) available to the account. |
| `add_suppression` | Adds an email or domain (`scope: "email" | "domain"`) to the suppression list so future campaigns exclude it. |
| `review_lead` | Marks a lead `kept`, `discarded`, or `unreviewed`. `fetch_results(kept_only=true)` then returns only kept leads. |
| `get_credits` | Checks the account's prepaid credit balance (1 credit = 1 validated lead), available packs, and recent ledger entries. When called with an API key, also returns that key's remaining total/daily budget. |
| `send_feedback` | Sends feedback to the Nose for Leads team. Use `kind: "product"` for feedback about leads, coverage, or pricing, and `kind: "tool"` (default) for feedback about these MCP tools themselves. |

## Credit semantics

- 1 credit = 1 validated lead.
- A campaign cannot start with a zero account balance. Check `get_credits`
  first if the balance is low, and buy a pack via the dashboard when it runs
  out.
- If a campaign discovers more leads than the account has credits for, it
  delivers what the balance covers and ends in status `done_partial` rather
  than failing outright.
- A key's own budget cap (set on the dashboard) is enforced before the
  account-wide balance check, so a capped key gets `budget_exhausted` instead
  of drawing on the full account.

## Verification receipts

Rows returned by `fetch_results` carry a verification receipt when one
exists:

- `verified_by`: which verifier performed the check
- `verified_at`: when the check ran
- `verifier_verdict`: the verifier's result

A receipt proves the lead's email was checked against the live mailbox
provider, not merely pattern-matched. A `null` receipt means no verification
has run yet for that row. That alone is not a failure signal.

## Idempotency

Pass a stable `idempotency_key` to `start_campaign` whenever a retry is
possible (timeout, dropped connection, retried tool call). A retry with the
same key returns the original campaign (`idempotent_replay: true`) instead of
starting a second campaign and charging twice.

## Errors

Every tool returns a plain `{code, message}` envelope on failure instead of
raising. Some codes carry an extra field.

| code | meaning | extra field |
|---|---|---|
| `auth_failed` (401) | missing or invalid credential | |
| `key_expired` (401) | the API key has expired, mint a new one | |
| `not_found` (404) | job or result id not found | |
| `invalid_input` (422) | malformed request (e.g. bad review status, missing vertical/geo) | |
| `payment_required` (402) | account credit balance is 0 | |
| `card_required` (403) | no verified card on file yet | `setup_url`: send the user here to add one |
| `budget_exhausted` (403) | this key's own spend cap is used up | |
| `quota_exceeded` (429) | rate limited | `retry_after`: seconds to wait before retrying |
| `internal_error` (500) | unexpected server error | |

## Feedback

Call `send_feedback` freely. It's the main signal for improving both the
leads themselves and the agent tool experience.
