ScraperCity logo

Integration Guide

ScraperCity + Manyreach

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: Run It From an AI Agent

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 Code

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/mcp

Then run /mcp, pick manyreach, choose Authenticate, and paste your Manyreach API key on the page that opens.

Cursor (~/.cursor/mcp.json)

{
  "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.

Requests to try

  1. “Find 200 heads of marketing at US computer software companies with 50 to 500 employees in the ScraperCity Lead Database. Create a Manyreach list called SaaS marketing heads and add them to it. Don't enroll them in a campaign yet.”
  2. “Enroll everyone in that list in my paused Manyreach campaign Q4 SaaS, skipping anyone who is already in another campaign. Put their seniority in custom1.”
  3. “Show me the stats for Q4 SaaS and list the prospects who replied.”

The agent's Manyreach calls share the same 60-per-minute limit as any script using that key.

Route 2: Two HTTP Requests

Keys and IDs

ItemWhere to get itSent as
ScraperCity API keyDashboard > API docs (app.scrapercity.com/dashboard/api-docs). The Lead Database needs the $149/month plan.Authorization: Bearer <key>
Manyreach API keyManyreach > Settings > API.X-API-Key: <key>
listIdGET /api/v2/lists, or create one with POST /api/v2/lists and a JSON body like {"title": "SaaS marketing heads"}.query parameter
campaignIdGET /api/v2/campaigns. Leave it out to load prospects into the list without enrolling them.query parameter

Request 1: search the Lead Database

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.

Request 2: upsert into Manyreach

curl -X POST "https://api.manyreach.com/api/v2/prospects/bulk?listId=YOUR_LIST_ID&campaignId=YOUR_CAMPAIGN_ID&notInOtherCampaign=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 parameterEffect
listIdThe list the prospects are saved to. Send it on every call.
campaignIdAlso enrolls each prospect in that campaign's first sequence. Optional.
addOnlyIfNew=trueSkips anyone whose email is already in your Manyreach CRM.
notInOtherCampaign=trueSkips 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 account
  • prospectsUpdated: emails that already existed and had fields overwritten
  • duplicatesInBatch: repeats of the same email inside the 100 you sent
  • totalProcessed: how many rows Manyreach handled in this call
  • prospectIdByEmail: a map from each email to its Manyreach prospect ID

Field and Merge Tag Map

ScraperCity fieldManyreach fieldMerge tag in your emails
emailemail{{EMAIL}}
first_namefirstName{{FIRST_NAME}}
last_namelastName{{LAST_NAME}}
company_namecompany{{COMPANY}}
titlejobPosition{{JOB_POSITION}}
company_industryindustry{{INDUSTRY}}
company_sizecompanySize{{COMPANY_SIZE}}
company_domaindomain{{DOMAIN}}
company_website (or company_domain)website-
mobile_numberphone{{PHONE}}
city, state, countrycity, state, country{{CITY}}, {{STATE}}, {{COUNTRY}}
linkedin_urlpersonalSocial-
company_linkedincompanySocial-
senioritycustom1{{CUSTOM_1}}
departmentcustom2{{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.

A Complete Import Script

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.

How Long a Big Import Takes

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.

Keeping People From Being Emailed Twice

  1. On the ScraperCity side, 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.
  2. On the Manyreach side, addOnlyIfNew=true skips emails already in your Manyreach CRM, such as people from earlier campaigns.
  3. Across campaigns, notInOtherCampaign=true keeps a prospect from sitting in two sequences at once.
  4. Before launch, enroll into a paused campaign. A running campaign starts sending to new prospects as soon as they are enrolled, so check the list and a preview of the merge tags first.

Error Codes and Fixes

StatusFromFix
401ManyreachThe key is wrong, expired or revoked. Copy a new one from Settings > API.
406ManyreachThe X-API-Key header is missing. A key in the query string is ignored.
415ManyreachAdd Content-Type: application/json.
422ManyreachA 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).
404ManyreachThat listId or campaignId is not in your account. Re-read the IDs from /api/v2/lists and /api/v2/campaigns.
429ManyreachMore than 60 calls in 60 seconds on this key, counting every tool that uses it. Wait and retry with a longer pause each time.
403ScraperCityYour plan does not include the Lead Database. It needs the $149/month plan or above.
200, empty dataScraperCityNothing 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.

Questions About ScraperCity and Manyreach

Pull your first Manyreach list from ScraperCity: