# Update redemption instructions or voucher details

Change the redemption instructions, voucher code or customer details saved on one redemption order in your community.

- Endpoint: `PATCH https://api.returning.ai/v1/purchase-histories/{purchaseHistoryID}`
- Section: Store and rewards / Purchase history
- Authentication: `Authorization: Bearer <API_KEY>` (Community API key)
- Permission: `purchase-history` (Shown in the dashboard as "Purchase History")
- Retries: No Idempotency-Key; read the order before retrying
- Guide: hand-written
- Verified: live, 2026-09-27
- Last updated: 26 Sep 2026
- Web page: https://docs.returning.ai/api-reference/store-purchase-history/update-redemption-instructions-or-voucher-details

## When to use this

- You fulfil a reward by hand and need to give the trader a new voucher code or expiry date.
- The instructions a trader sees for one order need correcting, such as where to collect a prize.
- A trader asks you to fix the delivery address or an order-form answer on their order.

**Instead:** Use [Update redemption order status](https://docs.returning.ai/api-reference/rewards-and-redemptions/update-redemption-order-status.md) instead to move the order to another status or refund it.

**Two kinds of change:** Send instruction and voucher fields, or a `changes` object for customer details, never both in one request.

## Authentication

- Header: `Authorization: Bearer <API_KEY>`
- Permission: `purchase-history`

Send a Community API key with the `purchase-history` permission. The key decides the community, so you can only change orders in that community. Keep the key on your server.

## Behaviour

There are two kinds of change, and each request makes one of them.

- **Instructions and voucher.** Send `redemptionInstructions`, the pair `voucherCode` and `voucherExpiryDate`, or all three. New instructions apply to this order only; the product keeps its own. Setting instructions also refreshes the order's saved copy of the product (name, description, image and price) from the current product, and fails with `404` if the product was deleted. A new voucher code changes the voucher record itself, and must not match another voucher on the same product.
- **Customer details.** Send `changes` with `userInfo`, `customOptions` or both. Only values that differ from what is saved count. Each successful update adds an entry to the order's change history, and `meta.auditEventId` identifies it.

The order's status, price and coins don't change, and the trader isn't emailed or notified. The latest update wins if two arrive together.

## Request

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `purchaseHistoryID` | `string` | Yes | The order ID (`ORD...`) from `redemptionId` in the order lists. The order's internal `_id` also works. (Order ID in your community) |

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Authorization` | `string` | Yes | Community API key with `purchase-history`. (`Bearer <API_KEY>`) |
| `Content-Type` | `string` | Yes | Request body format. (`application/json`) |

### Body

Send a JSON object with at least one field. Fields this endpoint doesn't take are rejected, not ignored. Text values are trimmed. `voucherExpiryDate` is a date only, in `DD/MM/YYYY`; the voucher expires at the start of that day, midnight UTC, so send the day after if it should last through a date.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `redemptionInstructions` | `string` | No | New instructions shown to the trader for this order only. The product's own instructions don't change. Needs the product to still exist. (1-10,000 chars, not blank; trimmed) |
| `voucherCode` | `string` | No | New voucher code for this order. Send it with `voucherExpiryDate`. (1-512 chars, not blank; trimmed; unique within the product) |
| `voucherExpiryDate` | `string` | No | New voucher expiry date. Send it with `voucherCode`. Saved as the start of that day, midnight UTC. (`DD/MM/YYYY`, a real date) |
| `changes` | `object` | No | Customer details and order-form answers to change. Send at least one of `userInfo` or `customOptions`, and no other top-level field. |
| `changes.userInfo` | `object` | No | Customer details saved on the order. Each field is optional: `firstName`, `lastName`, `phonePrefix`, `phone`, `country`, `city`, `state`, `address`, `postalCode`. Only `state` can be blank. Email can't be changed. |
| `changes.customOptions` | `object[]` | No | Answers to the product's order form. Each entry names one saved field, by `name` or `_id`, never both. |
| `changes.customOptions.name` | `string` | No | The form field's name, matched ignoring capital letters. Use `_id` instead if two fields share a name. |
| `changes.customOptions._id` | `string` | No | The form field's ID, as saved on the order. |
| `changes.customOptions.value` | `string` | Yes | The new answer. Can't be blank for a required field, and must be a number for a number field. |

### Example request

```bash
curl --request PATCH \
  --url https://api.returning.ai/v1/purchase-histories/ORD20269268300418273 \
  --header 'Authorization: Bearer <API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "redemptionInstructions": "Your $25 credit is added to your live trading account within 2 business days.",
    "voucherCode": "TC25-8R5N-3C1Y",
    "voucherExpiryDate": "31/12/2026"
  }'
