List campaigns
GET /campaigns
The campaign list payload and response changed in 2026-05. If you're upgrading an existing integration, read Campaigns in the migration guide before you start.
Description
Lists the campaigns belonging to your tenant, newest first, as a cursor-paginated collection. A campaign is the gift catalogue offered on a send — it groups the products a recipient can be given, and it's the thing you reference from Create a gift request to drive what gets offered and how.
Each item in the list carries the full campaign shape — the same object Get a campaign returns, including its product, variant, stock-level, and warehouse details.
When to use
Use this endpoint to discover which campaigns exist — for example to
present a picker, or to find the id you'll pass as campaign_id when you
create a gift request. When you already know the
campaign you want and only need that one, fetch it directly with
Get a campaign rather than paging the whole list.
Parameters
Headers
AndOpen-API-Version (header) · string · required — the API version this request
targets. Always send 2026-05. Requests authenticate with a bearer token; see
Authentication for how to present it and
Environments for the regional base URL to send it to.
Query parameters
All four are optional; sent together they page, sort, and filter the same collection.
limit (query) · integer · optional — how many campaigns to return per page. Defaults
to 25, which is also the maximum: each campaign carries its full nested
catalogue, so the page is capped to keep response size and time predictable.
A larger value is clamped to 25 server-side rather than rejected; a
non-numeric, zero, or negative value is rejected with a 400.
after (query) · string · optional — the keyset pagination cursor. Pagination is
cursor-based, not page-numbered: to fetch the next page, pass the id of the
last campaign in the page you just received. Omit it for the first page.
A cursor that references no existing record is rejected with a 400. Stop
paging when the response's has_more is false.
sort (query) · string · optional — the field to order by, - prefix for
descending. Defaults to -created_at (newest first); created_at, name, and
-name are also accepted. Any other field is rejected with a 400.
status (query) · string · optional — filter by public status. Accepts a single value
(available or archived) or a comma-separated list of both. An unrecognised
value matches nothing and yields an empty page rather than an error.
Worked examples
The cURL version is the raw HTTP call; the JavaScript, Python, and Ruby versions are idiomatic equivalents. Each requests the first page with the defaults.
curl -X GET "https://api.andopen.co/campaigns" \
-H "Authorization: Bearer <api_key>" \
-H "AndOpen-API-Version: 2026-05"
const response = await fetch("https://api.andopen.co/campaigns", {
headers: {
Authorization: "Bearer <api_key>",
"AndOpen-API-Version": "2026-05",
},
});
const page = await response.json();
for (const campaign of page.data) {
console.log(campaign.id, campaign.name, campaign.status);
}
import requests
response = requests.get(
"https://api.andopen.co/campaigns",
headers={
"Authorization": "Bearer <api_key>",
"AndOpen-API-Version": "2026-05",
},
)
response.raise_for_status()
page = response.json()
for campaign in page["data"]:
print(campaign["id"], campaign["name"], campaign["status"])
require "net/http"
require "json"
uri = URI("https://api.andopen.co/campaigns")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer <api_key>"
request["AndOpen-API-Version"] = "2026-05"
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
http.request(request)
end
page = JSON.parse(response.body)
page["data"].each do |campaign|
puts "#{campaign['id']} #{campaign['name']} #{campaign['status']}"
end
Paging through every campaign
To walk the whole collection, keep fetching while has_more is true, passing
the last campaign's id as the after cursor each time.
async function listAllCampaigns() {
const campaigns = [];
let after;
do {
const url = new URL("https://api.andopen.co/campaigns");
if (after) url.searchParams.set("after", after);
const response = await fetch(url, {
headers: {
Authorization: "Bearer <api_key>",
"AndOpen-API-Version": "2026-05",
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const page = await response.json();
campaigns.push(...page.data);
const lastId = page.data.at(-1)?.id;
after = page.has_more && lastId ? lastId : undefined;
} while (after);
return campaigns;
}
Response shape
A successful call returns 200 OK with a page object: a data array of
campaigns plus a boolean cursor flag. Paging is driven by has_more and the
after cursor.
array · required— the campaigns in this page. Each entry is a full campaign object, identical to what Get a campaign returns; see that page for the per-field detail of the nested catalogue.boolean · required— whether more campaigns exist beyond this page. Whentrue, fetch the next page by passing the last item'sidas theaftercursor; whenfalse, you've reached the end.- Each campaign's
string · requiredis one ofavailable,archived, and itsarray · requiredis the inline catalogue tree.
{
"data": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Q2 Customer Appreciation",
"description": "Send a thank-you gift to our top accounts.",
"status": "available",
"archived_at": null,
"products": [
{
"id": "b2c3d4e5-f6a7-8901-bcde-f01234567890",
"name": "Leather Notebook",
"enabled": true,
"variants": [
{
"id": "c3d4e5f6-a7b8-9012-cdef-012345678901",
"name": "Medium / Black",
"sku": "NB-LTHR-MED-BLK",
"enabled": true,
"position": 1,
"stock_levels": [
{
"id": "d4e5f6a7-b8c9-0123-defa-123456789012",
"quantity": 42,
"warehouse": {
"id": "e5f6a7b8-c901-2345-efab-234567890123",
"name": "EU Central — Frankfurt",
"enabled": true,
"created_at": "2026-04-16T10:30:00.000Z",
"updated_at": "2026-04-16T10:30:00.000Z"
},
"created_at": "2026-04-16T10:30:00.000Z",
"updated_at": "2026-04-16T10:30:00.000Z"
}
],
"created_at": "2026-04-16T10:30:00.000Z",
"updated_at": "2026-04-16T10:30:00.000Z"
}
],
"created_at": "2026-04-16T10:30:00.000Z",
"updated_at": "2026-04-16T10:30:00.000Z"
}
],
"created_at": "2026-04-16T10:30:00.000Z",
"updated_at": "2026-04-16T10:30:00.000Z"
}
],
"has_more": true
}
Per-field notes
- Every item is the full campaign. The list does not return a trimmed
summary — each entry includes the same product, variant, stock-level, and
warehouse details as the single-campaign endpoint. This is why the page size
is capped at
25. has_moredrives paging, not a total count. The response carries no total and nonextURL. Treathas_more: falseas the only signal to stop, and build the next request from the last item'sid.statusfiltering is forgiving. An unknown filter value yields an empty page, not a400— so an emptydataarray can mean either "no matches" or "filter typo". The catalogue tree within each campaign follows the same warehouse-filtering rules described on Get a campaign.
Error cases
Every failure uses the shared error model — a top-level errors array of
objects carrying type, code, message, and (for field-level problems)
param. See Errors for the full type/code taxonomy and how
to handle each category; the codes below are the ones this endpoint produces.
| Status | Meaning |
|---|---|
| 400 | The request is malformed or violates a precondition. Typical triggers are a missing or unsupported `AndOpen-API-Version` header, a request body that is not valid JSON, or pagination parameters that fail server-side validation (`limit` non-numeric or non-positive, `after` referencing a record that does not exist). |
| 401 | Authentication failed — missing or invalid bearer token. |
| 403 | Authorised but not permitted to access this resource. |
| 429 | Too many requests — rate limit exceeded. |
| 500 | Internal server error. |
| 503 | Service unavailable — the tenant may be under maintenance. Check the `Retry-After` header. |
Every error carries a type, one of: validation_error, authentication_error, authorization_error, not_found_error, rate_limit_error, api_error.
The ones you'll meet in practice:
400 api_error— the request could not be processed:unsupported_api_versionwhen theAndOpen-API-Versionheader is missing or unsupported (returned before authentication), or a rejected pagination parameter — alimitthat is non-numeric, zero, or negative, anaftercursor that references no existing record, or an unsupportedsortfield.401 authentication_error— the bearer token is missing, invalid, or expired (invalid_token).403 authorization_error— the token is valid but not permitted to list campaigns (forbidden).429 rate_limit_error— too many requests; back off and retry (rate_limited). TheRateLimit-*response headers tell you the budget and reset time.