List gift requests
GET /gift_requests
Description
Lists the gift requests in your account, newest first, as a cursor-paginated collection. Each item carries the full gift-request shape: the same object Get a gift request returns, with the recipient, sender, offering, and line items embedded inline.
What you get back depends on the token you send. An admin or service token sees every gift request in the account. A non-admin user token sees only the gift requests that user sent.
When to use
Use this endpoint to work across many gift requests at once: reconciling a batch
you sent, building a dashboard, or finding one by its reference when that is
all a colleague gave you. Filters combine, so you can narrow to one campaign, one
sender, or one week without paging the whole account.
When you already have a gift request's id, fetch it directly with
Get a gift request. To create one, use
Create a gift request.
Parameters
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.
Query parameters
Every one of these is optional, and sent together they page, sort, and filter the same collection.
Paging
limit (integer · optional) is how many gift requests to return per page.
Defaults to 25, which is also the maximum: each item carries its full nested
shape, 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 (string · uuid · optional) is the keyset pagination cursor. Pagination is
cursor-based, not page-numbered: to fetch the next page, pass the id of the
last gift request 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.
Ordering
sort (string · optional) is the field to order by, - prefix for
descending. Defaults to -created_at (newest first); created_at,
updated_at, and -updated_at are also accepted. Any other field is rejected
with a 400.
Filtering
Filters combine with AND, so each one you add narrows the page further. Two kinds behave differently when nothing matches, and the difference is worth knowing before you debug an empty page:
- An exact-match filter (
status,reference,campaign,sender) given an unrecognized value matches nothing and returns an empty page. - A substring filter (
campaign.name,sender.name,sender.email,recipient.email,recipient.name,recipient.first_name,recipient.last_name) is case-insensitive and matches anywhere in the value.
status (string · optional) filters by public status. Accepts a single value
or a comma-separated list, matching any of them.
reference (string · optional) matches a gift request's human-readable
reference exactly. A reference is unique within your account, so this returns at
most one item.
campaign (string · optional) filters by campaign id. Ad-hoc sends belong
to no campaign, so they never match this filter.
campaign.name (string · optional) matches against the campaign's name
instead.
sender (string · optional) filters by the sending user's id. The literal
me resolves to the authenticated user, which needs user authentication: a
service token has no connecting user and gets a 400.
sender.name (string · optional) and sender.email (string · optional)
match against the sender's name and email.
recipient.email (string · optional),
recipient.name (string · optional),
recipient.first_name (string · optional), and
recipient.last_name (string · optional) match against the gift recipient.
Because recipient.email is a substring match, a bare domain such as
@example.com selects every recipient on it.
created_at (object · optional) filters by creation time with bracketed range
operators: created_at[gte], created_at[lte], created_at[gt], and
created_at[lt]. Values are ISO 8601 timestamps, and several operators combine
into an interval, so
created_at[gte]=2026-01-01T00:00:00.000Z&created_at[lt]=2026-04-01T00:00:00.000Z
is the first quarter. An unknown operator or a non-ISO-8601 value is rejected
with a 400.
Worked examples
The cURL version is the raw HTTP call; the JavaScript, Python, and Ruby versions are idiomatic equivalents. Each asks for the redeemed gift requests on one campaign.
- cURL
- JavaScript
- Python
- Ruby
curl -X GET "https://api.andopen.co/gift_requests?campaign=a1b2c3d4-e5f6-7890-abcd-ef1234567890&status=redeemed" \
-H "Authorization: Bearer <api_key>" \
-H "AndOpen-API-Version: 2026-05"
const url = new URL("https://api.andopen.co/gift_requests");
url.searchParams.set("campaign", "a1b2c3d4-e5f6-7890-abcd-ef1234567890");
url.searchParams.set("status", "redeemed");
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();
for (const giftRequest of page.data) {
console.log(giftRequest.reference, giftRequest.status, giftRequest.recipient.email);
}
import requests
response = requests.get(
"https://api.andopen.co/gift_requests",
headers={
"Authorization": "Bearer <api_key>",
"AndOpen-API-Version": "2026-05",
},
params={
"campaign": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "redeemed",
},
)
response.raise_for_status()
page = response.json()
for gift_request in page["data"]:
print(gift_request["reference"], gift_request["status"], gift_request["recipient"]["email"])
require "net/http"
require "json"
uri = URI("https://api.andopen.co/gift_requests")
uri.query = URI.encode_www_form(
campaign: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
status: "redeemed"
)
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
page = JSON.parse(response.body)
page["data"].each do |gift_request|
puts "#{gift_request['reference']} #{gift_request['status']} #{gift_request['recipient']['email']}"
end
Paging through a filtered set
To walk every match, keep fetching while has_more is true, passing the last
gift request's id as the after cursor each time. Hold the filters steady
across the loop; changing one mid-walk shifts the collection under the cursor.
The cURL version shows a single follow-up request; the others loop until the
last page.
- cURL
- JavaScript
- Python
- Ruby
curl -X GET "https://api.andopen.co/gift_requests?created_at%5Bgte%5D=2026-01-01T00:00:00.000Z&after=4fb4cb3f-9666-43b5-8884-7f5194483d1a" \
-H "Authorization: Bearer <api_key>" \
-H "AndOpen-API-Version: 2026-05"
async function listGiftRequestsSince(isoTimestamp) {
const giftRequests = [];
let after;
do {
const url = new URL("https://api.andopen.co/gift_requests");
url.searchParams.set("created_at[gte]", isoTimestamp);
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();
giftRequests.push(...page.data);
const lastId = page.data.at(-1)?.id;
after = page.has_more && lastId ? lastId : undefined;
} while (after);
return giftRequests;
}
import requests
def list_gift_requests_since(iso_timestamp):
gift_requests = []
after = None
while True:
params = {"created_at[gte]": iso_timestamp}
if after:
params["after"] = after
response = requests.get(
"https://api.andopen.co/gift_requests",
headers={
"Authorization": "Bearer <api_key>",
"AndOpen-API-Version": "2026-05",
},
params=params,
)
response.raise_for_status()
page = response.json()
gift_requests.extend(page["data"])
if not page["has_more"] or not page["data"]:
return gift_requests
after = page["data"][-1]["id"]
require "net/http"
require "json"
def list_gift_requests_since(iso_timestamp)
gift_requests = []
after = nil
loop do
query = { "created_at[gte]" => iso_timestamp }
query["after"] = after if after
uri = URI("https://api.andopen.co/gift_requests")
uri.query = URI.encode_www_form(query)
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
page = JSON.parse(response.body)
gift_requests.concat(page["data"])
break gift_requests unless page["has_more"] && page["data"].any?
after = page["data"].last["id"]
end
end
Response shape
A successful call returns 200 OK with a page object: a data array of gift
requests plus a boolean cursor flag. Paging is driven by has_more and the
after cursor.
| Field | Description |
|---|---|
data (array · required) | The gift requests in this page. Each entry is a full gift request, identical to what Get a gift request returns; see that page for the per-field detail. |
has_more (boolean · required) | Whether more gift requests exist beyond this page. When true, fetch the next page by passing the last item's id as the after cursor; when false, you've reached the end. |
data.status (string · required) | Each item's status, one of submitted, redeemed, dispatched, delivered, cancelled. Every item also carries its reference, its campaign summary, and its sender summary, so you can label a row without a second call. |
{
"data": [
{
"id": "4fb4cb3f-9666-43b5-8884-7f5194483d1a",
"reference": "ACME-M5RD6J8",
"status": "redeemed",
"campaign": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Q4 Customer Appreciation",
"web_url": "https://acme.andopen.co/sender/gift-history/campaigns/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
},
"redemption_url": "https://gift.andopen.co/r/abc123",
"web_url": "https://acme.andopen.co/sender/gift-history/4fb4cb3f-9666-43b5-8884-7f5194483d1a",
"recipient": {
"id": "b2c3d4e5-f6a7-8901-bcde-f01234567890",
"thank_you_count": 1,
"first_name": "Alice",
"last_name": "Smith",
"name": "Alice Smith",
"email": "alice@example.com"
},
"sender": {
"id": "9f8e7d6c-5b4a-3210-fedc-ba9876543210",
"name": "Dana Okafor",
"email": "dana@acme.example"
},
"shipping_address": {
"id": "e5f6a7b8-c9d0-1234-5678-90abcdef0123",
"first_name": "Alice",
"last_name": "Smith",
"address1": "12 Merrion Square",
"address2": "Apt 4B",
"city": "Dublin",
"region": "Leinster",
"postal_code": "D02 XY45",
"country_code": "IE",
"phone": "+353871234567"
},
"offering": {
"selection_mode": "all",
"visible_at_redemption": false,
"line_items": null
},
"line_items": [
{
"id": "d4e5f6a7-b8c9-0123-4567-890abcdef012",
"sku": "HOODIE-L-BLK",
"variant_id": "c3d4e5f6-a7b8-9012-cdef-012345678901",
"name": "Hoodie: Large / Black",
"quantity": 1,
"images": [
{
"name": "hoodie-black.jpg",
"url": "https://files.andopen.co/8k2mfp4qz7x9v1c3b5n0jhtl",
"urls": {
"thumbnail": "https://files.andopen.co/p3wq7m2zx8k4n6v0bjhtldsf",
"large": "https://files.andopen.co/r9td5c1yfw3jq7n2vk8mzbxh",
"large_wide": "https://files.andopen.co/h4nj8sv2qd6kz0wt3mfy9lpc"
}
}
]
}
],
"email_message": {
"subject": "A small thank-you",
"body": "Thanks for being a great customer!"
},
"created_at": "2026-04-16T10:30:00.000Z",
"updated_at": "2026-04-16T10:30:00.000Z"
}
],
"has_more": false
}
Per-field notes
- Every item is the full gift request. The list does not return a trimmed
summary; each entry includes the same recipient, sender, offering, and line
items as the single-gift-request 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.- Exact-match filtering is forgiving. An unknown value for
status,reference,campaign, orsenderyields an empty page, not a400, so an emptydataarray can mean either "no matches" or "filter typo". data[].campaignisnullon an ad-hoc send. Those items are reachable only by leavingcampaignoff the request, so a campaign-filtered sweep never accounts for 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. |
| 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:
400 validation_error: a query parameter was rejected, each naming the offending one inparam. Anaftercursor referencing no existing record isinvalid_reference. Alimitthat is not a positive integer, an unsupportedsortfield, a malformedcreated_atoperator or timestamp, andsender=meunder service authentication are allinvalid_format.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 list gift requests (forbidden).429 rate_limit_error: too many requests; back off and retry (rate_limited). TheRateLimit-*response headers tell you the budget and reset time.