Cancel a gift request
POST /gift_requests/{id}/cancel
Description
Cancels a gift request the recipient hasn't redeemed yet, so they can no longer
redeem it. Their redemption_url then lands on an expired-gift page, and
&Open sends the gift_request_cancelled webhook. The response is the cancelled
gift request, in the same shape Get a gift request returns.
When to use
Reach for this endpoint when a gift should no longer go out: the recipient has
left the company, the deal fell through, or the gift was sent to the wrong
person. Only a gift request whose status is submitted can be cancelled.
Read the gift request's cancellable field first, with
Get a gift request or List gift requests. It's
true when the gift request's current state allows a cancel, so you can hide
or disable a cancel action for one that has moved on. Still handle the errors
below: the state can change between your read and the cancel, and a token that
can read a gift request isn't always allowed to cancel it.
An admin or a system token can cancel any gift request in your account, as can
an OAuth token from an account-level partner grant. Any other user, or a
user-level grant made by a non-admin, can cancel only the gift requests they
sent; one outside that scope returns 404, the same as one that doesn't exist.
Parameters
Path
id (string · uuid · required) is the gift request to cancel. An id that doesn't
match a gift request you can see returns 404.
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.
Idempotency-Key (string · optional) keys a retry explicitly, but you don't
need one here. The request takes no body, so a retry is already matched on the
gift request id, exactly as you sent it in the path, for 24 hours. A repeat
within that window returns the original 200 response, with an
Idempotent-Replayed: true header, and changes nothing. That means a retry
after a timeout gets the cancelled gift request back, not a 422.
Send a key when you want to name the retry yourself. Reusing a key within 24
hours for a different gift request returns a 422 with
idempotency_key_mismatch.
Only a successful cancel is stored. A refusal (404, 403, or
not_cancellable) is never replayed: a retry is checked again from scratch.
Worked examples
A cancel is a POST to the gift request's cancel path, with no request body.
The JavaScript, Python, and Ruby versions send the cancel and read the new
status off the response; swap in the id you want to cancel.
- cURL
- JavaScript
- Python
- Ruby
curl -X POST https://api.andopen.co/gift_requests/4fb4cb3f-9666-43b5-8884-7f5194483d1a/cancel \
-H "Authorization: Bearer <api_key>" \
-H "AndOpen-API-Version: 2026-05"
const response = await fetch(
"https://api.andopen.co/gift_requests/4fb4cb3f-9666-43b5-8884-7f5194483d1a/cancel",
{
method: "POST",
headers: {
Authorization: "Bearer <api_key>",
"AndOpen-API-Version": "2026-05",
},
},
);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const giftRequest = await response.json();
console.log(giftRequest.status, giftRequest.cancellable);
import requests
response = requests.post(
"https://api.andopen.co/gift_requests/4fb4cb3f-9666-43b5-8884-7f5194483d1a/cancel",
headers={
"Authorization": "Bearer <api_key>",
"AndOpen-API-Version": "2026-05",
},
)
response.raise_for_status()
gift_request = response.json()
print(gift_request["status"], gift_request["cancellable"])
require "net/http"
require "json"
uri = URI("https://api.andopen.co/gift_requests/4fb4cb3f-9666-43b5-8884-7f5194483d1a/cancel")
request = Net::HTTP::Post.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
gift_request = JSON.parse(response.body)
puts "#{gift_request['status']} #{gift_request['cancellable']}"
Response shape
A successful call returns 200 OK with the cancelled gift request as a flat
top-level JSON object: the same shape Get a gift request returns,
with status now cancelled.
| Field | Description |
|---|---|
reference (string · required) | A human-readable handle for the gift request, unique within your account and stable for its lifetime. Show it to your own users and quote it to support; the API itself takes the id. |
status (string · required) | The public-facing status, one of submitted, redeemed, dispatched, delivered, cancelled. After a successful cancel it's cancelled. |
cancellable (boolean · required) | Whether the gift request's current state allows a cancel. Always false in this response, because the gift request is already cancelled. |
campaign (object · required · nullable) | The campaign this request belongs to, embedded as an { id, name, web_url } summary. null for an ad-hoc send that belongs to no campaign. |
redemption_url (string · uri · required) | The recipient's link. It's still returned, but now opens an expired-gift page. |
web_url (string · uri · required) | The gift request's page in the &Open web app, on your organization's own domain. This is the sender-facing view and needs a signed-in session. |
recipient (object · required) | The recipient, returned inline. |
sender (object · required) | The user recorded as responsible for the send, embedded as an { id, name, email } summary. |
offering (object · required) | What was put in front of the recipient: the items on offer, whether they receive all of them or choose one, and whether they see them at redemption. |
line_items (array · required · nullable) | null, because a gift request is only cancellable before the recipient chooses, so its items were never locked. |
lifecycle_events (object · required) | When each step happened before the cancel, such as when the invitation email was sent and when the recipient first opened it. redeemed_at, dispatched_at, and delivered_at are null. |
The response also carries invitation_method, invitation_subject, and
notecard_message, as described on Get a gift request.
{
"id": "4fb4cb3f-9666-43b5-8884-7f5194483d1a",
"reference": "ACME-M5RD6J8",
"status": "cancelled",
"cancellable": false,
"invitation_method": "email_invitation",
"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": 0,
"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": null,
"offering": {
"selection_mode": "one",
"visible_at_redemption": true,
"line_items": [
{
"id": "f6a7b8c9-d0e1-2345-6789-0abcdef01234",
"sku": "NOTEBOOK-A5",
"variant_id": "a7b8c9d0-e1f2-3456-789a-bcdef0123456",
"name": "Leather Notebook",
"quantity": 1,
"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"
}
}
]
}
]
},
"line_items": null,
"email_message": {
"subject": "A small thank-you",
"body": "Thanks for being a great customer!"
},
"invitation_subject": "A small thank-you",
"notecard_message": null,
"lifecycle_events": {
"invitation_email_sent_at": "2026-04-16T10:31:00.000Z",
"invitation_first_opened_at": "2026-04-16T18:40:00.000Z",
"invitation_last_opened_at": "2026-04-17T08:12:00.000Z",
"reminder_sent_at": null,
"redeemed_at": null,
"dispatched_at": null,
"delivered_at": null
},
"created_at": "2026-04-16T10:30:00.000Z",
"updated_at": "2026-04-18T09:15:00.000Z"
}
Per-field notes
cancellableistrueonly whilestatusissubmitted. Read it before you offer a cancel action, rather than comparingstatusyourself.- A cancel is final. There's no endpoint to restore a cancelled gift request, so create a new one with Create a gift request if you change your mind.
- The
gift_request_cancelledwebhook fires for this cancel. The same event also fires when a gift request expires, so don't treat every delivery as a cancel you made. See Webhooks.
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. |
| 409 | An identical request under the same idempotency key is still being processed. Retry after the interval in the Retry-After header. |
| 422 | Validation error: the request was parseable but contains invalid data. All validation errors are returned at once. |
| 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 failure you'll handle most is a gift request that has moved on:
422 validation_error:not_cancellable: the gift request is already redeemed, dispatched, delivered, or cancelled. Themessagenames its current status. This isn't a field problem, so there's noparam.idempotency_key_mismatch: theIdempotency-Keyyou sent was used in the last 24 hours for a different gift request. Use a fresh key.
404 not_found_error:not_found. No gift request with thatid, or one you're not allowed to see.409 api_error:request_in_progress. An identical cancel is still being processed, so this one was neither run nor rejected. Wait the number of seconds in theRetry-Afterheader and send it again.401 authentication_error: the token is missing, unrecognized, or expired (unauthorized). A token is only valid in the region it was issued for, so one presented to the wrong region fails the same way.403 authorization_error: the token authenticated but isn't allowed to cancel gift requests (forbidden).400 api_error:unsupported_api_versionwhen theAndOpen-API-Versionheader is missing or unsupported (returned before authentication), ormalformed_requestfor anIdempotency-Keythat is empty or longer than 255 characters.429 rate_limit_error: you've exceeded the rate limit; back off using theRetry-Afterheader.500/503 api_error: a fault on our side; rare, and safe to retry after a short delay (honorRetry-Afteron a503).
A gift request the recipient has already redeemed returns a single error with
no param:
{
"errors": [
{
"type": "validation_error",
"code": "not_cancellable",
"message": "Gift request is already redeemed"
}
]
}