curl --request GET \
--url https://api.privy.com/v1/reports/campaigns/{campaign_id} \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.privy.com/v1/reports/campaigns/{campaign_id}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.privy.com/v1/reports/campaigns/{campaign_id}', 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://api.privy.com/v1/reports/campaigns/{campaign_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.privy.com/v1/reports/campaigns/{campaign_id}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.privy.com/v1/reports/campaigns/{campaign_id}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.privy.com/v1/reports/campaigns/{campaign_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"data": {
"campaign_id": "kD4qN8wZr1",
"type": "newsletter",
"title": "June Restock Announcement",
"channel": "email",
"sent_date": "2026-06-18",
"period": null,
"stats": {
"sent": {
"value": 8200,
"unit": "count"
},
"delivered": {
"value": 8020,
"unit": "count"
},
"delivery_rate": {
"value": 97.8,
"unit": "percent"
},
"opened_unique": {
"value": 3450,
"unit": "count"
},
"open_rate": {
"value": 43,
"unit": "percent"
},
"clicked_unique": {
"value": 610,
"unit": "count"
},
"click_rate": {
"value": 7.6,
"unit": "percent"
},
"bounced": {
"value": 74,
"unit": "count"
},
"bounce_rate": {
"value": 0.9,
"unit": "percent"
},
"spam_reports": {
"value": 3,
"unit": "count"
},
"unsubscribed": {
"value": 21,
"unit": "count"
},
"unsubscribe_rate": {
"value": 0.3,
"unit": "percent"
},
"attributed_orders": {
"value": 45,
"unit": "count"
},
"order_rate": {
"value": 0.6,
"unit": "percent"
},
"attributed_revenue": {
"value": 2810,
"unit": "usd"
},
"average_order_value": {
"value": 62.44,
"unit": "usd"
}
},
"ab_test": {
"state": "sent",
"winning_metric": "click_rate",
"results_source": "snapshot",
"measured_at": "2026-06-19T15:20:00Z",
"decision_due_at": "2026-06-19T15:20:00Z",
"winner_selected_at": "2026-06-19T15:24:00Z",
"completed_at": "2026-06-19T17:50:00Z",
"winner_version_id": "cpv_YJGOjpJ",
"selection_reason": "highest_metric",
"audience": {
"eligible_count": 8200,
"test_count": 2050,
"remainder_count": 6150
},
"versions": [
{
"id": "cpv_dkyYZmk",
"name": "Version A",
"is_control": true,
"is_winner": false,
"audience_share": 0.125,
"subject": "The restock you asked for is here",
"decision": {
"metric_value": {
"value": 4.8,
"unit": "percent"
},
"numerator": {
"value": 48,
"unit": "count"
},
"denominator": {
"value": 1003,
"unit": "count"
}
},
"stats": {
"assigned": {
"value": 1025,
"unit": "count"
},
"sent": {
"value": 1025,
"unit": "count"
},
"delivered": {
"value": 1003,
"unit": "count"
},
"delivery_rate": {
"value": 97.9,
"unit": "percent"
},
"opened_unique": {
"value": 431,
"unit": "count"
},
"open_rate": {
"value": 43,
"unit": "percent"
},
"clicked_unique": {
"value": 50,
"unit": "count"
},
"click_rate": {
"value": 5,
"unit": "percent"
},
"bounced": {
"value": 9,
"unit": "count"
},
"bounce_rate": {
"value": 0.9,
"unit": "percent"
},
"spam_reports": {
"value": 0,
"unit": "count"
},
"unsubscribed": {
"value": 3,
"unit": "count"
},
"unsubscribe_rate": {
"value": 0.3,
"unit": "percent"
},
"attributed_orders": {
"value": 4,
"unit": "count"
},
"order_rate": {
"value": 0.4,
"unit": "percent"
},
"attributed_revenue": {
"value": 238,
"unit": "usd"
},
"average_order_value": {
"value": 59.5,
"unit": "usd"
}
},
"top_links": [
{
"url": "https://shop.example.com/collections/restock",
"name": "Shop the restock",
"clicks": 44
}
]
},
{
"id": "cpv_YJGOjpJ",
"name": "Version B",
"is_control": false,
"is_winner": true,
"audience_share": 0.125,
"subject": "Your size is back, but not for long",
"decision": {
"metric_value": {
"value": 8,
"unit": "percent"
},
"numerator": {
"value": 80,
"unit": "count"
},
"denominator": {
"value": 1004,
"unit": "count"
}
},
"stats": {
"assigned": {
"value": 1025,
"unit": "count"
},
"sent": {
"value": 7175,
"unit": "count"
},
"delivered": {
"value": 7017,
"unit": "count"
},
"delivery_rate": {
"value": 97.8,
"unit": "percent"
},
"opened_unique": {
"value": 3019,
"unit": "count"
},
"open_rate": {
"value": 43,
"unit": "percent"
},
"clicked_unique": {
"value": 560,
"unit": "count"
},
"click_rate": {
"value": 8,
"unit": "percent"
},
"bounced": {
"value": 65,
"unit": "count"
},
"bounce_rate": {
"value": 0.9,
"unit": "percent"
},
"spam_reports": {
"value": 3,
"unit": "count"
},
"unsubscribed": {
"value": 18,
"unit": "count"
},
"unsubscribe_rate": {
"value": 0.3,
"unit": "percent"
},
"attributed_orders": {
"value": 41,
"unit": "count"
},
"order_rate": {
"value": 0.6,
"unit": "percent"
},
"attributed_revenue": {
"value": 2572,
"unit": "usd"
},
"average_order_value": {
"value": 62.73,
"unit": "usd"
}
},
"top_links": [
{
"url": "https://shop.example.com/collections/restock",
"name": "Shop the restock",
"clicks": 466
}
]
}
]
},
"top_links": [
{
"url": "https://shop.example.com/collections/restock",
"name": "Shop the restock",
"clicks": 44
}
]
}
}{
"error": {
"code": "unauthorized",
"message": "Bearer token is missing or invalid"
}
}{
"error": {
"code": "insufficient_scope",
"message": "Token does not have the required scope"
}
}{
"error": {
"code": "not_found",
"message": "Not found"
}
}{
"error": {
"code": "validation_failed",
"message": "One or more fields are invalid",
"details": [
{
"field": "email",
"message": "is required"
}
]
}
}{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded"
}
}Get campaign performance
One message campaign’s engagement funnel — sends, deliveries, opens,
clicks, attribution, and the derived rates — plus its most-clicked
links. Resolves newsletters and flow campaigns alike (flow campaigns
carry no sent_date, and instead name the flow that sends them in
flow). SMS campaigns return the SMS stat set (opted_out,
failed) and no links.
Without period parameters the funnel covers the campaign’s whole
life. Pass period (or period=custom with start_date/end_date)
to scope it to a reporting window — the same window vocabulary as
every other report read, so its numbers line up with
/v1/reports/campaigns rows for the same period. The response echoes
the window in period; null means lifetime. top_links is always
lifetime — link clicks are not stored by day.
The campaign’s configuration is /v1/campaigns/{id}.
Required scope: reports_read
curl --request GET \
--url https://api.privy.com/v1/reports/campaigns/{campaign_id} \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.privy.com/v1/reports/campaigns/{campaign_id}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.privy.com/v1/reports/campaigns/{campaign_id}', 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://api.privy.com/v1/reports/campaigns/{campaign_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.privy.com/v1/reports/campaigns/{campaign_id}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.privy.com/v1/reports/campaigns/{campaign_id}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.privy.com/v1/reports/campaigns/{campaign_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"data": {
"campaign_id": "kD4qN8wZr1",
"type": "newsletter",
"title": "June Restock Announcement",
"channel": "email",
"sent_date": "2026-06-18",
"period": null,
"stats": {
"sent": {
"value": 8200,
"unit": "count"
},
"delivered": {
"value": 8020,
"unit": "count"
},
"delivery_rate": {
"value": 97.8,
"unit": "percent"
},
"opened_unique": {
"value": 3450,
"unit": "count"
},
"open_rate": {
"value": 43,
"unit": "percent"
},
"clicked_unique": {
"value": 610,
"unit": "count"
},
"click_rate": {
"value": 7.6,
"unit": "percent"
},
"bounced": {
"value": 74,
"unit": "count"
},
"bounce_rate": {
"value": 0.9,
"unit": "percent"
},
"spam_reports": {
"value": 3,
"unit": "count"
},
"unsubscribed": {
"value": 21,
"unit": "count"
},
"unsubscribe_rate": {
"value": 0.3,
"unit": "percent"
},
"attributed_orders": {
"value": 45,
"unit": "count"
},
"order_rate": {
"value": 0.6,
"unit": "percent"
},
"attributed_revenue": {
"value": 2810,
"unit": "usd"
},
"average_order_value": {
"value": 62.44,
"unit": "usd"
}
},
"ab_test": {
"state": "sent",
"winning_metric": "click_rate",
"results_source": "snapshot",
"measured_at": "2026-06-19T15:20:00Z",
"decision_due_at": "2026-06-19T15:20:00Z",
"winner_selected_at": "2026-06-19T15:24:00Z",
"completed_at": "2026-06-19T17:50:00Z",
"winner_version_id": "cpv_YJGOjpJ",
"selection_reason": "highest_metric",
"audience": {
"eligible_count": 8200,
"test_count": 2050,
"remainder_count": 6150
},
"versions": [
{
"id": "cpv_dkyYZmk",
"name": "Version A",
"is_control": true,
"is_winner": false,
"audience_share": 0.125,
"subject": "The restock you asked for is here",
"decision": {
"metric_value": {
"value": 4.8,
"unit": "percent"
},
"numerator": {
"value": 48,
"unit": "count"
},
"denominator": {
"value": 1003,
"unit": "count"
}
},
"stats": {
"assigned": {
"value": 1025,
"unit": "count"
},
"sent": {
"value": 1025,
"unit": "count"
},
"delivered": {
"value": 1003,
"unit": "count"
},
"delivery_rate": {
"value": 97.9,
"unit": "percent"
},
"opened_unique": {
"value": 431,
"unit": "count"
},
"open_rate": {
"value": 43,
"unit": "percent"
},
"clicked_unique": {
"value": 50,
"unit": "count"
},
"click_rate": {
"value": 5,
"unit": "percent"
},
"bounced": {
"value": 9,
"unit": "count"
},
"bounce_rate": {
"value": 0.9,
"unit": "percent"
},
"spam_reports": {
"value": 0,
"unit": "count"
},
"unsubscribed": {
"value": 3,
"unit": "count"
},
"unsubscribe_rate": {
"value": 0.3,
"unit": "percent"
},
"attributed_orders": {
"value": 4,
"unit": "count"
},
"order_rate": {
"value": 0.4,
"unit": "percent"
},
"attributed_revenue": {
"value": 238,
"unit": "usd"
},
"average_order_value": {
"value": 59.5,
"unit": "usd"
}
},
"top_links": [
{
"url": "https://shop.example.com/collections/restock",
"name": "Shop the restock",
"clicks": 44
}
]
},
{
"id": "cpv_YJGOjpJ",
"name": "Version B",
"is_control": false,
"is_winner": true,
"audience_share": 0.125,
"subject": "Your size is back, but not for long",
"decision": {
"metric_value": {
"value": 8,
"unit": "percent"
},
"numerator": {
"value": 80,
"unit": "count"
},
"denominator": {
"value": 1004,
"unit": "count"
}
},
"stats": {
"assigned": {
"value": 1025,
"unit": "count"
},
"sent": {
"value": 7175,
"unit": "count"
},
"delivered": {
"value": 7017,
"unit": "count"
},
"delivery_rate": {
"value": 97.8,
"unit": "percent"
},
"opened_unique": {
"value": 3019,
"unit": "count"
},
"open_rate": {
"value": 43,
"unit": "percent"
},
"clicked_unique": {
"value": 560,
"unit": "count"
},
"click_rate": {
"value": 8,
"unit": "percent"
},
"bounced": {
"value": 65,
"unit": "count"
},
"bounce_rate": {
"value": 0.9,
"unit": "percent"
},
"spam_reports": {
"value": 3,
"unit": "count"
},
"unsubscribed": {
"value": 18,
"unit": "count"
},
"unsubscribe_rate": {
"value": 0.3,
"unit": "percent"
},
"attributed_orders": {
"value": 41,
"unit": "count"
},
"order_rate": {
"value": 0.6,
"unit": "percent"
},
"attributed_revenue": {
"value": 2572,
"unit": "usd"
},
"average_order_value": {
"value": 62.73,
"unit": "usd"
}
},
"top_links": [
{
"url": "https://shop.example.com/collections/restock",
"name": "Shop the restock",
"clicks": 466
}
]
}
]
},
"top_links": [
{
"url": "https://shop.example.com/collections/restock",
"name": "Shop the restock",
"clicks": 44
}
]
}
}{
"error": {
"code": "unauthorized",
"message": "Bearer token is missing or invalid"
}
}{
"error": {
"code": "insufficient_scope",
"message": "Token does not have the required scope"
}
}{
"error": {
"code": "not_found",
"message": "Not found"
}
}{
"error": {
"code": "validation_failed",
"message": "One or more fields are invalid",
"details": [
{
"field": "email",
"message": "is required"
}
]
}
}{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded"
}
}Authorizations
Send either an API token or an OAuth access token as
Authorization: Bearer <token>. See the Authentication page for details.
Headers
Target a subaccount instead of the main connected account.
Pass a subaccount id from get_account's subaccounts list. Only direct subaccounts
of the connected account are reachable — omit this for the main account itself.
Path Parameters
The campaign's unique identifier (returned as campaign_id in report responses and id in campaign responses). Ids are opaque public strings — a numeric id from a Privy dashboard URL is not a valid API id and will 404; list campaigns and match by title instead.
Query Parameters
Scope the funnel to a date range, resolved in the account's time zone. Pass custom together with start_date and end_date. Omit entirely for the campaign's lifetime funnel (the default).
last_7_days, last_30_days, last_90_days, this_month, last_month, custom Inclusive start of a custom period (ISO 8601 date). Required when period=custom, ignored otherwise.
Inclusive end of a custom period (ISO 8601 date). Required when period=custom, ignored otherwise. Custom periods are capped at 366 days.
Response
The campaign's engagement funnel — lifetime, or scoped to the requested period.
Show child attributes
Show child attributes
Was this page helpful?