curl --request POST \
--url https://app.manypi.com/api/leads/research \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"criteria": {
"description": "E-commerce agencies building Shopify stores for DTC brands",
"locations": [
"Germany",
"Austria"
],
"industries": [
"Marketing agency",
"E-commerce"
],
"company_size": "5-50 employees",
"titles": [
"Founder",
"Head of Growth"
],
"exclusions": [
"SEO-only agencies"
],
"required_fields": [
"email"
],
"extra_columns": [
{
"name": "Ecommerce platform",
"description": "Shopify, WooCommerce, custom or unknown"
}
],
"count": 25
}
}
'import requests
url = "https://app.manypi.com/api/leads/research"
payload = { "criteria": {
"description": "E-commerce agencies building Shopify stores for DTC brands",
"locations": ["Germany", "Austria"],
"industries": ["Marketing agency", "E-commerce"],
"company_size": "5-50 employees",
"titles": ["Founder", "Head of Growth"],
"exclusions": ["SEO-only agencies"],
"required_fields": ["email"],
"extra_columns": [
{
"name": "Ecommerce platform",
"description": "Shopify, WooCommerce, custom or unknown"
}
],
"count": 25
} }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
criteria: {
description: 'E-commerce agencies building Shopify stores for DTC brands',
locations: ['Germany', 'Austria'],
industries: ['Marketing agency', 'E-commerce'],
company_size: '5-50 employees',
titles: ['Founder', 'Head of Growth'],
exclusions: ['SEO-only agencies'],
required_fields: ['email'],
extra_columns: [
{
name: 'Ecommerce platform',
description: 'Shopify, WooCommerce, custom or unknown'
}
],
count: 25
}
})
};
fetch('https://app.manypi.com/api/leads/research', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.manypi.com/api/leads/research",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'criteria' => [
'description' => 'E-commerce agencies building Shopify stores for DTC brands',
'locations' => [
'Germany',
'Austria'
],
'industries' => [
'Marketing agency',
'E-commerce'
],
'company_size' => '5-50 employees',
'titles' => [
'Founder',
'Head of Growth'
],
'exclusions' => [
'SEO-only agencies'
],
'required_fields' => [
'email'
],
'extra_columns' => [
[
'name' => 'Ecommerce platform',
'description' => 'Shopify, WooCommerce, custom or unknown'
]
],
'count' => 25
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.manypi.com/api/leads/research"
payload := strings.NewReader("{\n \"criteria\": {\n \"description\": \"E-commerce agencies building Shopify stores for DTC brands\",\n \"locations\": [\n \"Germany\",\n \"Austria\"\n ],\n \"industries\": [\n \"Marketing agency\",\n \"E-commerce\"\n ],\n \"company_size\": \"5-50 employees\",\n \"titles\": [\n \"Founder\",\n \"Head of Growth\"\n ],\n \"exclusions\": [\n \"SEO-only agencies\"\n ],\n \"required_fields\": [\n \"email\"\n ],\n \"extra_columns\": [\n {\n \"name\": \"Ecommerce platform\",\n \"description\": \"Shopify, WooCommerce, custom or unknown\"\n }\n ],\n \"count\": 25\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.manypi.com/api/leads/research")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"criteria\": {\n \"description\": \"E-commerce agencies building Shopify stores for DTC brands\",\n \"locations\": [\n \"Germany\",\n \"Austria\"\n ],\n \"industries\": [\n \"Marketing agency\",\n \"E-commerce\"\n ],\n \"company_size\": \"5-50 employees\",\n \"titles\": [\n \"Founder\",\n \"Head of Growth\"\n ],\n \"exclusions\": [\n \"SEO-only agencies\"\n ],\n \"required_fields\": [\n \"email\"\n ],\n \"extra_columns\": [\n {\n \"name\": \"Ecommerce platform\",\n \"description\": \"Shopify, WooCommerce, custom or unknown\"\n }\n ],\n \"count\": 25\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.manypi.com/api/leads/research")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"criteria\": {\n \"description\": \"E-commerce agencies building Shopify stores for DTC brands\",\n \"locations\": [\n \"Germany\",\n \"Austria\"\n ],\n \"industries\": [\n \"Marketing agency\",\n \"E-commerce\"\n ],\n \"company_size\": \"5-50 employees\",\n \"titles\": [\n \"Founder\",\n \"Head of Growth\"\n ],\n \"exclusions\": [\n \"SEO-only agencies\"\n ],\n \"required_fields\": [\n \"email\"\n ],\n \"extra_columns\": [\n {\n \"name\": \"Ecommerce platform\",\n \"description\": \"Shopify, WooCommerce, custom or unknown\"\n }\n ],\n \"count\": 25\n }\n}"
response = http.request(request)
puts response.read_body{
"run_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"conversation_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"status": "<string>",
"count": 123,
"profile": {},
"clamped": {
"requested": 123,
"allowed": 123,
"limit": 123
},
"redirected": true,
"known": {},
"description": "<string>"
}{
"error": "<string>",
"code": "<string>",
"details": "<unknown>"
}{
"error": "<string>",
"code": "<string>",
"details": "<unknown>"
}{
"error": "<string>",
"code": "<string>",
"details": "<unknown>"
}Start a lead search
Launch the lead-generation agent against structured criteria, a saved search, or a set of seed leads (“find more like these”).
The run is asynchronous: poll GET /api/agents/runs with the returned conversation_id. The run may pause with a clarifying question — answer it with POST /api/agents/runs/{id}/reply and the same run continues.
count is clamped to the workspace’s remaining lead capacity; when that happens the response carries a clamped block. A full workspace returns 403 with code: "lead_limit".
With a saved search. criteria sent alongside profile_id replaces the saved criteria as a whole — fields you leave out fall back to their defaults, not to the saved values. To change one field, read the saved search from GET /api/leads/research-profiles, merge your change, and send the full criteria. Because save defaults to true, the criteria you send are also written back to that saved search; send save: false to run without changing it.
Two responses that start no lead search. Both apply only to a typed request, with no profile_id and no seed_lead_ids:
- When
criteriacarries only a free-textdescriptionthat does not read as a request for leads, ManyPI starts a plain agent run instead and the response includesredirected: true. Sendcheck_intent: falseto skip that check. - When the description names leads the workspace already has, the response is
{ "known": {...}, "description": "..." }with norun_id, and no run starts. Sendforce_search: trueto search anyway.
Requires the agents permission.
curl --request POST \
--url https://app.manypi.com/api/leads/research \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"criteria": {
"description": "E-commerce agencies building Shopify stores for DTC brands",
"locations": [
"Germany",
"Austria"
],
"industries": [
"Marketing agency",
"E-commerce"
],
"company_size": "5-50 employees",
"titles": [
"Founder",
"Head of Growth"
],
"exclusions": [
"SEO-only agencies"
],
"required_fields": [
"email"
],
"extra_columns": [
{
"name": "Ecommerce platform",
"description": "Shopify, WooCommerce, custom or unknown"
}
],
"count": 25
}
}
'import requests
url = "https://app.manypi.com/api/leads/research"
payload = { "criteria": {
"description": "E-commerce agencies building Shopify stores for DTC brands",
"locations": ["Germany", "Austria"],
"industries": ["Marketing agency", "E-commerce"],
"company_size": "5-50 employees",
"titles": ["Founder", "Head of Growth"],
"exclusions": ["SEO-only agencies"],
"required_fields": ["email"],
"extra_columns": [
{
"name": "Ecommerce platform",
"description": "Shopify, WooCommerce, custom or unknown"
}
],
"count": 25
} }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
criteria: {
description: 'E-commerce agencies building Shopify stores for DTC brands',
locations: ['Germany', 'Austria'],
industries: ['Marketing agency', 'E-commerce'],
company_size: '5-50 employees',
titles: ['Founder', 'Head of Growth'],
exclusions: ['SEO-only agencies'],
required_fields: ['email'],
extra_columns: [
{
name: 'Ecommerce platform',
description: 'Shopify, WooCommerce, custom or unknown'
}
],
count: 25
}
})
};
fetch('https://app.manypi.com/api/leads/research', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.manypi.com/api/leads/research",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'criteria' => [
'description' => 'E-commerce agencies building Shopify stores for DTC brands',
'locations' => [
'Germany',
'Austria'
],
'industries' => [
'Marketing agency',
'E-commerce'
],
'company_size' => '5-50 employees',
'titles' => [
'Founder',
'Head of Growth'
],
'exclusions' => [
'SEO-only agencies'
],
'required_fields' => [
'email'
],
'extra_columns' => [
[
'name' => 'Ecommerce platform',
'description' => 'Shopify, WooCommerce, custom or unknown'
]
],
'count' => 25
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.manypi.com/api/leads/research"
payload := strings.NewReader("{\n \"criteria\": {\n \"description\": \"E-commerce agencies building Shopify stores for DTC brands\",\n \"locations\": [\n \"Germany\",\n \"Austria\"\n ],\n \"industries\": [\n \"Marketing agency\",\n \"E-commerce\"\n ],\n \"company_size\": \"5-50 employees\",\n \"titles\": [\n \"Founder\",\n \"Head of Growth\"\n ],\n \"exclusions\": [\n \"SEO-only agencies\"\n ],\n \"required_fields\": [\n \"email\"\n ],\n \"extra_columns\": [\n {\n \"name\": \"Ecommerce platform\",\n \"description\": \"Shopify, WooCommerce, custom or unknown\"\n }\n ],\n \"count\": 25\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.manypi.com/api/leads/research")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"criteria\": {\n \"description\": \"E-commerce agencies building Shopify stores for DTC brands\",\n \"locations\": [\n \"Germany\",\n \"Austria\"\n ],\n \"industries\": [\n \"Marketing agency\",\n \"E-commerce\"\n ],\n \"company_size\": \"5-50 employees\",\n \"titles\": [\n \"Founder\",\n \"Head of Growth\"\n ],\n \"exclusions\": [\n \"SEO-only agencies\"\n ],\n \"required_fields\": [\n \"email\"\n ],\n \"extra_columns\": [\n {\n \"name\": \"Ecommerce platform\",\n \"description\": \"Shopify, WooCommerce, custom or unknown\"\n }\n ],\n \"count\": 25\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.manypi.com/api/leads/research")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"criteria\": {\n \"description\": \"E-commerce agencies building Shopify stores for DTC brands\",\n \"locations\": [\n \"Germany\",\n \"Austria\"\n ],\n \"industries\": [\n \"Marketing agency\",\n \"E-commerce\"\n ],\n \"company_size\": \"5-50 employees\",\n \"titles\": [\n \"Founder\",\n \"Head of Growth\"\n ],\n \"exclusions\": [\n \"SEO-only agencies\"\n ],\n \"required_fields\": [\n \"email\"\n ],\n \"extra_columns\": [\n {\n \"name\": \"Ecommerce platform\",\n \"description\": \"Shopify, WooCommerce, custom or unknown\"\n }\n ],\n \"count\": 25\n }\n}"
response = http.request(request)
puts response.read_body{
"run_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"conversation_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"status": "<string>",
"count": 123,
"profile": {},
"clamped": {
"requested": 123,
"allowed": 123,
"limit": 123
},
"redirected": true,
"known": {},
"description": "<string>"
}{
"error": "<string>",
"code": "<string>",
"details": "<unknown>"
}{
"error": "<string>",
"code": "<string>",
"details": "<unknown>"
}{
"error": "<string>",
"code": "<string>",
"details": "<unknown>"
}Authorizations
A ManyPI API key (mpi_...) from the profile menu → API Access in the dashboard, or an OAuth 2.1 access token obtained through the MCP consent flow.
Body
The structured search. The description does most of the work; the rest is what the agent enforces.
Show child attributes
Show child attributes
Re-run a saved search. Its criteria are used when criteria is omitted; when criteria is sent, it replaces them entirely.
Seed the search from existing leads ("find similar").
50Persist the criteria as a reusable saved search. With profile_id, writes them back to that saved search.
120icp, similar Check that a free-text-only request reads as a lead search, and start a plain agent run when it does not. Send false when your integration only ever sends lead searches.
Search even when the description names leads the workspace already has.
Response
A run was created — or, when known is present instead of run_id, the workspace already holds the leads the description names and no run was started.
Absent when the response carries known.
Leads the run will actually try to save, after clamping.
Present only when count was reduced to fit the plan.
Show child attributes
Show child attributes
Present and true when the request did not read as a lead search and a plain agent run was started instead. See check_intent.
Present instead of run_id when the description names leads the workspace already has. Holds those leads; no run was started. See force_search.
The description that was matched, returned alongside known.
