How to get new B2B prospects into an n8n or Make workflow
Is there a Prosp node or app for n8n or Make?
Prosp gives your workflow a list of companies with contacts, ready to feed the next step of an outreach sequence. There is no ready-made Prosp node or app in n8n or Make. The workflow calls Prosp's REST API with the plain HTTP request step. It can also read the results as a CSV file.
Prosp finds and checks companies. It does not send e-mails or messages to them. Your workflow sends them.
The steps, in order: get credentials, get a token, start a search, poll until it ends, read the companies, hand each one to the sequence.
How does the workflow get credentials and a token?
Credentials are a client id and a client secret. The Telegram bot shows them with /credentials. An account and its credentials are created when the first credit pack is bought.
In the workflow, send the client id and secret to get an access token (the OAuth client-credentials flow). Then send that token as a Bearer token on every later request. Do this in the first HTTP request step and pass the token on to the steps after it.
How does the workflow start a search?
Send POST /api/v1/discoveries. The body holds either a saved ICP id or inline criteria. Prosp returns a job id at once, and the job runs asynchronously. Keep the job id: every later step uses it.
The request below describes the buyers in one sentence: independent coffee roasters in the Netherlands that supply cafes and restaurants. The response below it is what Prosp answered.
One discovery job costs 100 credits. Polling status and reading results cost no credits.
// accessToken: exchange your Client ID and Secret (/credentials in the bot) for a token with the OAuth client-credentials grant; the token URL is in /docs/
// idempotencyKey: make ONE per run and reuse it when a step is retried, so a retry never starts a second paid job
const res = await fetch("https://api.prosp.cc/api/v1/discoveries", {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
"X-Idempotency-Key": idempotencyKey,
},
body: JSON.stringify({
"query": "Independent coffee roasters in the Netherlands that supply cafes and restaurants",
"locations": [
"Netherlands"
],
"company_sizes": [
"small",
"medium"
],
"maxResults": 5
}),
});
if (!res.ok) throw new Error(`start failed: HTTP ${res.status}`); // 402 no credits; 409 a warning or the same job already running (nothing charged); 429 too many requests or jobs
const { jobId } = await res.json();{
"jobId": "958b5ea1e58f7050d05bb105b87e62a5",
"status": "queued",
"createdAt": "2026-11-04T11:45:08.272639372Z",
"poll_after_seconds": 20,
"estimated_duration_seconds": 2057,
"estimated_duration_p50_seconds": 53,
"estimated_duration_p90_seconds": 1409,
"estimated_duration_p95_seconds": 2057,
"estimated_yield": 5
}How does the workflow wait until the job has ended?
Prosp does not call your workflow back when a job ends, so the workflow polls. Call GET /mcp/jobs/{jobId}/status. The answer holds the state, the prospects so far, the target, and how long to wait before the next poll. Wait that long, then poll again. In n8n use a Wait node and a loop. In Make use a sleep step and a repeater.
While the job runs, the answer looks like the first response below. When it has ended, it looks like the second. Read the companies only once the job has ended.
To stop a job you no longer want, call POST /mcp/jobs/{jobId}/cancel. Credits are refunded only when nothing had been delivered at cancel time.
// accessToken and jobId: from the calls above
const terminal = ["completed", "failed", "exhausted", "cancelled"];
const deadline = Date.now() + 45 * 60 * 1000;
let job, cursor = null, wait = 20; // seconds; then the poll_after_seconds of the last answer
do {
await new Promise((r) => setTimeout(r, wait * 1000));
if (Date.now() > deadline) {
await fetch(`https://api.prosp.cc/mcp/jobs/${jobId}/cancel`, {
method: "POST",
headers: { Authorization: `Bearer ${accessToken}` },
}); // frees the concurrency slot; prospects already delivered are kept
throw new Error("the job did not finish in 45 minutes and was cancelled");
}
const url = `https://api.prosp.cc/mcp/jobs/${jobId}/status` + (cursor ? `?cursor=${encodeURIComponent(cursor)}` : "");
const r = await fetch(url, {
headers: { Authorization: `Bearer ${accessToken}` },
});
if (!r.ok) throw new Error(`status check failed: HTTP ${r.status}`);
job = await r.json();
cursor = job.next_cursor ?? cursor; // only the prospects added since the last poll
wait = job.poll_after_seconds ?? 20;
} while (!terminal.includes(job.status));{
"evaluated_count": 0,
"total_prospects": 0,
"jobId": "958b5ea1e58f7050d05bb105b87e62a5",
"next_cursor": "MA",
"poll_after_seconds": 20,
"prospectsFound": 0,
"target": 5,
"estimated_duration_p50_seconds": 53,
"candidates_screened": 0,
"has_more": true,
"status": "processing",
"estimated_duration_p95_seconds": 2057,
"stage": "searching",
"estimated_duration_seconds": 2057,
"estimated_duration_p90_seconds": 1409,
"estimated_yield": 5
}{
"stage": "complete",
"candidates_screened": 102,
"estimated_duration_p50_seconds": 53,
"total_prospects": 5,
"estimated_duration_p90_seconds": 1409,
"estimated_duration_p95_seconds": 2057,
"estimated_duration_seconds": 2057,
"estimated_yield": 5,
"evaluated_count": 36,
"found": [
"koffielust.nl"
],
"status": "completed",
"jobId": "958b5ea1e58f7050d05bb105b87e62a5",
"target": 5,
"poll_after_seconds": 20,
"has_more": false,
"prospectsFound": 5,
"terminal_reason": "target_reached",
"already_owned_count": 0
}How does the workflow read the companies and pass each one on?
Once the job has ended, GET /api/v1/discoveries/{jobId}/prospects returns the companies as JSON. GET /mcp/jobs/{jobId}/prospects.csv returns the same companies as a CSV file. Use the JSON for an iterator step that runs once per company, or the CSV for a spreadsheet or file step.
Each company carries, where it publishes them, its phones, e-mail addresses, Instagram, LinkedIn and other social profiles, and a WhatsApp link. Each named contact carries name, position, e-mail and phone when published. A contact shows what Prosp found for that person. The web page it was found on is not shown. An e-mail Prosp inferred rather than found is marked as inferred.
From here the next step of your sequence takes over: it writes and sends the message, not Prosp.
// accessToken and jobId: from the calls above
const r = await fetch(`https://api.prosp.cc/api/v1/discoveries/${jobId}/prospects`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
const { prospects } = await r.json();{
"prospects": [
{
"description": "Koffiebranderij Koffielust is an artisanal coffee roastery that sells freshly roasted coffee beans to consumers and businesses in the Netherlands. The company focuses on fair-trade, high-quality Arabica coffee from its Veenhuizen base.",
"employee_count": 15,
"company_size": "small",
"employee_count_source": "unverified_estimate",
"employee_count_as_of": "2026-11-04T11:54:37.921262Z",
"size_certainty": "estimated",
"headquarters_location": "Veenhuizen, Netherlands",
"country": "Netherlands",
"country_code": "NL",
"city": "Veenhuizen",
"state": "Drenthe",
"postal_code": "9341",
"street": "Gracht 4a",
"linkedin_url": "https://www.linkedin.com/company/koffielust",
"instagram_url": "https://www.instagram.com/koffielust",
"facebook_url": "https://www.facebook.com/koffielust",
"twitter_url": "https://twitter.com/koffielust",
"business_model": "manufacturer",
"name": "Koffiebranderij Koffielust",
"hq_address": "Gracht 4a, Veenhuizen, Drenthe 9341, Netherlands",
"discovered_at": "2026-11-04T11:54:37.921262Z",
"detected_language": "nl",
"has_wikipedia_article": false,
"detected_industry": "Agriculture & Food Production",
"sub_country_region": "NL-DR",
"origin_country": "NL",
"estimated_annual_revenue_usd": 8800000,
"domain": "koffielust.nl",
"estimated_revenue_currency": "USD",
"industry": "Agriculture & Food Production"
},
{
"description": "Roast Factory is a specialty coffee roastery based in The Hague, Netherlands. They sell sustainably sourced, freshly roasted coffee beans to individual consumers, restaurants, coffee bars, and offices.",
"employee_count": 20,
"company_size": "small",
"employee_count_source": "unverified_estimate",
"employee_count_as_of": "2026-11-04T11:54:00.027464Z",
"size_certainty": "estimated",
"headquarters_location": "'s-Gravenhage, Netherlands",
"country": "Netherlands",
"country_code": "NL",
"city": "'s-Gravenhage",
"state": "Zuid-Holland",
"postal_code": "2512 ER",
"instagram_url": "https://www.instagram.com/roast_factory/?hl=en",
"business_model": "manufacturer",
"name": "Roast Factory",
"hq_address": "'s-Gravenhage, Zuid-Holland 2512 ER, Netherlands",
"discovered_at": "2026-11-04T11:54:00.027464Z",
"detected_language": "en",
"has_wikipedia_article": false,
"domain_age_days": 3936,
"detected_industry": "Agriculture & Food Production",
"sub_country_region": "NL-ZH",
"origin_country": "NL",
"estimated_annual_revenue_usd": 12000000,
"domain": "roastfactory.nl",
"estimated_revenue_currency": "USD",
"industry": "Agriculture & Food Production"
}
],
"evaluated_count": 36,
"already_owned_count": 0,
"total": 5,
"candidates_screened": 102
}[
"name",
"email",
"position",
"phone",
"department",
"organization",
"linkedin_url",
"instagram_url",
"facebook_url",
"twitter_url",
"youtube_url",
"telegram_url",
"viber_url",
"whatsapp_url",
"contact_address",
"contact_city",
"contact_country",
"contact_country_code",
"contact_region",
"registry_source",
"email_source",
"is_officer"
]What do I do first?
Get your API credentials with /credentials in the Telegram bot, then follow the quick-start with the button below. Your workflow can then start its first search and read the companies.