Integration Guide
Search the ScraperCity Lead Database, then upsert the results into a Manyreach list and campaign in batches of 100. Ask an AI agent that has both MCP servers, or write two HTTP requests yourself.
Route 1
Claude Code, Cursor or another MCP client runs the search and the import from a plain-English request. Best for one-off lists and testing an ICP.
Route 2
One GET to ScraperCity, one POST to Manyreach per 100 leads. Best for scheduled imports from cron, n8n, Make or your own backend.
Manyreach hosts its MCP server at https://mcp.manyreach.com/mcp. ScraperCity's MCP server runs on your machine through npx. With both connected, the agent can query the Lead Database and call Manyreach's bulk import in the same conversation.
claude mcp add scrapercity --transport stdio -e SCRAPERCITY_API_KEY=your_api_key_here -- npx -y --package scrapercity scrapercity-mcp
claude mcp add --transport http manyreach https://mcp.manyreach.com/mcpThen run /mcp, pick manyreach, choose Authenticate, and paste your Manyreach API key on the page that opens.
{
"mcpServers": {
"scrapercity": {
"command": "npx",
"args": ["-y", "--package", "scrapercity", "scrapercity-mcp"],
"env": {
"SCRAPERCITY_API_KEY": "your_api_key_here"
}
},
"manyreach": {
"url": "https://mcp.manyreach.com/mcp"
}
}
}After saving, open Cursor Settings > Tools & Integrations, select manyreach and sign in the same way. Steps for other clients are at mcp.manyreach.com/install.
The agent's Manyreach calls share the same 60-per-minute limit as any script using that key.
| Item | Where to get it | Sent as |
|---|---|---|
| ScraperCity API key | Dashboard > API docs (app.scrapercity.com/dashboard/api-docs). The Lead Database needs the $149/month plan. | Authorization: Bearer <key> |
| Manyreach API key | Manyreach > Settings > API. | X-API-Key: <key> |
| listId | GET /api/v2/lists, or create one with POST /api/v2/lists and a JSON body like {"title": "SaaS marketing heads"}. | query parameter |
| campaignId | GET /api/v2/campaigns. Leave it out to load prospects into the list without enrolling them. | query parameter |
curl -s "https://app.scrapercity.com/api/v1/database/leads?title=Head%20of%20Marketing&industry=computer%20software&minEmployees=50&maxEmployees=500&country=United%20States&hasEmail=true&limit=100&after=0" \
-H "Authorization: Bearer $SCRAPERCITY_API_KEY"Leads come back in data. For the next 100, pass pagination.next_after as after; when it comes back null, the search is done. On a schedule, add excludeDelivered=true so each run returns only leads your account has never pulled. hasEmail=true drops leads with no email; if you import Apollo scraper rows instead, check them with ScraperCity's Email Validator before they reach a campaign.
curl -X POST "https://api.manyreach.com/api/v2/prospects/bulk?listId=YOUR_LIST_ID&campaignId=YOUR_CAMPAIGN_ID¬InOtherCampaign=true" \
-H "X-API-Key: $MANYREACH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prospects": [
{
"email": "maria.lopez@cloudledger.io",
"firstName": "Maria",
"lastName": "Lopez",
"company": "CloudLedger",
"domain": "cloudledger.io",
"jobPosition": "Head of Marketing",
"industry": "computer software",
"companySize": "201-500",
"city": "Denver",
"state": "Colorado",
"country": "United States",
"custom1": "head"
}
]
}'| Query parameter | Effect |
|---|---|
listId | The list the prospects are saved to. Send it on every call. |
campaignId | Also enrolls each prospect in that campaign's first sequence. Optional. |
addOnlyIfNew=true | Skips anyone whose email is already in your Manyreach CRM. |
notInOtherCampaign=true | Skips anyone already enrolled in a different campaign. |
A successful call returns 201. Log these fields from the response:
prospectsInserted: emails that were new to your Manyreach accountprospectsUpdated: emails that already existed and had fields overwrittenduplicatesInBatch: repeats of the same email inside the 100 you senttotalProcessed: how many rows Manyreach handled in this callprospectIdByEmail: a map from each email to its Manyreach prospect ID| ScraperCity field | Manyreach field | Merge tag in your emails |
|---|---|---|
email | email | {{EMAIL}} |
first_name | firstName | {{FIRST_NAME}} |
last_name | lastName | {{LAST_NAME}} |
company_name | company | {{COMPANY}} |
title | jobPosition | {{JOB_POSITION}} |
company_industry | industry | {{INDUSTRY}} |
company_size | companySize | {{COMPANY_SIZE}} |
company_domain | domain | {{DOMAIN}} |
company_website (or company_domain) | website | - |
mobile_number | phone | {{PHONE}} |
city, state, country | city, state, country | {{CITY}}, {{STATE}}, {{COUNTRY}} |
linkedin_url | personalSocial | - |
company_linkedin | companySocial | - |
seniority | custom1 | {{CUSTOM_1}} |
department | custom2 | {{CUSTOM_2}} |
Manyreach has 20 custom slots, custom1 to custom20, so keywords, revenue or headline can come along too. Their tags always carry an underscore: {{CUSTOM_1}} works and {{CUSTOM1}} is rejected. To clear a field on an existing prospect send an empty string; to keep it, leave the field out.
Python, using the requests library. It walks a search with the cursor, maps each page with the table above, sends it as one bulk call, waits about a second between calls, and backs off on 429.
import os
import time
import requests
SCRAPERCITY_KEY = os.environ["SCRAPERCITY_API_KEY"]
MANYREACH_KEY = os.environ["MANYREACH_API_KEY"]
LIST_ID = os.environ["MANYREACH_LIST_ID"]
CAMPAIGN_ID = os.environ["MANYREACH_CAMPAIGN_ID"]
MAX_LEADS = 1000
SEARCH = {
"title": "Head of Marketing",
"industry": "computer software",
"minEmployees": 50,
"maxEmployees": 500,
"country": "United States",
"hasEmail": "true",
"excludeDelivered": "true", # only leads this account has not pulled before
"limit": 100,
}
def to_prospect(lead):
p = {
"email": lead.get("email"),
"firstName": lead.get("first_name"),
"lastName": lead.get("last_name"),
"company": lead.get("company_name"),
"website": lead.get("company_website") or lead.get("company_domain"),
"domain": lead.get("company_domain"),
"jobPosition": lead.get("title"),
"industry": lead.get("company_industry"),
"companySize": lead.get("company_size"),
"personalSocial": lead.get("linkedin_url"),
"companySocial": lead.get("company_linkedin"),
"phone": lead.get("mobile_number"),
"city": lead.get("city"),
"state": lead.get("state"),
"country": lead.get("country"),
"custom1": lead.get("seniority"),
"custom2": lead.get("department"),
}
# Leave out empty fields so Manyreach keeps any value it already has
return {k: v for k, v in p.items() if v not in (None, "")}
def push_batch(prospects):
for attempt in range(5):
resp = requests.post(
"https://api.manyreach.com/api/v2/prospects/bulk",
headers={"X-API-Key": MANYREACH_KEY, "Content-Type": "application/json"},
params={"listId": LIST_ID, "campaignId": CAMPAIGN_ID, "addOnlyIfNew": "true"},
json={"prospects": prospects},
timeout=240, # big batches can take a few minutes
)
if resp.status_code == 429:
time.sleep(5 * 2 ** attempt) # 60 requests per minute per key
continue
resp.raise_for_status()
return resp.json()
raise RuntimeError("Still rate limited after 5 tries")
after, sent = "0", 0
while sent < MAX_LEADS:
sc = requests.get(
"https://app.scrapercity.com/api/v1/database/leads",
headers={"Authorization": f"Bearer {SCRAPERCITY_KEY}"},
params={**SEARCH, "after": after},
timeout=90,
)
sc.raise_for_status()
body = sc.json()
leads = [l for l in body.get("data", []) if l.get("email")]
if leads:
result = push_batch([to_prospect(l) for l in leads])
sent += len(leads)
print(f"Sent {len(leads)}: {result.get('prospectsInserted', 0)} new, "
f"{result.get('prospectsUpdated', 0)} updated")
after = body.get("pagination", {}).get("next_after")
if not after:
break
time.sleep(1.1) # about one bulk call per second
print(f"Done: {sent} leads sent to Manyreach")Set the four environment variables and run it with python3 import_to_manyreach.py. For a daily import, put the same command in cron, or rebuild it in n8n or Make as an HTTP GET, an HTTP POST and a one-second wait.
ScraperCity hands back 100 leads per request and up to 100,000 new leads per day. Manyreach takes 100 prospects per bulk call and 60 calls per minute on one key, so its side tops out at 6,000 prospects a minute.
A 10,000-lead campaign is 100 searches and 100 bulk calls. With the script's one-second spacing, the calls themselves fit in about two minutes; the rest of the run time is Manyreach processing each batch, which can take up to a few minutes for a full 100.
excludeDelivered=true skips every lead your account already pulled, from the API or the dashboard. Leads you already have do not count against the daily limit again.addOnlyIfNew=true skips emails already in your Manyreach CRM, such as people from earlier campaigns.notInOtherCampaign=true keeps a prospect from sitting in two sequences at once.| Status | From | Fix |
|---|---|---|
| 401 | Manyreach | The key is wrong, expired or revoked. Copy a new one from Settings > API. |
| 406 | Manyreach | The X-API-Key header is missing. A key in the query string is ignored. |
| 415 | Manyreach | Add Content-Type: application/json. |
| 422 | Manyreach | A field failed validation and the body says which. Usually a missing email or a value over its length cap (companySize takes 32 characters, state 128). |
| 404 | Manyreach | That listId or campaignId is not in your account. Re-read the IDs from /api/v2/lists and /api/v2/campaigns. |
| 429 | Manyreach | More than 60 calls in 60 seconds on this key, counting every tool that uses it. Wait and retry with a longer pause each time. |
| 403 | ScraperCity | Your plan does not include the Lead Database. It needs the $149/month plan or above. |
| 200, empty data | ScraperCity | Nothing matched, or excludeDelivered=true skipped leads you already have. Widen the title or location, or run once without excludeDelivered to see the full count in pagination.total. |
Blank merge tags in a sent email usually mean the tag is missing its underscore ({{CUSTOM1}} instead of {{CUSTOM_1}}) or that lead had no value for the field, since the script leaves empty fields out.