Find verified professional email addresses with the FinalScout API — by LinkedIn profile URL, by full name + company domain, or by news article URL (author l...
---
name: b2b-contact-enrichment
description: "Find verified professional email addresses with the FinalScout API — by LinkedIn profile URL, by full name + company domain, or by news article URL (author lookup). Supports single lookups (long-polling waterfall or submit-and-poll), bulk batch tasks with progress tracking, paginated result dump, CSV export, webhooks, and account credits / rate-limit checks."
version: 1.1.0
metadata:
openclaw:
primaryEnv: FINALSCOUT_API_KEY
envVars:
- name: FINALSCOUT_API_KEY
required: true
description: "FinalScout API key. Get yours at https://finalscout.com/app/api/settings"
emoji: "📧"
homepage: https://finalscout.com
---
# FinalScout Email Finder & Contact Enrichment
Find verified professional email addresses. Pick the find method by what the user has:
| User has | Method | Single endpoint | Bulk endpoint |
|----------|--------|-----------------|---------------|
| LinkedIn profile URL | `linkedin` | `POST /v1/find/linkedin/single` | `POST /v1/find/linkedin/bulk` |
| Full name + company domain | `professional` | `POST /v1/find/professional/single` | `POST /v1/find/professional/bulk` |
| News article URL (find its author) | `author` | `POST /v1/find/author/single` | `POST /v1/find/author/bulk` |
**Base URLs:**
- Standard API: `https://api.finalscout.com`
- Waterfall API (long-polling single finds): `https://api-waterfall.finalscout.com`
**Auth:** every request needs the header `Authorization: $FINALSCOUT_API_KEY` (raw key, no `Bearer` prefix).
**Choosing a flow:**
- 1 to a few contacts → **Waterfall single** (one blocking call per contact, no polling).
- Many contacts (CSV, list, table, JSON) → **Bulk** (async task: submit → poll status → dump/export results).
---
## Person object per method
The `person` object (single) / `persons` array items (bulk) differ by method:
**`linkedin`** — only `linkedin_url` is required:
```json
{
"linkedin_url": "https://www.linkedin.com/in/williamhgates/",
"full_name": "",
"current_positions": [
{"company_name": "", "company_linkedin_url": "", "title": "", "location": ""}
],
"meta_data": {"your_key": "your value"}
}
```
Supported `linkedin_url` formats: `https://www.linkedin.com/in/<slug>`, `https://www.linkedin.com/in/<ACoAA... id>`, Sales Navigator `https://www.linkedin.com/sales/people/<id>,name,sSHS` and `https://www.linkedin.com/sales/lead/<id>,name,sSHS`.
Supported `company_linkedin_url` formats: `https://www.linkedin.com/company/<slug|id>`, `https://www.linkedin.com/sales/company/<id>`.
Cost is 1 credit per email found whether you send just the URL or the URL plus full profile info (`full_name`, `company_name`, `company_linkedin_url`, `title`, `location`).
**`professional`** — `full_name` and `domain` are required:
```json
{
"full_name": "Bill Gates",
"domain": "microsoft.com",
"domain_extra": ["gatesfoundation.org"],
"deep_verify": false,
"meta_data": {"your_key": "your value"}
}
```
`domain_extra` (optional): extra candidate domains. `deep_verify` (optional, default `false`): deeper email verification — only set `true` when confident the domain is valid for the person.
**`author`** — only `article_url` is required:
```json
{
"article_url": "https://www.forbes.com/sites/.../article-slug/",
"meta_data": {"your_key": "your value"}
}
```
`meta_data` (optional, all methods): flat object of string values (e.g. CRM contact id). Echoed back in responses, dump items, and webhook events — use it to correlate results with the user's data.
---
## Single lookup — Waterfall (preferred)
One blocking call; the server holds the connection until the task finishes or `timeout` (seconds, 30–600, default 80) is reached.
```bash
curl -s -m 130 -w "\nHTTP %{http_code}" \
"https://api-waterfall.finalscout.com/find/professional/single?timeout=120" \
-H "Authorization: $FINALSCOUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"person":{"full_name":"<name>","domain":"<domain>"}}'
```
Swap the path for `/find/linkedin/single` or `/find/author/single` with the matching `person` body. Set curl's `-m` (max time) a bit above the `timeout` value. Longer timeouts (up to 600) mean fewer round-trips; use a shorter one if your shell caps command runtime.
**Responses:**
- `HTTP 200` → task complete; read `contact`, `credits_consumed`, `credits_available` (see [Interpreting results](#interpreting-results)).
- `HTTP 408` → still running; body has `{"id": "...", "status": "Running"}`. Resubmit the same request to keep waiting, or poll `/v1/find/single/status?id=<id>`.
Optional body fields (top level, next to `person`):
- `"tags": ["tag1"]` — organize contacts in the FinalScout web app (all methods)
- `"webhook_url": "<url>"` — receive a `single_task.finished` event (all methods; see [Webhooks](#webhooks))
- `"enable_work_email": true` — default `true`; find work emails (linkedin method only)
- `"enable_personal_email": false` — default `false`; fall back to personal emails, e.g. Gmail (linkedin method only)
- `"enable_generic_email": false` — default `false`; fall back to generic emails, e.g. support@ (linkedin method only)
## Single lookup — submit + poll (alternative)
Use when a long-held connection is not an option.
**Submit** (same bodies and optional fields as waterfall):
```bash
curl -s -X POST https://api.finalscout.com/v1/find/professional/single \
-H "Authorization: $FINALSCOUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"person":{"full_name":"<name>","domain":"<domain>"}}'
```
Save the returned `id`. If `status` is already `Complete`, done.
**Poll** every 3 seconds until `status` is `Complete` (works for any single find task, all three methods):
```bash
curl -s "https://api.finalscout.com/v1/find/single/status?id=<id>" \
-H "Authorization: $FINALSCOUT_API_KEY"
```
Give up after ~40 attempts (2 minutes) and report the task `id` so the user can check later.
---
## Bulk lookup
### 1. Submit
Parse the user's input (CSV, list, table, JSON) into a `persons` array of the matching person objects. Strip empty rows.
```bash
curl -s -X POST https://api.finalscout.com/v1/find/professional/bulk \
-H "Authorization: $FINALSCOUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"persons": [
{"full_name": "Bill Gates", "domain": "microsoft.com"},
{"full_name": "Elon Musk", "domain": "tesla.com"}
],
"name": "Bulk find - <date>",
"duplicate": "skip_duplicates"
}'
```
Endpoints: `/v1/find/professional/bulk`, `/v1/find/linkedin/bulk`, `/v1/find/author/bulk`.
Optional top-level fields (all bulk endpoints unless noted):
- `"name"` — task name, max 300 characters
- `"duplicate"` — `skip_duplicates` (default) or `scrape_and_update_duplicates` (re-process contacts already in the account)
- `"tags": ["tag1"]`
- `"webhook_url": "<url>"` and `"webhook_subscribe_events": ["bulk_task.finished", "contact.change"]` — see [Webhooks](#webhooks)
- `"enable_work_email"` / `"enable_personal_email"` / `"enable_generic_email"` — linkedin bulk only, same defaults as single
The response is the task: `{id, status, total, finished, success, duplicated, credits_consumed, credits_available, ...}`. Save `id`.
### 2. Poll status
```bash
curl -s "https://api.finalscout.com/v1/find/bulk/status?id=<id>" \
-H "Authorization: $FINALSCOUT_API_KEY"
```
Poll every 5 seconds; report progress as `finished / total`. Task `status`: `Queued` → `Running` → `Completed` or `Failed`.
### 3. Dump results (after Completed)
```bash
curl -s "https://api.finalscout.com/v1/find/bulk/dump?id=<id>&page_size=100" \
-H "Authorization: $FINALSCOUT_API_KEY"
```
Response: `{cursor, task: {id, status}, items: [{contact, meta_data}]}`. If `cursor` is non-empty, request the next page with `&cursor=<cursor>`; repeat until `cursor` is null/empty. `page_size` range 10–100 (default 50). Cursors expire after 1 day.
### 4. Present results
| Name | Input | Email | Type | Status | Score |
|------|-------|-------|------|--------|-------|
| Bill Gates | microsoft.com | bill@microsoft.com | Work email | Valid | 95 |
| ... | ... | no email found | - | - | - |
Summary line: `X / Y emails found. Z credits consumed, W credits remaining.`
### CSV export (optional)
When the user wants a file instead of (or in addition to) inline results:
```bash
curl -s "https://api.finalscout.com/v1/find/bulk/export?id=<id>" \
-H "Authorization: $FINALSCOUT_API_KEY"
```
Response: `{"status": "Generating download link" | "Ready", "download_url": "..."}`. If still generating, retry after a few seconds until `Ready`. Warn the user: the link is public (anyone with it can download) and expires after 7 days.
---
## Account info
```bash
curl -s https://api.finalscout.com/v1/account \
-H "Authorization: $FINALSCOUT_API_KEY"
```
Returns `owner_email`, `credits_available`, and per-endpoint `rate_limit` entries. Use this to check the balance before large bulk jobs or to diagnose 429s.
---
## Webhooks
Pass `webhook_url` in any find request to get HTTP POST callbacks instead of / in addition to polling:
- **`single_task.finished`** — single find completed. Contains `contact` when an email was found, otherwise a `message`; plus `task_id`, `credits_consumed`, `meta_data`.
- **`bulk_task.finished`** — bulk task completed. Task-level summary (`total`, `finished`, `success`, `duplicated`, `credits_consumed`, `credits_available`); no per-contact `meta_data` at this level.
- **`contact.change`** — sent during bulk tasks for each contact whose email is found (or when an existing contact is updated). Contains `contact` and that contact's `meta_data`. Nothing is sent for contacts with no email found.
Bulk requests can filter events with `webhook_subscribe_events` (array of the two bulk event names); if unset, all events are sent.
---
## Interpreting results
Contact fields that matter most:
- `email` + `email_status`: `Valid` (safe to send), `Risky`, `Invalid`, `Unknown`. Only `Valid` is guaranteed deliverable; show `Risky`/`Unknown` emails with a caveat.
- `email_type`: `Work email`, `Personal email`, or `Generic email`.
- `email_score` (0–100): deliverability confidence; higher is better.
- `email_is_catchall`: domain accepts all mail; FinalScout's verification (via BounceBan) still makes these as reliable as standard addresses — mention it, don't discard.
- No `contact` in a single find response (or a contact without `email`) → no email found; no credit charged.
- Enrichment extras when available: `title`, `company`, `location`, `linkedin`, `website`, `industry`, `company_domain`, `company_phone`, company address fields, etc. Include them if the user asked for enrichment.
Always surface `credits_consumed` and `credits_available` after an operation.
---
## Error handling
| HTTP status | Meaning | Action |
|-------------|---------|--------|
| 400 | Invalid parameter | Fix the request body/params; re-check required fields |
| 401 | Bad/missing API key | Ask the user to check `FINALSCOUT_API_KEY` (and IP whitelist at finalscout.com/app/api/settings) |
| 403 | Insufficient credits | Report `credits_required` vs `credits_available` from the response body |
| 405 | Account blocked | Tell the user to contact dev@finalscout.com |
| 408 | Waterfall timeout (waterfall endpoints only) | Task still running — resubmit the same request or poll `/v1/find/single/status` |
| 429 | Rate limited | Wait 2 seconds and retry once; if repeated, slow down (limits via `/v1/account`) |
| 500 | Server error | Retry once; if it fails again, report the error |
## Rate limits (typical defaults — actual values via `/v1/account`)
| Endpoint | Limit |
|----------|-------|
| /find/linkedin/single | 50 / second |
| /find/author/single, /find/professional/single | 5 / second |
| /find/single/status, /find/bulk/status, /find/bulk/export | 25 / second |
| /find/*/bulk (all three) | 2 / second |
| /account | 5 / second |
## Credits
Universal credits: each email successfully found costs 1 credit, regardless of method. No charge when no email is found. Need test credits or higher limits → dev@finalscout.com.
don't have the plugin yet? install it then click "run inline in claude" again.
added explicit decision tree for single vs bulk and waterfall vs poll, broken procedure into numbered steps with clear inputs/outputs, documented all rate limits and edge cases (catchall domains, 408 timeouts, 429 throttling, insufficient credits, missing fields), clarified contact object schema per method, separated webhook flows from polling, added outcome signals for each flow type, and included full error handling matrix.
find and verify professional email addresses using the FinalScout API. use this skill when you need to enrich b2b contact data by linkedin profile, full name + company domain, or news article author. supports single lookups (waterfall blocking or submit-and-poll), bulk batch operations with progress tracking, paginated result dumps, csv export, webhooks, and account credit checks. choose the method based on what data you have and how many contacts you're processing.
required environment
FINALSCOUT_API_KEY: raw api key (no bearer prefix). get it from https://finalscout.com/app/api/settings. include as Authorization header on all requests.api base urls
https://api-waterfall.finalscout.comhttps://api.finalscout.comconnection context
input data , one of three find methods
linkedin method: linkedin profile url (required). optional: full_name, current_positions array (company_name, company_linkedin_url, title, location), meta_data object.
https://www.linkedin.com/in/<slug>, https://www.linkedin.com/in/<id>, sales navigator https://www.linkedin.com/sales/people/<id>,name,sSHS, https://www.linkedin.com/sales/lead/<id>,name,sSHShttps://www.linkedin.com/company/<slug|id>, https://www.linkedin.com/sales/company/<id>professional method: full_name and domain (both required). optional: domain_extra array (extra candidate domains), deep_verify boolean (default false), meta_data object.
author method: article_url (required). optional: meta_data object.
bulk-specific inputs
skip_duplicates (default) or scrape_and_update_duplicatesbuild request: choose endpoint /find/linkedin/single, /find/professional/single, or /find/author/single. shape the person object per your method (see inputs). add optional top-level fields if needed: tags, webhook_url, enable_work_email, enable_personal_email, enable_generic_email.
submit blocking call: post to https://api-waterfall.finalscout.com/find/<method>/single?timeout=120 (adjust timeout 30-600 seconds). set http client timeout to timeout+50 seconds (e.g., 130s for timeout=80). include Authorization: $FINALSCOUT_API_KEY header and Content-Type: application/json.
handle 200 response: extract contact object, credits_consumed, credits_available. contact may be null/absent (no email found, no credit charge). parse contact fields: email, email_status, email_type, email_score, email_is_catchall, title, company, location, linkedin, website, industry, company_domain, company_phone, etc. go to step 11 (interpret and present).
handle 408 response (timeout, task still running): body contains id and status=Running. either resubmit the same request (keep polling) or switch to submit-and-poll (step 8). max 3 resubmits before falling back to poll.
submit: post to https://api.finalscout.com/v1/find/professional/single (swap method path). same person and optional fields as waterfall. http timeout 30 seconds.
check status: if status=Complete, go to step 11. if status=Running or Queued, proceed to step 9.
poll loop: every 3 seconds, get https://api.finalscout.com/v1/find/single/status?id=<id>. include Authorization header.
poll exit: after 40 attempts (~2 min), give up. report the id to user so they can check later. otherwise when status=Complete, extract contact and credits. go to step 11.
parse input: read csv, json list, table, or spreadsheet. skip empty rows. convert to persons array, one object per row. required fields per method (linkedin_url for linkedin, full_name+domain for professional, article_url for author). include meta_data (e.g., crm_id) per contact if available.
submit bulk task: post to https://api.finalscout.com/v1/find/professional/bulk (swap method endpoint). body: {persons: [...], name: "Bulk find -
save task id: store the returned id for polling and result retrieval.
poll status: every 5 seconds, get https://api.finalscout.com/v1/find/bulk/status?id=<id>. log progress as finished / total.
poll exit: task status changes to Completed or Failed. if Failed, report the error. go to step 16.
dump results (after Completed): get https://api.finalscout.com/v1/find/bulk/dump?id=<id>&page_size=100. response: {cursor, task: {...}, items: [{contact, meta_data}]}.
paginate: if cursor is non-empty, repeat step 16 with &cursor=<cursor>. continue until cursor is null/empty. cursors expire after 1 day.
format table: for each item, extract contact.email, contact.email_status, contact.email_type, contact.email_score, contact.title, contact.company, contact.location. render as table: Name | Input | Email | Type | Status | Score | Title | Company | Location (omit missing columns).
flag risky emails: if email_status is Risky or Unknown, append caveat "unverified". if email_is_catchall=true, append "catchall domain (but verified by BounceBan)".
summary line: count success, report "X / Y emails found. Z credits consumed, W credits remaining."
request export: get https://api.finalscout.com/v1/find/bulk/export?id=<id>. response: {status: "Generating download link" | "Ready", download_url: "..."}.
poll export: if status=Generating, retry after 3 seconds. repeat until Ready. once Ready, warn user: url is public (anyone with it can download) and expires in 7 days. return download_url.
check balance: get https://api.finalscout.com/v1/account. returns owner_email, credits_available, rate_limit entries per endpoint.
validate before bulk: if credits_available < bulk person count, warn user and offer to submit anyway or cancel.
single vs bulk: if user has 1-5 contacts, use single (waterfall preferred). if 6+ or unknown count, use bulk.
waterfall vs poll: if http client supports 130+ second persistent connections and network is stable, use waterfall (one blocking call, fewer roundtrips). if behind a reverse proxy with connection timeout, or mobile/unreliable network, use submit-and-poll (shorter round-trip time).
waterfall timeout (408): if waterfall request times out after timeout seconds, check the response id. if user needs the answer soon (blocking ui), resubmit the same request (wait time accumulates). if user can wait asynchronously, save the id and use submit-and-poll / webhook for final result. max 3 resubmits before abandoning waterfall.
no email found: if contact.email is null/absent after single lookup or contact is missing from bulk results, zero credits were consumed for that contact. display "no email found" without error. do not fail the entire operation.
insufficient credits (403): if response body shows credits_required > credits_available, report the gap to user. offer to submit anyway (partial batch will process until credits exhausted) or suggest contacting dev@finalscout.com to purchase more.
rate limit (429): if rate-limited, wait 2 seconds and retry once. if 429 repeats, fetch /v1/account to check actual rate limits per endpoint and throttle request frequency (typical: 2 req/sec for bulk, 5 req/sec for professional single, 50 req/sec for linkedin single). if limits still exceeded, back off and retry after 10 seconds.
webhook vs poll: if webhook_url is provided and accepted (request succeeds), task will post to that url on completion (single_task.finished or bulk_task.finished event). user may rely on webhook instead of polling, but still offer polling as fallback if webhook delivery fails.
bulk duplicate handling: if user has the same contact in the persons array, set duplicate: "skip_duplicates" (default, recommended). if user wants to re-process and update contacts already in their account, set duplicate: "scrape_and_update_duplicates" (consumes credits again).
email type filters (linkedin bulk only): default is enable_work_email=true, others false. if user wants personal emails as fallback, set enable_personal_email=true (will return gmail, yahoo, etc. if work email not found). if user wants generic emails (support@, info@), set enable_generic_email=true (not recommended, low quality). surface this choice to user before submitting.
missing required fields: if full_name or domain is blank in professional method, or linkedin_url is missing in linkedin method, reject the request before submitting to api. surface the row number and missing field to user.
single waterfall / poll success
bulk dump results
bulk export (csv)
account info
error responses
user knows it worked when
single result: email address displayed with status (Valid, Risky, etc.), type (Work, Personal, Generic), and confidence score. credits_consumed and credits_available shown. if no email found, "no email found" message (zero credits charged).
bulk results: paginated table of contacts with email column populated (or "no email found" per row). summary line shows "X / Y emails found. Z credits consumed, W credits remaining." all rows visible or downloadable.
csv export: download link provided, user can save file locally. file is readable in excel/sheets with email column populated.
account check: credits_available matches user's expectations. rate limits per endpoint visible for diagnostics.
webhook (optional): if provided, http post received at webhook_url with event type (single_task.finished or bulk_task.finished) and full result payload. timestamps confirm delivery.
no surprises: credits only consumed when email found. invalid input rejected before api call. rate limits respected, no 429s in happy path. 408s transparently handled (user knows task is still running, has id for later checks).
credits. original by finalscout. adapted for implexa standards.