Get inbox messages
Retrieve a paginated list of email messages available in the Woodpecker inbox. You can use the available parameters to narrow down the results by mailbox, campaign, prospect status, or interest level. In standard Woodpecker accounts and agency HQs, the /inbox endpoint lets you access emails from mailboxes connected by the owner of the API key. In agency client accounts, it provides access to all connected mailboxes.
If you'd like to retrieve responses for a specific prospect, use the v2/prospects endpoint
Request
Endpoint
GET https://api.woodpecker.co/rest/v2/inbox/messages
Headers
x-api-key: {YOUR_API_KEY}
For details on how to authenticate your requests, please see the authentication guide.
Parameters
You can use the query parameters below to filter inbox messages. Each filter is optional - if you omit a parameter, that filter will not be applied. If no parameters are provided, the request will return unfiltered inbox messages based on the default pagination settings.
| Parameter | Required | Type | Description |
|---|---|---|---|
prospect_status | No | string | Filters inbox messages by the prospect's current status. Available values: RESPONDED, AUTOREPLIED, BOUNCED, BLACKLISTED, OPT_OUT. Mutually exclusive with prospect_interest_level and out_of_campaign |
prospect_interest_level | No | string | Filters inbox messages by the prospect's interest level. Available values: INTERESTED, MAYBE_LATER, NOT_INTERESTED, NOT_MARKED. Mutually exclusive with prospect_status and out_of_campaign |
mailbox_ids | No | string (comma-separated integers) | Retrieve only responses fetched from the specified mailbox IMAP accounts. Use /mailboxes endpoint to fetch mailbox details |
campaign_ids | No | string (comma-separated integers) | Retrieve only responses associated with given campaigns. Use /campaign endpoints to fetch campaign details |
search_phrases | No | array[string] | Filters messages using up to 20 search phrases. Phrases must be at least 3 characters long. Phrases can be provided as a comma-separated list search_phrases=one,two,three with a combined total length of 100 characters, or as repeated parameters search_phrases=one&search_phrases=two, where each phrase can be up to 100 characters long |
read | No | boolean | Whether to list messages that are marked as read or unread. If not specified, both will be included. |
out_of_campaign | No | string | Filters inbox messages that are not associated with any prospects. Accepted value: YES. Mutually exclusive with prospect_status and prospect_interest_level |
per_page | No | integer | Number of records per page. Default: 10, maximum: 50 |
next_page_cursor | No | string | Cursor used to retrieve a specific page of results. Use null to start from the first page. Only values returned in the next_page_cursor field of a previous response are accepted. Mutually exclusive with previous_page_cursor, but both can be null. See the pagination section for more details |
previous_page_cursor | No | string | Cursor used to retrieve the previous page of results. Use null to start from the first page. Mutually exclusive with next_page_cursor, but both can be null. Only values returned in the previous_page_cursor field of a previous response are accepted. |
Request samples
Retrieve 10 latest responses marked as INTERESTED
- cURL
- Python
- Java
- Node.js
- PHP
curl --request GET \
--url "https://api.woodpecker.co/rest/v2/inbox/messages?per_page=5&prospect_interest_level=INTERESTED" \
--header "x-api-key: {YOUR_API_KEY}"
import requests
def get_interested_messages():
url = "https://api.woodpecker.co/rest/v2/inbox/messages?per_page=5&prospect_interest_level=INTERESTED"
headers = {
"x-api-key": "{YOUR_API_KEY}"
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.json()
else:
raise Exception(f"GET request failed: {response.status_code}, {response.text}")
if __name__ == "__main__":
try:
data = get_interested_messages()
print("GET response:", data)
except Exception as e:
print("Error:", e)
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
public class WoodpeckerApiClient {
public static void main(String[] args) {
try {
String url = "https://api.woodpecker.co/rest/v2/inbox/messages?per_page=5&prospect_interest_level=INTERESTED";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("x-api-key", "{YOUR_API_KEY}")
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
System.out.println("GET response: " + response.body());
} else {
throw new Exception("GET request failed: " + response.statusCode() + ", " + response.body());
}
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}
const axios = require('axios');
async function getInterestedMessages() {
const url = 'https://api.woodpecker.co/rest/v2/inbox/messages?per_page=5&prospect_interest_level=INTERESTED';
const headers = {
'x-api-key': '{YOUR_API_KEY}'
};
try {
const response = await axios.get(url, { headers });
console.log('GET response:', response.data);
} catch (error) {
console.error('GET request failed:', error.response ? error.response.status : error.message);
}
}
getInterestedMessages();
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$client = new Client([
'base_uri' => 'https://api.woodpecker.co/rest/v2/',
'headers' => [
'x-api-key' => getenv('WOODPECKER_API_KEY'),
],
]);
try {
$response = $client->get('inbox/messages', [
'query' => [
'per_page' => 5,
'prospect_interest_level' => 'INTERESTED',
],
]);
echo $response->getStatusCode(), "\n";
echo $response->getBody(), "\n";
} catch (RequestException $e) {
echo "Error: ", $e->getMessage(), "\n";
if ($e->hasResponse()) {
echo $e->getResponse()->getBody(), "\n";
}
}
Response
Response examples
- 200
- 400
- 401
- 404
- 500
A list of responses retrieved from the Woodpecker inbox. The responses are sorted by the most recently received message
{
"content": [
{
"id": 123456,
"prospect_id": 987654,
"read": true,
"bounced": false,
"global_prospect_status": "ACTIVE",
"stamp": "2026-03-05T17:57:00Z",
"subject": "Re: Subject line of the response",
"body": {
"html": "<div>This is a reply in HTML format.</div>"
},
"campaigns": [
{
"id": 1763200,
"name": "Associated campaign name",
"status": "RUNNING",
"prospect": {
"campaign_prospect_id": 654321,
"interest_level": {
"level": "NOT_MARKED",
"ai_detected": false
},
"status": "PAUSED",
"resume_followup_after": "2026-03-12T00:00:00Z",
"status_reasons": [
{
"reason": "AUTOREPLIED"
}
],
"substatus": {
"created_at": "2026-01-15T14:00:00.000Z",
"id": 10123,
"name": "Q1 Contact"
}
}
}
],
"campaign": {
"id": 1763200,
"name": "Associated campaign name"
},
"from_name": "John Smith",
"from_email": "john@prospect.com",
"recipient": "receiving@email.com",
"mailbox_id": 112233,
"cc": ["steve@prospect.com", "jimothy@prospect.com"],
"not_found": false
}
],
"pagination": {
"previous_page_cursor": null,
"next_page_cursor": "Y3VyaW91cywgYXJlbid0IHlvdT8="
}
}
Body schema
| Field | Type | Description |
|---|---|---|
content | array[object] | Array of email messages |
└─[].id | integer | Unique ID of the prospect's response |
└─[].prospect_id | integer/null | Unique ID of the prospect associated with the response |
└─[].global_prospect_status | string/null | Prospect's global status: ACTIVE, BOUNCED, RESPONDED, BLACKLIST, INVALID, OPT_OUT. Available if prospect_id is not null |
└─[].read | boolean | Whether the message was marked as read |
└─[].bounced | boolean | Whehter the message was bounced |
└─[].stamp | string | ISO 8601 timestamp of the message in UTC |
└─[].subject | string | Subject line of the response |
└─[].body.html | string | Body of the response email in HTML format |
└─[].campaigns | array[object] | List of campaigns associated with the response. One response can be assigned to multiple campaigns |
└─[].id | integer | ID of the campaign associated with a response |
└─[].name | string | Name of the campaign |
└─[].status | string | Current status of the campaign. Available values: RUNNING, DRAFT, STOPPED, PAUSED, EDITED, COMPLETED, DELETED |
└─[].prospect | object | Campaign-level prospect information related to this message |
└─campaign_prospect_id | integer | ID of the campaign prospect. Different than global prospect_id |
└─interest_level | object | Interest level details for given prospect in associated campaign |
└─level | string | Prospect's Interest Level. Available levels: INTERESTED, MAYBE_LATER, NOT_INTERESTED, NOT_MARKED |
└─ai_detected | boolean | Indicates whether the level was set by AI or by the user |
└─status | string | Prospect's campaign status: ACTIVE, BOUNCED, RESPONDED, BLACKLIST, OPT_OUT, PAUSED, INVALID, NON_RESPONSIVE. REMOVED |
└─resume_followup_after | string | The earliest date and time prospects can be contacted, for example to followup after an autoresponder. ISO 8601 timestamp |
└─status_reasons | array[object] | List of reasons clarifying the prospect's status |
└─reason | string | Values can contain: SECONDARY_REPLY, OTHER_CAMPAIGN, AUTOREPLIED, MANUAL, SNIPPET_ISSUE, CATCH_ALL_DELIVERABLE, CATCH_ALL_RISKY, CATCH_ALL_UNDELIVERABLE, ESP_MATCHING |
└─substatus | object/null | Details about a substatus assigned to a prospect in a campaign. Read more here |
└─id | integer | ID of the substatus |
└─name | string | Name of the assigned substatus |
└─created_at | string | ISO 8601 timestamp of when the substatus was first created |
└─[].campaign | object | Object containing a campaign related to the response. Using campaigns instead is recommended. |
└─id | integer/null | ID of the campaign associated to a response |
└─name | string/null | Name of the campaign |
└─[].from_name | string | Name of the responder |
└─[].from_email | string | Email address of the responder |
└─[].recipient | string | Email address of the recipient (typically, the IMAP of the mailbox that sent the email) |
└─[].mailbox_id | integer | Id of the IMAP mailbox that received that message |
└─[].cc | array[string] | List of email addresses that were added to the CC of the received email |
└─[].not_found | boolean | Indicator whether the message content was unavailable to display. If true, try again later |
pagination | object | Object containing pagination information |
└─next_page_cursor | string | Pagination cursor. null means no more results (last page); non-null value indicates the cursor for the next page |
└─previous_page_cursor | string | Pagination cursor. null means first page; non-null value indicates the cursor for previous page |
Invalid request parameters or malformed request syntax. Example messages are provided below; other variations are also possible
{
"title": "Bad Request",
"status": 400,
"detail": "Query param 'per_page' value '0' must be greater or equal to 1" | "Query param {param} unsupported value {value}" | "string",
"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 |
Unexpected 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 |
Pagination
This endpoint uses cursor-based pagination. Results are returned in pages, and each response includes a next_page_cursor field:
- To start from the first page, omit the
next_page_cursorparameter or set it to null. - If
next_page_cursorarenull, there are no more pages available, - If
next_page_cursororprevious_page_cursorcontain a value, you can use it in the next request to retrieve the next or previous page of results,
Request first page:
curl --request GET \
--url "https://api.woodpecker.co/rest/v2/inbox/messages?prospect_interest_level=INTERESTED" \
--header "x-api-key: {YOUR_API_KEY}"
Example response:
{
"content": [...],
"pagination": {
"next_page_cursor": "T2ggSGkgTWFyayE=",
"previous_page_cursor": null
}
}
Request the next page:
curl --request GET \
--url "https://api.woodpecker.co/rest/v2/inbox/messages?next_page_cursor=T2ggSGkgTWFyayE=&prospect_interest_level=INTERESTED" \
--header "x-api-key: {YOUR_API_KEY}"
Example response:
{
"content": [...],
"pagination": {
"next_page_cursor": "TSXQncyBhIHRyYXAh",
"previous_page_cursor": "RG9uJ3QgdGVsbCBhbnlvbmUh"
}
}
Request the previous page:
curl --request GET \
--url "https://api.woodpecker.co/rest/v2/inbox/messages?next_page_cursor=RG9uJ3QgdGVsbCBhbnlvbmUh&prospect_interest_level=INTERESTED" \
--header "x-api-key: {YOUR_API_KEY}"