General statistics per campaign
This report provides an overview of campaign statistics, including the number of contacted prospects, response rate, bounce rate, interest levels, and more. It focuses on campaign-level metrics. For detailed statistics at each step of the campaign, please refer to the Complete statistics for each level of campaign report.
- All of the campaign metrics are counted as distinct events per prospect, in a given period - if one prospect receives an opening email and a followup in one campaign, in the defined period, the
delivered
statistic will be counted as 1, as one prospect has been delivered an email. - The statistics are grouped by campaign and sending email. This means that you might see statistics for the same campaign multiple times if it is sent from multiple mailboxes.
- Campaigns that did not send any messages during the selected period will not be included in the results.
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/campaigns
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/campaigns" \
--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/campaigns"))
.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/campaigns",
{
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 data[].id
ascending. You can change the order by using the sort
parameter.
Key | Value | Required | Description |
---|---|---|---|
sort | +id /-id | No | Sort the results by data[].id . 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 31, 2025, covering two campaigns sent from two email addresses. You can observe three objects as the "First Successful Campaign" utilizes inbox rotation and includes statistics for both senders, while "Second campaign" uses one sending address.
{
"status": "READY",
"report": {
"description": "General_statistics_per_campaign_2025-01-01-2025-01-31",
"data": [
{
"id": 1234567,
"name": "First successful campaign",
"status": "RUNNING",
"sent_from": "email-1@gmail.com",
"contacted_prospects": 64,
"bounced": 0,
"bounced_rate": "0.0%",
"opened": 0,
"open_rate": "0.0%",
"clicked": 0,
"opt_out": 0,
"delivered": 64,
"responded": 7,
"response_rate": "10.9%",
"interested": 2,
"maybe_later": 1,
"not_interested": 4
},
{
"id": 1234567,
"name": "First successful campaign",
"status": "RUNNING",
"sent_from": "email-2@gmail.com",
"contacted_prospects": 67,
"bounced": 0,
"bounced_rate": "0.0%",
"opened": 0,
"open_rate": "0.0%",
"clicked": 0,
"opt_out": 0,
"delivered": 67,
"responded": 8,
"response_rate": "11.9%",
"interested": 0,
"maybe_later": 0,
"not_interested": 6
},
{
"id": 9876543,
"name": "Second campaign",
"status": "RUNNING",
"sent_from": "email-1@gmail.com",
"contacted_prospects": 56,
"bounced": 0,
"bounced_rate": "0.0%",
"opened": 0,
"open_rate": "0.0%",
"clicked": 0,
"opt_out": 0,
"delivered": 56,
"responded": 8,
"response_rate": "14.3%",
"interested": 3,
"maybe_later": 0,
"not_interested": 4
}
]
}
}
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[].status | string | Current status of the campaign. One of: RUNNING PAUSED STOPPED EDITED DRAFT COMPLETED DELETED |
└─data[].sent_from | string | Email address used to send the campaign |
└─data[].contacted_prospects | integer | Number of contacted prospects |
└─data[].bounced | integer | Number of prospects who bounced |
└─data[].bounced_rate | string | Percentage of prospects who bounced |
└─data[].opened | integer | Number of prospects who opened an email |
└─data[].open_rate | string | Percentage of prospects who opened an email |
└─data[].clicked | integer | Number of prospects who who clicked a link |
└─data[].opt_out | integer | Number of prospects who opted-out |
└─data[].delivered | integer | Number of prospects who received an email |
└─data[].responded | integer | Number of prospects who responded |
└─data[].response_rate | string | Percentage of prospects who responded |
└─data[].interested | integer | Number of "interested" responses |
└─data[].maybe_later | integer | Number of "maybe later" responses |
└─data[].not_interested | integer | Number of "not interested" responses |
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 |