curl --request POST \
--url https://app.manypi.com/api/outreach/enroll \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"campaign_id": "…",
"lead_ids": [
"8f1c0e6a-1c3e-4f2a-9a4b-0f7a1e2b3c4d"
]
}
'import requests
url = "https://app.manypi.com/api/outreach/enroll"
payload = {
"campaign_id": "…",
"lead_ids": ["8f1c0e6a-1c3e-4f2a-9a4b-0f7a1e2b3c4d"]
}
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({campaign_id: '…', lead_ids: ['8f1c0e6a-1c3e-4f2a-9a4b-0f7a1e2b3c4d']})
};
fetch('https://app.manypi.com/api/outreach/enroll', 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/outreach/enroll",
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([
'campaign_id' => '…',
'lead_ids' => [
'8f1c0e6a-1c3e-4f2a-9a4b-0f7a1e2b3c4d'
]
]),
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/outreach/enroll"
payload := strings.NewReader("{\n \"campaign_id\": \"…\",\n \"lead_ids\": [\n \"8f1c0e6a-1c3e-4f2a-9a4b-0f7a1e2b3c4d\"\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/outreach/enroll")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"campaign_id\": \"…\",\n \"lead_ids\": [\n \"8f1c0e6a-1c3e-4f2a-9a4b-0f7a1e2b3c4d\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.manypi.com/api/outreach/enroll")
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 \"campaign_id\": \"…\",\n \"lead_ids\": [\n \"8f1c0e6a-1c3e-4f2a-9a4b-0f7a1e2b3c4d\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"enrolled": 123,
"skipped_no_email": 123,
"skipped_suppressed": 123,
"skipped_invalid": 123,
"already_enrolled": 123,
"queued_for_validation": 123
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "<string>"
}Enroll leads into a campaign
Queue the campaign’s sequence for real sending, respecting the inbox’s warmup and daily caps. Up to 1,000 leads per call.
Skipped rather than failed: leads with no email, leads already enrolled, suppressed or unsubscribed addresses, and addresses known to be undeliverable. When the brand has validate on enroll turned on (the default), unchecked addresses are validated first and undeliverable ones dropped before anything is sent.
Sending cannot be undone. Requires a paid plan and the outreach:send permission.
curl --request POST \
--url https://app.manypi.com/api/outreach/enroll \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"campaign_id": "…",
"lead_ids": [
"8f1c0e6a-1c3e-4f2a-9a4b-0f7a1e2b3c4d"
]
}
'import requests
url = "https://app.manypi.com/api/outreach/enroll"
payload = {
"campaign_id": "…",
"lead_ids": ["8f1c0e6a-1c3e-4f2a-9a4b-0f7a1e2b3c4d"]
}
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({campaign_id: '…', lead_ids: ['8f1c0e6a-1c3e-4f2a-9a4b-0f7a1e2b3c4d']})
};
fetch('https://app.manypi.com/api/outreach/enroll', 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/outreach/enroll",
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([
'campaign_id' => '…',
'lead_ids' => [
'8f1c0e6a-1c3e-4f2a-9a4b-0f7a1e2b3c4d'
]
]),
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/outreach/enroll"
payload := strings.NewReader("{\n \"campaign_id\": \"…\",\n \"lead_ids\": [\n \"8f1c0e6a-1c3e-4f2a-9a4b-0f7a1e2b3c4d\"\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/outreach/enroll")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"campaign_id\": \"…\",\n \"lead_ids\": [\n \"8f1c0e6a-1c3e-4f2a-9a4b-0f7a1e2b3c4d\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.manypi.com/api/outreach/enroll")
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 \"campaign_id\": \"…\",\n \"lead_ids\": [\n \"8f1c0e6a-1c3e-4f2a-9a4b-0f7a1e2b3c4d\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"enrolled": 123,
"skipped_no_email": 123,
"skipped_suppressed": 123,
"skipped_invalid": 123,
"already_enrolled": 123,
"queued_for_validation": 123
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "<string>"
}Authorizations
A ManyPI API key (mpi_...) from Settings → API keys, or an OAuth 2.1 access token obtained through the MCP consent flow.
Body
Response
Enrollment outcome.
Skips are reported as separate counters rather than a single skipped total with a reasons map. Add the four skip counters together for the number of leads that were not enrolled.
Leads whose first step was queued.
Leads with no email address.
Addresses on the do-not-contact list, or already unsubscribed.
Addresses already known to be undeliverable.
Leads already enrolled in this campaign.
Unchecked addresses sent for validation first, when the brand has validate-on-enroll turned on. These are not yet enrolled.
