Number of messages sent from each level in campaigns
This report focuses on the number of emails sent from your campaigns. It provides a breakdown of emails sent per campaign, day, path, step, and version. For campaigns sent from multiple mailboxes, the data will be presented as a total rather than by individual mailbox. To see a similar per-mailbox breakdown, review the Open rate per campaign report.
You can preview example results below
Generating a report
Use the below endpoint to generate a report hash
. Afterwards use the rest/v2/reports/{hash} to retrieve the statistics data.
Request
Endpoint
POST https://api.woodpecker.co/rest/v2/reports/messages
Headers
x-api-key: {YOUR_API_KEY}
Content-type: application/json
For details on how to authenticate your requests, please see the authentication guide.
Body
A JSON object containing from
and to
date fields, that define the date period for the report's data generation.
You can generate data for up to 30 last days
{
"from": "YYYY-MM-DD",
"to": "YYYY-MM-DD"
}
Field | Type | Description | Example |
---|---|---|---|
from | string | Start date of the report in ISO 8601 format | "2025-01-01" |
to | string | End date of the report in ISO 8601 format | "2025-01-31" |
Request sample
Generate a report hash
- cURL
- Java
- Node.js
curl --request POST \
--url "https://api.woodpecker.co/rest/v2/reports/messages" \
--header "Content-Type: application/json" \
--header "x-api-key: {YOUR_API_KEY}" \
--data '{
"from": "YYYY-MM-DD",
"to": "YYYY-MM-DD"
}'
import com.fasterxml.jackson.databind.ObjectMapper;
public class WoodpeckerApiClient {
public static String sendReportRequest(String apiKey, String fromDate, String toDate) throws Exception {
HttpClient client = HttpClient.newHttpClient();
ObjectMapper objectMapper = new ObjectMapper();
Map<String, String> requestBody = Map.of(
"from", fromDate,
"to", toDate
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.woodpecker.co/rest/v2/reports/messages"))
.header("Content-Type", "application/json")
.header("x-api-key", apiKey)
.POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(requestBody)))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
Map<String, String> responseBody = objectMapper.readValue(response.body(), Map.class);
return responseBody.get("hash");
}
}
const axios = require("axios");
async function sendReportRequest(apiKey, fromDate, toDate) {
try {
const response = await axios.post(
"https://api.woodpecker.co/rest/v2/reports/messages",
{
from: fromDate,
to: toDate,
},
{
headers: {
"Content-Type": "application/json",
"x-api-key": apiKey,
},
}
);
return response.data.hash;
} catch (error) {
console.error("Error making API request:", error);
throw error;
}
}
const apiKey = "YOUR_API_KEY";
const fromDate = "YYYY-MM-DD";
const toDate = "YYYY-MM-DD";
sendReportRequest(apiKey, fromDate, toDate)
.then((hash) => console.log("Report hash:", hash))
.catch((error) => console.error(error));
Response
Response examples
- 200
- 400
- 401
- 404
- 405
- 500
The response is a hash that you should use the fetch the report content using the rest/v2/reports/{hash} endpoint.
{
"hash":"c966572e5b7c12d73f....347b5186242782c9550d"
}
Body schema
Field | Type | Description |
---|---|---|
hash | string | Representation of the report's ID. Use it to fetch the generated data |
Invalid request parameters or malformed request syntax. Please review the request body
{
"title": "Bad Request",
"status": 400,
"detail": "Reports can be generated from the last 30 days. Change the from parameter" | "Value of to is incorrect." | "Value of from is incorrect." | "From date cannot be later than the to date.",
"timestamp": "2025-03-05 17:57:00"
}
Body schema
Field | Type | Description |
---|---|---|
title | string | A short title describing the error |
status | integer | The HTTP status code |
detail | string | A detailed message explaining the error |
timestamp | string | The timestamp when the error occurred, YYYY-MM-DD HH:MM:SS UTC |
An issue with authorization. Please review the authorization guide
{
"title": "Unauthorized",
"status": 401,
"detail": "Invalid api key",
"timestamp": "2025-03-05 17:57:00"
}
Body schema
Field | Type | Description |
---|---|---|
title | string | A short title describing the error |
status | integer | The HTTP status code |
detail | string | A detailed message explaining the error |
timestamp | string | The timestamp when the error occurred, YYYY-MM-DD HH:MM:SS UTC |
Please review the request URL
{
"title": "Not Found",
"status": 404,
"detail": "Requested resource does not exist",
"timestamp": "2025-03-05 17:57:00"
}
Body schema
Field | Type | Description |
---|---|---|
title | string | A short title describing the error |
status | integer | The HTTP status code |
detail | string | A detailed message explaining the error |
timestamp | string | The timestamp when the error occurred, YYYY-MM-DD HH:MM:SS UTC |
Incorrect report name in the URL. Please review the endpoint URL
Status: 405
Body: None
Unknown error, please try again later
{
"title": "Internal server error",
"status": 500,
"detail": "An unexpected error has occurred. Please try again later.",
"timestamp": "2025-03-05 17:57:00"
}
Body schema
Field | Type | Description |
---|---|---|
title | string | A short title describing the error |
status | integer | The HTTP status code |
detail | string | A detailed message explaining the error |
timestamp | string | The timestamp when the error occurred, YYYY-MM-DD HH:MM:SS UTC |
Retrieving a report
After requesting the report generation, you can fetch it using the below endpoint. Insert the hash, obtained from the above request, to the URL.
Preparing the data may take some time. Please check the status
value to monitor the progress. The possible statuses are PENDING
, WAITING
, IN_PROGRESS
, READY
and FAILED
.
Request
Endpoint
GET https://api.woodpecker.co/rest/v2/reports/{hash}
Headers
x-api-key: {YOUR_API_KEY}
For details on how to authenticate your requests, please see the authentication guide.
Parameters
By default, the results are sorted by id
, sent_date
, step
and version
ascending. You can change the order by using one of the sort
parameter and one of the values.
Key | Value | Required | Description |
---|---|---|---|
sort | +id /-id | No | Sort the results by data[].id . Use - for descending and + for ascending |
sort | +sent_date /-sent_date | No | Sort the results by data[].sent_date . Use - for descending and + for ascending |
Request sample
Retrieve a report using the hash
- cURL
- Java
- Node.js
curl --request GET \
--url "https://api.woodpecker.co/rest/v2/reports/{hash}" \
--header "x-api-key: {YOUR_API_KEY}"
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class WoodpeckerApiClient {
public static void main(String[] args) {
String apiKey = "YOUR_API_KEY"; // Replace with your actual API key
String hash = "YOUR_REPORT_HASH"; // Replace with the actual hash
String endpoint = "https://api.woodpecker.co/rest/v2/reports/" + hash;
try {
URL url = new URL(endpoint);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("x-api-key", apiKey);
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println("Response: " + response.toString());
} else {
System.out.println("GET request failed with response code: " + responseCode);
}
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
const axios = require("axios");
const apiKey = "YOUR_API_KEY";
const hash = "YOUR_REPORT_HASH";
const url = `https://api.woodpecker.co/rest/v2/reports/${hash}`;
axios
.get(url, {
headers: {
"x-api-key": apiKey,
},
})
.then((response) => {
console.log("Response:", response.data);
})
.catch((error) => {
console.error("Error:", error.response ? error.response.data : error.message);
});
Response
Response examples
- 200
- 401
- 404
- 500
This example presents campaign statistics for the period from January 1 to January 13, 2025, covering two campaigns. Each object represents a number of emails sent, grouped by campaign, day, path, step and version.
The campaign with ID 1234567 is a linear campaign that does not have any conditions/paths, but it does include multiple A/B versions for each step. In contrast, campaign 9876543 has a condition before the first email step, resulting in two separate objects for the emails sent from the second step, as they follow different paths.
{
"status": "READY",
"report": {
"description": "Number_of_messages_sent_from_each_level_in_campaigns_2025-01-01-2025-01-13",
"data": [
{
"id": 1234567,
"name": "Campaign with A/B versions and no IF condition",
"sent_date": "2025-01-01",
"path": "",
"step": 1,
"version": "A",
"sent": 25
},
{
"id": 1234567,
"name": "Campaign with A/B versions and no IF condition",
"sent_date": "2025-01-01",
"path": "",
"step": 1,
"version": "B",
"sent": 25
},
{
"id": 1234567,
"name": "Campaign with A/B versions and no IF condition",
"sent_date": "2025-01-01",
"path": "",
"step": 1,
"version": "C",
"sent": 25
},
{
"id": 1234567,
"name": "Campaign with A/B versions and no IF condition",
"sent_date": "2025-01-01",
"path": "",
"step": 1,
"version": "D",
"sent": 25
},
{
"id": 1234567,
"name": "Campaign with A/B versions and no IF condition",
"sent_date": "2025-01-03",
"path": "",
"step": 1,
"version": "A",
"sent": 10
},
{
"id": 1234567,
"name": "Campaign with A/B versions and no IF condition",
"sent_date": "2025-01-03",
"path": "",
"step": 1,
"version": "B",
"sent": 10
},
{
"id": 1234567,
"name": "Campaign with A/B versions and no IF condition",
"sent_date": "2025-01-03",
"path": "",
"step": 2,
"version": "A",
"sent": 25
},
{
"id": 1234567,
"name": "Campaign with A/B versions and no IF condition",
"sent_date": "2025-01-03",
"path": "",
"step": 2,
"version": "B",
"sent": 25
},
{
"id": 1234567,
"name": "Campaign with A/B versions and no IF condition",
"sent_date": "2025-01-03",
"path": "",
"step": 2,
"version": "C",
"sent": 25
},
{
"id": 1234567,
"name": "Campaign with A/B versions and no IF condition",
"sent_date": "2025-01-03",
"path": "",
"step": 2,
"version": "D",
"sent": 25
},
{
"id": 9876543,
"name": "Campaign with A/B versions and a condition before the first email",
"sent_date": "2025-01-08",
"path": "Path YES/NO",
"step": 1,
"version": "A",
"sent": 15
},
{
"id": 9876543,
"name": "Campaign with A/B versions and a condition before the first email",
"sent_date": "2025-01-08",
"path": "Path YES/NO",
"step": 1,
"version": "B",
"sent": 15
},
{
"id": 9876543,
"name": "Campaign with A/B versions and a condition before the first email",
"sent_date": "2025-01-08",
"path": "Path YES/NO",
"step": 1,
"version": "C",
"sent": 15
},
{
"id": 9876543,
"name": "Campaign with A/B versions and a condition before the first email",
"sent_date": "2025-01-13",
"path": "Path NO",
"step": 2,
"version": "A",
"sent": 30
},
{
"id": 9876543,
"name": "Campaign with A/B versions and a condition before the first email",
"sent_date": "2025-01-13",
"path": "Path YES",
"step": 2,
"version": "A",
"sent": 15
}
]
}
}
Body schema
Field | Type | Description |
---|---|---|
status | string | Status of the generation. PENDING , WAITING , IN_PROGRESS , READY , FAILED |
report | object/null | Container for report details. Null if status is not READY |
└─report.description | string | Full name of the report and its period |
└─report.data | array | List of campaign statistics |
└─data[].id | integer | Unique ID of the campaign |
└─data[].name | string | Name of the campaign |
└─data[].sent_date | string | Date of sending emails from a specific step YYYY-MM-DD |
└─data[].path | string | Indication from which path the emails were sent. "" - there's no condition in this campaign; PATH YES - the sum of sent emails refers to the YES path on a given step; PATH NO - the sum of sent emails refers to the NO path on a given step; PATH YES/NO IF-condition is set up in a further step of the campaign, it has not been evaluated yet |
└─data[].step | integer | Step of the campaign |
└─data[].version | string | A/B/C/D/E version of of a step. Defaults to A |
└─data[].sent | integer | Number of emails sent |
An issue with authorization. Please review the authorization guide
{
"title": "Unauthorized",
"status": 401,
"detail": "Invalid api key",
"timestamp": "2025-03-05 17:57:00"
}
Body schema
Field | Type | Description |
---|---|---|
title | string | A short title describing the error |
status | integer | The HTTP status code |
detail | string | A detailed message explaining the error |
timestamp | string | The timestamp when the error occurred, YYYY-MM-DD HH:MM:SS UTC |
Report not found - please check the hash or the request URL.
{
"title": "Not Found",
"status": 404,
"detail": "Requested resource does not exist",
"timestamp": "2025-03-05 17:57:00"
}
Body schema
Field | Type | Description |
---|---|---|
title | string | A short title describing the error |
status | integer | The HTTP status code |
detail | string | A detailed message explaining the error |
timestamp | string | The timestamp when the error occurred, YYYY-MM-DD HH:MM:SS UTC |
Unknown error, please try again later
{
"title": "Internal server error",
"status": 500,
"detail": "An unexpected error has occurred. Please try again later.",
"timestamp": "2025-03-05 17:57:00"
}
Body schema
Field | Type | Description |
---|---|---|
title | string | A short title describing the error |
status | integer | The HTTP status code |
detail | string | A detailed message explaining the error |
timestamp | string | The timestamp when the error occurred, YYYY-MM-DD HH:MM:SS UTC |