Get a campaign
GET /campaigns/:id
Description
Retrieves a single campaign by its id. A campaign is the gift collection
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, with 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 (string · uuid · required) is the campaign to retrieve. An id that matches no
campaign in your account returns 404, not an empty body.
Headers
AndOpen-API-Version (string · required) is 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
- JavaScript
- Python
- Ruby
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",
},
},
);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
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
response.value # raises on a non-2xx response
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 gift-collection associations are returned inline rather than as
references you have to fetch separately.
| Field | Description |
|---|---|
status (string · required) | The public-facing status, one of available, archived. Internal hidden states are collapsed to available for API consumers. |
description (string · required · nullable) | The long-form description. Always present as a key, but null when none has been set. |
archived_at (string · date-time · required · nullable) | When the campaign was archived, or null if it has not been. |
web_url (string · uri · required) | The campaign's page in the &Open web app, on your organization's own domain. It needs a signed-in session, so it's a link for your own users. |
offering (object · required) | How the campaign puts its gifts to a recipient. offering.selection_mode (string · required) is one of all, one, and offering.visible_at_redemption (boolean · required) says whether the recipient sees the gifts when they redeem. |
owner (object · required · nullable) | The user who owns the campaign, embedded as an { id, name, email } summary. Read their team and budget with Get a user. |
supported_country_codes (array · required) | The ISO 3166-1 alpha-2 countries the campaign can ship to. Check a destination against it before you create a gift request. |
shipping_from (array · required) | The countries it ships from: its warehouse's country, or one entry per On-Demand vendor. |
default_email_message (object · required) | The campaign's default invitation copy. Its subject and body are each null until the campaign sets them, and a send's own email_message overrides both. |
gift_request_statistics (object · required) | A roll-up of the campaign's sends by public status, as total, redeemed, dispatched, and delivered counts. |
products (array · required) | The products on offer, returned inline. products.variants (array · required) holds each product's variants, and products.variants.stock_levels (array · required) holds each variant's stock levels down to the warehouse. Products also carry an images array, and variants a price. |
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Q2 Customer Appreciation",
"description": "Send a thank-you gift to our top accounts.",
"status": "available",
"web_url": "https://acme.andopen.co/sender/gift-history/campaigns/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"default_email_message": {
"subject": "A gift for you",
"body": "Thanks for being a valued customer. Enjoy your gift!"
},
"owner": {
"id": "9f8e7d6c-5b4a-3210-fedc-ba9876543210",
"name": "Dana Okafor",
"email": "dana@acme.example"
},
"supported_country_codes": [
"IE",
"GB",
"US"
],
"shipping_from": [
"IE"
],
"gift_request_statistics": {
"total": 120,
"redeemed": 84,
"dispatched": 12,
"delivered": 70
},
"offering": {
"selection_mode": "one",
"visible_at_redemption": true
},
"archived_at": null,
"products": [
{
"id": "b2c3d4e5-f6a7-8901-bcde-f01234567890",
"name": "Leather Notebook",
"enabled": true,
"images": [
{
"name": "leather-notebook.jpg",
"url": "https://files.andopen.co/rndd63mwgj94j2vf3v2ngnye0hkn",
"urls": {
"thumbnail": "https://files.andopen.co/vw2mvwevyqxh98l7esf67nvunr4h",
"large": "https://files.andopen.co/mz4rc8kx1nvq7j93wtf5bd2hs0yl",
"large_wide": "https://files.andopen.co/q7pz3n8htv2ke0d5rjw94flxs6cb"
}
}
],
"variants": [
{
"id": "c3d4e5f6-a7b8-9012-cdef-012345678901",
"name": "Medium / Black",
"sku": "NB-LTHR-MED-BLK",
"enabled": true,
"position": 1,
"price": {
"amount": 1500,
"currency": "EUR"
},
"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 gift collection is filtered to the campaign's warehouse. When a campaign
has a fulfillment warehouse, products, variants, and stock levels with no
stock at that warehouse are omitted. Each variant then exposes a single
stock level scoped to that warehouse. Campaigns without a warehouse (for
example voucher- or charity-only campaigns) return the gift collection
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".variants[].priceis a money object: an integeramountin the smallest unit of thecurrency, so{ "amount": 1500, "currency": "EUR" }is €15.00. It is the gift's value on this campaign, and it is what a send draws against the sender's budget.products[].imagesand each line item'simagescarry three pre-generated sizes underurls, alongside the full-resolutionurl. Each size is a separate file, so read the one you want fromurlsand don't try to derive it fromurl.supported_country_codesandshipping_fromare empty arrays, nevernull, when the campaign has no fulfillment warehouse or no resolvable origin. An emptysupported_country_codesmeans the campaign can't tell you where it ships, so validate the address on the send instead.- The campaign's
offeringis its configured mode, and a send from it can still differ. Voucher and cause gifts always let the recipient choose one, whatever the campaign says. A gift request's ownofferingis what actually happened on that send, so read it from Get a gift request when the answer matters. gift_request_statisticscounts every send on the campaign, archived campaigns included, and reads0across the board for a campaign with no sends. Itsdeliveredcount includes partially fulfilled requests, matching thedeliveredstatus on a gift request.- Server-set fields are read-only.
id,status,archived_at,gift_request_statistics, 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 | Authorized 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 account 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 account (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 (unauthorized).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.