Get a campaign
GET /campaigns/:id
The campaign payload and response changed in 2026-05. If you're upgrading an existing integration, read Campaigns in the migration guide before you start.
Description
Retrieves a single campaign by its id. A campaign is the gift catalogue
offered on a send — the products a recipient can be given, and the configuration
that drives how a gift request built against it
behaves.
The campaign is returned as a single JSON object that includes its product,
variant, stock-level, and warehouse details inline, so one call gives you
everything — no follow-up requests and no include parameter.
When to use
Use this endpoint when you already know which campaign you want — for example to
show its products before creating a gift request, or to check current stock. To
discover campaigns or to find an id in the first place, use
List campaigns; the list returns the identical per-campaign shape,
so anything documented here applies to each item there too.
Parameters
Path
id (path) · string · required — the campaign to retrieve. An id that matches no
campaign in your tenant returns 404, not an empty body.
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.
Worked examples
The cURL version is the raw HTTP call; the JavaScript, Python, and Ruby versions
are idiomatic equivalents. Replace the path id with the campaign you want.
curl -X GET "https://api.andopen.co/campaigns/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer <api_key>" \
-H "AndOpen-API-Version: 2026-05"
const campaignId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
const response = await fetch(
`https://api.andopen.co/campaigns/${campaignId}`,
{
headers: {
Authorization: "Bearer <api_key>",
"AndOpen-API-Version": "2026-05",
},
},
);
const campaign = await response.json();
console.log(campaign.name, campaign.status);
for (const product of campaign.products) {
console.log(product.name, product.variants.length, "variants");
}
import requests
campaign_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
response = requests.get(
f"https://api.andopen.co/campaigns/{campaign_id}",
headers={
"Authorization": "Bearer <api_key>",
"AndOpen-API-Version": "2026-05",
},
)
response.raise_for_status()
campaign = response.json()
print(campaign["name"], campaign["status"])
for product in campaign["products"]:
print(product["name"], len(product["variants"]), "variants")
require "net/http"
require "json"
campaign_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
uri = URI("https://api.andopen.co/campaigns/#{campaign_id}")
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
campaign = JSON.parse(response.body)
puts "#{campaign['name']} #{campaign['status']}"
campaign["products"].each do |product|
puts "#{product['name']} #{product['variants'].length} variants"
end
Response shape
A successful call returns 200 OK with the campaign as a flat top-level JSON
object. The catalogue associations are returned inline rather than as references
you have to fetch separately.
string · required— the public-facing status, one ofavailable,archived. Internal hidden states are collapsed toavailablefor API consumers.string · required · nullable— the long-form description. Always present as a key, butnullwhen none has been set.string · date-time · required · nullable— when the campaign was archived, ornullif it has not been.array · required— the products on offer, returned inline. Each product carries itsarray · required, and each variant itsarray · requireddown to the warehouse.
{
"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"
}
Per-field notes
- The catalogue is filtered to the campaign's warehouse. When a campaign has
a fulfilment warehouse, products, variants, and stock levels with no stock at
that warehouse are omitted, and each variant exposes a single stock level
scoped to that warehouse. Campaigns without a warehouse (for example voucher-
or charity-only campaigns) return the catalogue unfiltered, and a variant may
then carry several
stock_levels. stock_levels[].quantityis the in-stock figure. It's the net units fulfillable for the variant at that warehouse — when pending stock is enabled it nets expected inflow against backorders, otherwise it's units on hand. It's always positive for a stock level that appears in a response, so it's the number that decides whether a variant is offerable.variantsare ordered bypositionascending. Render them in array order to match how the campaign is configured.nameandskucan benullon a variant. A single-variant product often has no own variant name; treatnullas "inherits the product name".- Server-set fields are read-only.
id,status,archived_at, and the timestamps are assigned by &Open; this endpoint only reads them.
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. |
| 404 | The requested resource does not exist. |
| 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:
404 not_found_error— no campaign with thatidexists in your tenant (not_found).400 api_error—unsupported_api_versionwhen theAndOpen-API-Versionheader is missing or unsupported (returned before authentication).401 authentication_error— the bearer token is missing, invalid, or expired (invalid_token).403 authorization_error— the token is valid but not permitted to read this campaign (forbidden).429 rate_limit_error— too many requests; back off and retry (rate_limited). TheRateLimit-*response headers tell you the budget and reset time.