> ## Documentation Index
> Fetch the complete documentation index at: https://developer.voyado.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Point shop

The Engage Point Shop allows you to embed point shop offers directly into your web shop, native apps, or POS systems. You can fetch available point shop offers for a member, allow them to purchase an offer using their points, and track purchases.

The API ensures your customer experience stays in sync with the Engage backend, handling redemptions and point deductions seamlessly.

## How it works

When you create a point shop offer in Engage, two entities are generated:

<AccordionGroup>
  <Accordion title="Point shop item">
    This represents the shop-facing item, and includes:

    * Point cost
    * Availability window
    * Presentation data
  </Accordion>

  <Accordion title="Multichannel promotion">
    This is the actual reward the member receives after purchase (discount, ticket, pre-sale access). This includes:

    * Presentation data
  </Accordion>
</AccordionGroup>

<Tip>
  Presentation data (including title, description, image, link) is stored both for point shop item and for multichannel promotions.
</Tip>

From an integration perspective you will:

1. Fetch and display available point shop items
2. Let the member purchase a point shop offer
3. Retrieve and redeem the reward via Multichannel APIs

The relevant API endpoints are presented here.

## 1. Get point shop items

This endpoint fetches all point shop items that a specific member can purchase.

```http theme={null}
GET /api/v3/point-shop-items?contactId=[contactId]
```

<ResponseField name="contactId" type="GUID" required>
  Unique identifier of the contact.
</ResponseField>

<Accordion title="See response payload structure">
  ```json theme={null}
  [
    {
      "id": "GUID",
      "name": "string",
      "cost": 0,
      "pointDefinitionId": 0,
      "promotionId": "GUID",
      "validFrom": "2026-03-20T13:53:08.003Z",
      "validTo": "2026-03-20T13:53:08.003Z",
      "presentation": {
        "title": "string",
        "description": "string",
        "imageUrl": "string",
        "link": "string"
      }
    }
  ]
  ```

  <ResponseField name="cost" type="int">
    The points required for this item.
  </ResponseField>

  <ResponseField name="promotionId" type="GUID">
    The ID of the reward.
  </ResponseField>

  <Tip>
    Items are filtered based on:

    * Member eligibility
    * Point cost
    * Active time window (`validFrom`, `validTo`)
  </Tip>
</Accordion>

<Accordion title="See response errors">
  *400 Bad Request* is received if `contactId` is empty or not a valid GUID.
</Accordion>

## 2. Purchase point shop item

Use this endpoint to purchase a point shop item for a specific contact and assign the promotion purchased to them.

```http theme={null}
POST /api/v3/point-shop-items/purchase
```

The following payload is sent in the body:

```json Purchase item payload structure theme={null}
{
  "pointShopItemPurchaseId": "00000000-0000-0000-0000-000000000000",
  "pointShopItemId": "00000000-0000-0000-0000-000000000000",
  "contactId": "00000000-0000-0000-0000-000000000000"
}
```

<ResponseField name="contactId" type="GUID" required>
  Standard identifier for the contact.
</ResponseField>

<ResponseField name="pointShopItemPurchaseId" type="GUID" required>
  This, the unique ID for this purchase, is a value that you have to generate in your system for each purchase attempt. This endpoint is therefore *idempotent*, and this is enforced based on the combination of `pointShopItemPurchaseId` and `contactId`. See below for more.
</ResponseField>

<ResponseField name="pointShopItemId" type="GUID" required>
  This is the same value as the `id` returned in the *Get point shop items* endpoint above.
</ResponseField>

### Idempotency

Idempotency here is based on the combination of `pointShopItemPurchaseId` and `contactId`. In practice, if a request is retried using the same values of these two attributes, the API will just return the existing purchase instead of creating a new one. This ensures that:

* A purchase ID cannot be reused across different contacts
* No duplicate point deductions will occur
* Retries are safe in case of network issues

### Handling the request

When a POST has been made to this endpoint, the systems first validates that `pointShopItemPurchaseId` and `contactId` belong together, preventing duplicate purchases and cross-user reuse of purchase IDs. If validated, then:

* The purchase operation identified by `pointShopItemPurchaseId` is created or, if it already exists, reused
* Points are deducted from the contact
* The linked promotion is assigned to the contact

In the case of a request to the endpoint being repeated:

| Scenario                             | Result                                   |
| :----------------------------------- | :--------------------------------------- |
| Same `purchaseId` + same `contactId` | ✅ Returns existing purchase (idempotent) |
| Same `purchaseId` + new `contactId`  | ❌ Rejected (invalid request)             |
| New `purchaseId` + same `contactId`  | ✅ New purchase created                   |

<Accordion title="See response payload structure">
  ```json theme={null}
  {
    "id": "00000000-0000-0000-0000-000000000000",
    "pointShopItemId": "00000000-0000-0000-0000-000000000000",
    "pointAccountId": "00000000-0000-0000-0000-000000000000",
    "pointTransactionId": "00000000-0000-0000-0000-000000000000",
    "promotionAssignmentId": "00000000-0000-0000-0000-000000000000",
    "pointShopItemCost": 0,
    "status": "Processing",
    "purchasedOn": "2026-04-14T13:23:28.458Z"
  }
  ```
</Accordion>

See response errors:

<AccordionGroup>
  <Accordion title="400 Bad Request">
    The generic PromotionAssignmentError (HTTP 400) is split into specific error codes:

     - PromotionAssignmentContactNotFound (400) — the contact doesn't exist
     - PromotionAssignmentPromotionNotFound (400) — the linked promotion was removed
     - PromotionAssignmentPromotionExpired (400) — the linked promotion has ended
     - PromotionAssignmentError (400 → 500) — internal server error, safe to retry
  </Accordion>

  <Accordion title="404 Not Found">
    This can have one of the following error codes:

    * Item does not exist
  </Accordion>

  <Accordion title="404 Conflict">
    This can have one of the following error codes:

    * PointShopItemPurchaseProcessingTimedOut
    * PointShopItemPurchaseFailed
  </Accordion>

  <Accordion title="422 Unprocessable Entity">
    ```json theme={null}
    {
      "message": "string",
      "validationErrors": [
        {
          "validationErrorCode": "string",
          "field": "string",
          "errorDescription": "string"
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="500 Internal Server Error">
    * Unknown error
  </Accordion>
</AccordionGroup>

### Integration guidance

Clients should:

1. Generate a new `pointShopItemPurchaseId` for each new purchase
2. Reuse the same ID only when retrying the same request
3. Never reuse a purchase ID across different users (different `contactId` values)

## 3. Get point shop item purchases

Use this endpoint to fetch all purchases made by a specific contact.

```http theme={null}
GET /api/v3/point-shop-item-purchases?contactId=[contactId]
```

<ResponseField name="contactId" type="GUID" required>
  Standard identifier for the contact.
</ResponseField>

The response payload looks like this:

```json Point shop item purchases response theme={null}
[
  {
    "pointShopItemPurchaseId": "GUID",
    "pointShopItemId": "GUID",
    "promotionAssignmentId": "GUID",
    "pointShopItemCost": 0,
    "status": "Processing",
    "purchasedOn": "2026-03-20T13:41:01.840Z"
  }
]
```

### Uses for this endpoint

This data from this endpoint is useful for:

* Seeing the full purchase history
* Order tracking
* UI confirmation flows
* Understand the purchase state with`status` (for example `Processing`)

See response errors:

<AccordionGroup>
  <Accordion title="400 Bad Request">
    * InvalidContact
  </Accordion>

  <Accordion title="404 Not Found">
    * No purchases found or invalid reference
  </Accordion>
</AccordionGroup>

## Purchase flow

A point shop item purchase flow looks like this:

1. Call `GET /point-shop-items`
2. Display available offers
3. User selects an item
4. Call `POST /point-shop-items/purchase` (with `pointShopItemPurchaseId`)
5. Retry safely if needed using the same purchase ID
6. Optionally track via `GET /point-shop-item-purchases`
7. Fetch reward via the multichannel promotion API

## Multichannel promotion

While the point shop API handle this:

* Listing items
* Purchasing items

The actual reward purchased is handled by the multichannel promotion endpoints.

You must integrate with those endpoints in order to:

* Display reward details (title, description, barcode, expiry)
* Allow redemption (in-store or online)

See the multichannel promotion API documentation for how to retrieve, present, and validate assigned offers.

<Card title="Learn about the multichannel promotion API" href="/docs/loyalty/promotions" icon="https://mintcdn.com/voyado/Ns4bBcK3LNctK_Un/icons/developer-link.png?fit=max&auto=format&n=Ns4bBcK3LNctK_Un&q=85&s=fbd08f956358ab12f664a7158e1a1399" horizontal width="128" height="128" data-path="icons/developer-link.png" />