```

## Response

A `200` returns the whole order in `data`, including the voucher code the trader sees. Branch on the HTTP status and `meta.code`, never on `message`.

### Response fields

| Field | Type | Presence | Description |
| --- | --- | --- | --- |
| `meta` | `object` | always | Status details. |
| `meta.status` | `string` | always | Result of the request. (`success`) |
| `meta.statusCode` | `integer` | always | The HTTP status, repeated. |
| `meta.code` | `string` | always | Machine-readable result code. (`PURCHASE_HISTORY_UPDATED`) |
| `meta.auditEventId` | `string` | when you send changes | ID of the change-history entry this update created. Present only for `changes` updates. |
| `message` | `string` | always | Human-readable summary. It differs between the two kinds of change. Do not branch on it. |
| `data` | `object` | always | The whole order after the change. |
| `data._id` | `string` | - | Internal record ID of the order. |
| `data.purchaseID` | `string` | - | The order ID (`ORD...`). |
| `data.communityID` | `string` | - | Your community ID. |
| `data.userID` | `string` | - | The trader's internal record ID. This is not the platform user ID. |
| `data.status` | `string` | - | The order's current status name. This call doesn't change it. |
| `data.purchaseStatusID` | `string` | - | ID of the order's current status. |
| `data.price` | `number` | - | Coins the trader paid for the order. |
| `data.quantity` | `number` | - | Units in the order. |
| `data.voucherCode` | `string` | - | The voucher code the trader sees for this order. |
| `data.voucherExpiryDate` | `string` | - | When that voucher expires, ISO 8601 UTC. (Date-time) |
| `data.voucher` | `object` | - | The voucher record assigned to the order. A voucher update changes it too. |
| `data.voucher._id` | `string` | - | Voucher ID. |
| `data.voucher.code` | `string` | - | The voucher's code. |
| `data.voucher.status` | `string` | - | Voucher status. (`active`, `redeemed` or `expired`) |
| `data.voucher.expiryDate` | `string` | - | When the voucher expires, ISO 8601 UTC. (Date-time) |
| `data.product` | `object` | - | The product as saved on the order, including the order's `redemptionInstructions`. `null` when the product no longer exists and the order has no saved copy. |
| `data.product._id` | `string` | - | Product ID. |
| `data.product.name` | `string` | - | Product name. |
| `data.product.redemptionInstructions` | `string` | - | The instructions the trader sees for this order. |
| `data.userInfo` | `object` | - | Customer details saved on the order, such as `username`, `name`, `email` and any address fields. |
| `data.customOptions` | `object[]` | - | The order-form answers, each with its `_id`, `name` and `value`. Empty when the product has no form. |
| `data.purchasedDate` | `string` | - | When the trader redeemed, ISO 8601 UTC. |
| `data.refundedAt` | `string` | - | When the order was refunded, ISO 8601 UTC. Present only on refunded orders. |
| `data.latestStatusHistory` | `object` | - | The order's most recent status change, with `oldStatusName`, `newStatusName`, `remark` and `createdAt`. Present once the status has changed. |
| `data.createdAt` | `string` | - | When the order was created, ISO 8601 UTC. (Date-time) |
| `data.updatedAt` | `string` | - | When the order last changed, ISO 8601 UTC. (Date-time) |

### Example response (200)

```json
{
  "meta": {
    "status": "success",
    "statusCode": 200,
    "code": "PURCHASE_HISTORY_UPDATED"
  },
  "message": "Update purchase history success.",
  "data": {
    "_id": "66f000000000000000000401",
    "purchaseID": "ORD20269268300418273",
    "communityID": "66f000000000000000000010",
    "userID": "<userObjectId>",
    "status": "Completed",
    "purchaseStatusID": "66f000000000000000000405",
    "price": 500,
    "quantity": 1,
    "voucherCode": "TC25-8R5N-3C1Y",
    "voucherExpiryDate": "2026-12-31T00:00:00.000Z",
    "voucher": {
      "_id": "66f000000000000000000502",
      "code": "TC25-8R5N-3C1Y",
      "status": "redeemed",
      "expiryDate": "2026-12-31T00:00:00.000Z"
    },
    "product": {
      "_id": "66f000000000000000000101",
      "name": "$25 Trading Credit",
      "description": "<p>Redeem 500 coins for a $25 trading credit on your live account.</p>",
      "image": "https://cdn.example.com/store/trading-credit-25.png",
      "price": 500,
      "type": "voucher",
      "redemptionInstructions": "Your $25 credit is added to your live trading account within 2 business days."
    },
    "userInfo": {
      "username": "sample_trader",
      "name": "Sample Trader",
      "email": "trader@example.com"
    },
    "customOptions": [],
    "purchasedDate": "2026-09-26T08:30:00.000Z",
    "latestStatusHistory": {
      "oldStatusName": "New Purchase",
      "newStatusName": "Completed",
      "remark": "Updated by API",
      "createdAt": "2026-09-26T09:15:00.000Z"
    },
    "createdAt": "2026-09-26T08:30:00.000Z",
    "updatedAt": "2026-09-27T10:15:00.000Z"
  }
}
```

## Errors

Every error carries its code in `meta.code`, with the reason in `detail`, except a `400` for a body of the wrong shape, which has no code and puts the reason in `detail.body`.

### Fix the request

| Status | Code | What to do |
| --- | --- | --- |
| 400 | - | The body's shape is wrong, and there is no `meta.code`. `detail.body` says which: an empty body, a field this endpoint doesn't take, both kinds of change in one request, or a form answer with both or neither of `name` and `_id`. Nothing was changed. |
| 400 | `VALIDATION_FAILED` | A value broke a rule, and nothing was changed. `detail` says which: for example a voucher code sent without its expiry date, a date not in `DD/MM/YYYY`, a blank value, an unknown form field name, or `No purchase history details changed` when every value already matches. |
| 401 | `AUTH_API_KEY_REQUIRED` | No key was sent. Send `Authorization: Bearer <API_KEY>`. |
| 401 | `AUTH_API_KEY_INVALID` | The key is unknown, expired or malformed. Use a current Community API key. |
| 403 | `AUTH_PERMISSION_REQUIRED` | The key lacks `purchase-history`. Add the permission in Settings > Integration > API Keys. |

### Fix the data

| Status | Code | What to do |
| --- | --- | --- |
| 404 | `STORE_RESOURCE_NOT_FOUND` | Nothing changed. `detail` says which: `Purchase history not found` (check the `ORD...` order ID from the order lists), or `Product not found for purchaseHistory` (the product was deleted, so instructions can't be set; voucher and customer changes still work). |
| 409 | `STORE_RESOURCE_CONFLICT` | Another voucher on the same product already has this code, and nothing was changed. Use a different code. |

### Retry with backoff

| Status | Code | What to do |
| --- | --- | --- |
| 502 | `STORE_DEPENDENCY_UNAVAILABLE` | The outcome is unknown. Read the order, then send the update again if it didn't apply. |
| 500 | `INTERNAL_ERROR` | The outcome is unknown. Read the order, then send the update again if it didn't apply. |
| 401 | `AUTH_API_KEY_VALIDATION_FAILED` | The key could not be checked just now, and nothing was changed. Retry with backoff. |

**Retries:** This endpoint doesn't accept an `Idempotency-Key`. Repeating an instructions or voucher update leaves the order the same. Repeating a customer-details update that already applied returns `400` with `No purchase history details changed`, which you can treat as done. After a timeout, a `500` or a `502`, read the order back before you retry. Over the [rate limit](https://docs.returning.ai/how-it-works.md#rate-limits), requests return `429`; wait for the window to reset, then retry.

## Next step

- [Get redemption transaction detail](https://docs.returning.ai/api-reference/store-redemption-transaction/get-redemption-transaction-detail.md): `GET /v2/redemption-transactions/{redemptionID}`. Read the order back to confirm what the trader sees.
- [Mark it fulfilled with Update redemption order status](https://docs.returning.ai/api-reference/rewards-and-redemptions/update-redemption-order-status.md): `PUT /v1/redemption-transactions/status`.
