Skip to main content
Version: 2026-05

Get a campaign

GET /campaigns/:id

Shape changed since 2025-10

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 of available, archived. Internal hidden states are collapsed to available for API consumers.
  • string · required · nullable — the long-form description. Always present as a key, but null when none has been set.
  • string · date-time · required · nullable — when the campaign was archived, or null if it has not been.
  • array · required — the products on offer, returned inline. Each product carries its array · required, and each variant its array · required down 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[].quantity is 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.
  • variants are ordered by position ascending. Render them in array order to match how the campaign is configured.
  • name and sku can be null on a variant. A single-variant product often has no own variant name; treat null as "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.

StatusMeaning
400The 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).
401Authentication failed — missing or invalid bearer token.
403Authorised but not permitted to access this resource.
404The requested resource does not exist.
429Too many requests — rate limit exceeded.
500Internal server error.
503Service 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 that id exists in your tenant (not_found).
  • 400 api_errorunsupported_api_version when the AndOpen-API-Version header 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). The RateLimit-* response headers tell you the budget and reset time.