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

# Third Party Checkout Provider Integration Guide

> Step-by-step guide for third party checkout providers to integrate Yuko Loyalty into the checkout experience.

<Warning>
  IMPORTANT: This guide is only applicable for checkout providers in Shopify platform
</Warning>

## Overview

When a customer reaches your checkout, this integration lets you show them three things from Yuko — in real time:

| What the customer sees / does                             | API endpoint                        |
| --------------------------------------------------------- | ----------------------------------- |
| Their current points balance                              | `GET /customers/rewards/redeemable` |
| Rewards available to redeem right now                     | `GET /customers/rewards/redeemable` |
| Spend their points to get a discount code or store credit | `POST /customers/rewards/redeem`    |
| Points they will earn on this purchase                    | `POST /points/estimate`             |

Three API calls. One seamless checkout experience.

***

## How It Works

```text theme={null}
Customer reaches checkout
        │
        ▼
Your checkout identifies the customer
(Shopify Customer ID is required for Endpoints 1 & 3)
        │
        ├─────────────────────────────────────────────┐
        ▼                                             ▼
  API Call #1                                  API Call #2
  GET /customers/rewards/redeemable            POST /points/estimate
  Requires Shopify Customer ID                 Public — no customer ID needed
  Only for logged-in customers                 Works for guests too
        │                                             │
        ▼                                             ▼
  Returns:                                     Returns:
  · Points balance                             · Points earned on this cart
  · Rewards available to redeem
  · Unused coupon codes already held
        │                                             │
        └────────────────┬────────────────────────────┘
                         ▼
          Your checkout UI renders:
          "You have 650 points"
          "Redeem: $5 off (costs 500 pts)"   ← customer clicks this
          "Unused coupon: YUKO-TVJ-BL1"
          "You'll earn 120 points on this order"
                         │
          Customer clicks "Redeem" on a reward
                         │
                         ▼
                   API Call #3
                   POST /customers/rewards/redeem
                   Sends: customer_id + reward_id
                         │
                         ▼
                   Returns:
                   · New discount code (e.g. YUKO-ABC-123)
                   · Points deducted
                   · Expiry date
                         │
                         ▼
          Your checkout applies the discount code
          Customer completes purchase
```

<Note>
  Both API calls must be made from your **backend server** — never directly from the customer's browser. See [Security Rules](#security-rules) for details.
</Note>

***

## Before You Start

### What the merchant needs to do

The merchant must have Yuko installed on their Shopify store. Once installed:

<Steps>
  <Step title="Open Shopify Admin">
    Go to the merchant's **Shopify Admin Dashboard**.
  </Step>

  <Step title="Open Yuko">
    Click **Apps → Yuko Loyalty**.
  </Step>

  <Step title="Go to API settings">
    Navigate to **Account Settings → Integrations → REST API**.
  </Step>

  <Step title="Create an API token">
    Create a new token and enable the `rewards:read` scope.
  </Step>

  <Step title="Share the API key">
    Copy the API key and share it securely with your team. It looks like this:

    ```text theme={null}
    py_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    ```
  </Step>
</Steps>

<Warning>
  Each merchant generates their own API key. If you are integrating across multiple merchants, each one will have a different key.
</Warning>

### What your team needs

* The customer's **Shopify Customer ID** at checkout time — required for Endpoint 1, only available when the customer is logged in
* **Shopify Product IDs and Variant IDs** for every item in the cart — required for Endpoint 2

***

## Authentication

Yuko uses **Bearer Token Authentication**. Every API request must include the merchant's API key in the `Authorization` header.

```text theme={null}
Authorization: Bearer py_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

This header is required on **every single request** — no exceptions.

<Tip>
  Full authentication details are in the [Authentication reference](/authentication).
</Tip>

### Base URL

All Yuko API requests start with:

```text theme={null}
https://api.yukoapp.com/api/v1/public
```

### General rules

* Always use **HTTPS** — plain HTTP requests are rejected
* Always set `Content-Type: application/json` on POST requests
* All responses are JSON
* Successful responses wrap data inside a `"data"` key
* Error responses include a `"message"` key

See the [API Introduction](/introduction) for a full overview of request and response formats.

***

## Endpoint 1 — Get Points Balance & Redeemable Rewards

This is the primary endpoint for your checkout loyalty panel.

**One call returns everything:**

* The customer's current points balance
* All rewards they can redeem (with a flag showing if they have enough points for each)
* Coupon codes they've already redeemed but haven't applied yet

<Note>
  `/customers/rewards/redeemable` is a single endpoint that covers both the points balance **and** the full rewards list. You do not need a separate "get balance" call.
</Note>

Full API reference → [`GET /customers/rewards/redeemable`](/get-redeemable-rewards)

### Request

```text theme={null}
GET https://api.yukoapp.com/api/v1/public/customers/rewards/redeemable
```

### Query parameter

<ParamField query="customer_id" type="string" required>
  The Shopify Customer ID of the logged-in customer. This is mandatory — do not call this endpoint for guest checkouts.

  Example: `9049402769586`
</ParamField>

### Required token scope

Your API token must have `rewards:read` enabled. The merchant configures this in **Shopify Admin → Apps → Yuko Loyalty → Account Settings → Integrations → REST API**.

### Example request

```bash theme={null}
curl --request GET \
  --url "https://api.yukoapp.com/api/v1/public/customers/rewards/redeemable?customer_id=9049402769586" \
  --header "Authorization: Bearer py_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

### Example response (200)

```json theme={null}
{
  "data": {
    "points_balance": 650,
    "rewards": [
      {
        "id": "90424fa7-e8a9-4ef4-b43a-72c9b2028db5",
        "name": "$5 off your next order",
        "description": "Redeem 500 points for a $5 discount.",
        "reward_type": "fixed_discount",
        "points_cost": 500,
        "reward_rule": {
          "points_required": 500,
          "discount_value": 5
        },
        "can_redeem": true,
        "points_needed": 0
      },
      {
        "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "name": "10% off your next order",
        "description": "Redeem 1000 points for 10% off.",
        "reward_type": "percentage_discount",
        "points_cost": 1000,
        "reward_rule": {
          "points_required": 1000,
          "discount_percentage": 10
        },
        "can_redeem": false,
        "points_needed": 350
      }
    ],
    "customer_rewards": [
      {
        "id": "1fd80ced-326f-4082-951b-21a38632a603",
        "reward_type": "fixed_discount",
        "status": "active",
        "discount_code": "YUKO-TVJ-BL1",
        "points_used": 500,
        "expires_at": "2026-06-01T00:00:00+00:00",
        "used_at": null,
        "created_at": "2026-02-15T08:30:00+00:00"
      }
    ]
  }
}
```

### Response fields

<ResponseField name="data.points_balance" type="integer">
  The customer's current redeemable points balance. Display this prominently at the top of your loyalty panel.

  Example: `650`
</ResponseField>

<ResponseField name="data.rewards" type="array">
  All rewards the merchant has configured, enriched with affordability data for this specific customer.

  <Expandable title="rewards[] fields">
    <ResponseField name="id" type="string (uuid)">
      Unique ID of the reward.
    </ResponseField>

    <ResponseField name="name" type="string">
      The reward name to display to the customer.
    </ResponseField>

    <ResponseField name="description" type="string | null">
      Optional longer description of the reward.
    </ResponseField>

    <ResponseField name="reward_type" type="enum">
      The type of discount. Possible values: `fixed_discount`, `percentage_discount`, `free_shipping`, `free_product`, `store_credit`.
    </ResponseField>

    <ResponseField name="points_cost" type="integer">
      How many points the customer must spend to redeem this reward.
    </ResponseField>

    <ResponseField name="reward_rule" type="object">
      The discount configuration. Structure varies by `reward_type`. For example, `fixed_discount` includes `discount_value`; `percentage_discount` includes `discount_percentage`.
    </ResponseField>

    <ResponseField name="can_redeem" type="boolean">
      `true` if the customer has enough points. `false` if not.
    </ResponseField>

    <ResponseField name="points_needed" type="integer">
      How many more points the customer needs. Always `0` when `can_redeem` is `true`.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="data.customer_rewards" type="array">
  Coupon codes the customer has already redeemed (converted points into a discount code) but not yet applied at checkout.

  <Expandable title="customer_rewards[] fields">
    <ResponseField name="discount_code" type="string | null">
      The actual coupon code (e.g. `YUKO-TVJ-BL1`). This is a standard Shopify discount code — pass it directly through your checkout discount flow.
    </ResponseField>

    <ResponseField name="status" type="enum">
      Current status. Only show coupons where `status === "active"`. Possible values: `active`, `used`, `inactive`, `expired`, `deleted`, `revert`.
    </ResponseField>

    <ResponseField name="expires_at" type="string (datetime) | null">
      When this coupon expires. `null` means no expiry.
    </ResponseField>

    <ResponseField name="used_at" type="string (datetime) | null">
      `null` if the coupon has not been used yet.
    </ResponseField>

    <ResponseField name="points_used" type="integer">
      How many points were spent to create this coupon.
    </ResponseField>
  </Expandable>
</ResponseField>

### Display logic

```text theme={null}
If points_balance > 0:
  Show: "You have {points_balance} points"

For each reward in rewards[]:
  If can_redeem === true  → Show active "Redeem" button: "{name} — {points_cost} pts"
  If can_redeem === false → Show greyed out: "{name} — earn {points_needed} more points"

For each item in customer_rewards[] where status === "active":
  Show: "You have an unused reward: {discount_code}"
```

***

## Endpoint 2 — Points Estimate at Checkout

This endpoint tells the customer how many loyalty points they will **earn** when they complete this purchase.

This is a **public endpoint** — no Shopify Customer ID is required. It works for both logged-in customers and guests.

Full API reference → [`POST /points/estimate`](/checkout-points-estimate)

### Request

```text theme={null}
POST https://api.yukoapp.com/api/v1/public/points/estimate
```

### Request body

<ParamField body="customer" type="object">
  Optional. Include only if the customer is logged in. Omit entirely for guest checkouts.

  <Expandable title="customer fields">
    <ParamField body="customer.id" type="string">
      Shopify Customer ID. Used to look up the customer if `email` is not provided.
    </ParamField>

    <ParamField body="customer.email" type="string (email)">
      Customer email address. Takes priority over `id` when both are provided.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="items" type="array" required>
  The line items in the cart. Must contain at least one item.

  <Expandable title="items[] fields">
    <ParamField body="items[].product_id" type="string" required>
      The Shopify Product ID.

      Example: `"prod_1001"`
    </ParamField>

    <ParamField body="items[].variant_id" type="string" required>
      The Shopify Variant/SKU ID.

      Example: `"var_1001_red_m"`
    </ParamField>
  </Expandable>
</ParamField>

### Example request — logged-in customer

```bash theme={null}
curl --request POST \
  --url "https://api.yukoapp.com/api/v1/public/points/estimate" \
  --header "Authorization: Bearer py_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "Content-Type: application/json" \
  --data '{
    "customer": {
      "id": "9049402769586",
      "email": "rahul.sharma@example.com"
    },
    "items": [
      {
        "product_id": "prod_1001",
        "variant_id": "var_1001_red_m"
      },
      {
        "product_id": "prod_2044",
        "variant_id": "var_2044_black_42"
      }
    ]
  }'
```

### Example request — guest checkout

```bash theme={null}
curl --request POST \
  --url "https://api.yukoapp.com/api/v1/public/points/estimate" \
  --header "Authorization: Bearer py_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "Content-Type: application/json" \
  --data '{
    "items": [
      {
        "product_id": "prod_1001",
        "variant_id": "var_1001_red_m"
      }
    ]
  }'
```

### Example response (200) — points earned

```json theme={null}
{
  "data": {
    "points_earned": 120,
    "message": "You will earn 120 points after completing this purchase"
  }
}
```

### Example response (200) — no active campaign

```json theme={null}
{
  "data": {
    "points_earned": 0,
    "message": null
  }
}
```

### Response fields

<ResponseField name="data.points_earned" type="integer">
  The number of loyalty points the customer will earn after completing the purchase.

  Example: `120`
</ResponseField>

<ResponseField name="data.message" type="string | null">
  A ready-to-display string. You can show this directly in your UI without additional formatting.

  Example: `"You will earn 120 points after completing this purchase"`

  Will be `null` if no earning campaign matches the cart.
</ResponseField>

### Display logic

```text theme={null}
If points_earned > 0:
  Show the message field directly in your UI

If points_earned === 0 or message === null:
  Hide this section entirely — never show "You will earn 0 points"
```

***

## Endpoint 3 — Redeem a Reward (Burn Points)

This is the endpoint your checkout calls when a customer **actively chooses to spend their points** on a reward.

When the customer clicks "Redeem" on an available reward, call this endpoint. Yuko will:

1. Deduct the required points from the customer's balance
2. Generate a fresh Shopify discount code for that reward
3. Return the discount code so you can apply it to the order

<Note>
  This endpoint is triggered by a **customer action** — not automatically at checkout load. Only call it when the customer explicitly clicks "Redeem" on a reward option.
</Note>

Full API reference → [`POST /customers/rewards/redeem`](/redeem-reward)

### Request

```text theme={null}
POST https://api.yukoapp.com/api/v1/public/customers/rewards/redeem
```

### Required headers

| Header          | Value                 |
| --------------- | --------------------- |
| `Authorization` | `Bearer YOUR_API_KEY` |
| `Content-Type`  | `application/json`    |

### Request body

<ParamField body="customer_id" type="string" required>
  The Shopify Customer ID of the logged-in customer.

  Example: `"9049402769586"`
</ParamField>

<ParamField body="reward_id" type="string (uuid)" required>
  The UUID of the reward the customer wants to redeem. Get this from the `rewards[].id` field in the Endpoint 1 response (`GET /customers/rewards/redeemable`).

  Example: `"90424fa7-e8a9-4ef4-b43a-72c9b2028db5"`
</ParamField>

### Example request

```bash theme={null}
curl --request POST \
  --url "https://api.yukoapp.com/api/v1/public/customers/rewards/redeem" \
  --header "Authorization: Bearer py_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "Content-Type: application/json" \
  --data '{
    "customer_id": "9049402769586",
    "reward_id": "90424fa7-e8a9-4ef4-b43a-72c9b2028db5"
  }'
```

<Tip>
  The `reward_id` comes directly from the `rewards[].id` field in the Endpoint 1 response. Store these IDs when you render the rewards list so you have them ready when the customer clicks "Redeem".
</Tip>

### Example response (201 — Created)

A `201` status means the redemption was successful and a new coupon code has been generated.

```json theme={null}
{
  "data": {
    "coupon_code": "YUKO-TVJ-BL1",
    "points_spent": 500,
    "reward_type": "fixed_discount",
    "discount_value": 5,
    "expires_at": "2026-06-01T00:00:00+00:00"
  }
}
```

### Response fields

<ResponseField name="data.coupon_code" type="string | null">
  The newly generated Shopify discount code (e.g. `YUKO-TVJ-BL1`). Apply this directly through your checkout's discount flow.

  Will be `null` for `store_credit` reward types — the credit is added to the customer's account automatically.
</ResponseField>

<ResponseField name="data.points_spent" type="integer">
  The number of points deducted from the customer's balance.

  Example: `500`
</ResponseField>

<ResponseField name="data.reward_type" type="enum | null">
  The type of reward that was redeemed. Possible values: `fixed_discount`, `percentage_discount`, `free_shipping`, `free_product`, `store_credit`.
</ResponseField>

<ResponseField name="data.discount_value" type="number | null">
  The discount amount or percentage associated with the coupon.

  * For `fixed_discount`: this is the rupee/dollar amount (e.g. `5` means \$5 off)
  * For `percentage_discount`: this is the percentage (e.g. `10` means 10% off)
  * Will be `null` for `free_shipping` and `free_product` reward types
</ResponseField>

<ResponseField name="data.expires_at" type="string (datetime) | null">
  When the generated coupon expires. `null` if the reward has no expiry.

  Example: `"2026-06-01T00:00:00+00:00"`
</ResponseField>

### Error responses for this endpoint

| Code  | Scenario                            | What to do                                                                                                                                             |
| ----- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `422` | Customer doesn't have enough points | This shouldn't happen if you used `can_redeem` from Endpoint 1 correctly. Show the customer a message: "You don't have enough points for this reward." |
| `422` | Reward not found or is inactive     | The reward may have been deactivated by the merchant. Refresh the rewards list by calling Endpoint 1 again.                                            |
| `404` | Customer not found                  | The `customer_id` is not in Yuko.                                                                                                                      |
| `401` | Invalid API key                     | Check the `Authorization` header.                                                                                                                      |

### Display logic after a successful redemption

```text theme={null}
On successful 201 response:

1. Show the customer their new discount code:
   "Your reward is ready! Code: {coupon_code}"

2. Auto-apply the discount code to the order if your checkout supports it

3. Update the displayed points balance:
   New balance = old points_balance - points_spent

4. Remove the redeemed reward from the "available rewards" list in your UI
```

<Warning>
  After a successful redemption, the customer's points balance has decreased. If you have the old balance cached, subtract `points_spent` from it to show the updated balance without making another API call.
</Warning>

***

## Full Checkout Flow

### Step 1 — Collect what you need at checkout

* **Shopify Customer ID** — only if the customer is logged in (needed for Endpoint 1)
* **Product IDs and Variant IDs** for every item in the cart (needed for Endpoint 2)

### Step 2 — Decide which endpoints to call

| Customer state                           | Call Endpoint 1? | Call Endpoint 2?                 | Call Endpoint 3?                      |
| ---------------------------------------- | ---------------- | -------------------------------- | ------------------------------------- |
| Logged in, Shopify Customer ID available | ✅ Yes            | ✅ Yes — include `customer` field | ✅ Yes — when customer clicks "Redeem" |
| Guest checkout                           | ❌ No — skip      | ✅ Yes — omit `customer` field    | ❌ No — guests can't redeem points     |

### Step 3 — Fire both calls in parallel

Do not wait for one to finish before starting the other. Running them in parallel keeps checkout fast.

```bash theme={null}
# Call 1 — Only if the customer is logged in
curl --request GET \
  --url "https://api.yukoapp.com/api/v1/public/customers/rewards/redeemable?customer_id=9049402769586" \
  --header "Authorization: Bearer py_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

```bash theme={null}
# Call 2 — Always run this, for both logged-in and guest customers
curl --request POST \
  --url "https://api.yukoapp.com/api/v1/public/points/estimate" \
  --header "Authorization: Bearer py_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "Content-Type: application/json" \
  --data '{
    "customer": {
      "id": "9049402769586"
    },
    "items": [
      {
        "product_id": "prod_1001",
        "variant_id": "var_1001_red_m"
      }
    ]
  }'
```

### Step 4 — Render the loyalty panel  - Example

Combine both responses to display the loyalty section: Its an example. Customize as per your UI.

```text theme={null}
┌──────────────────────────────────────────────┐
│  🏆  Your Rewards                            │
│                                              │
│  Points Balance: 650 pts                     │
│                                              │
│  ✅  $5 off your next order                 │
│      Costs 500 pts              [Redeem →]  │
│                                              │
│  🔒  10% off your next order                │
│      Earn 350 more points to unlock          │
│                                              │
│  🎟️  You have an unused coupon              │
│      YUKO-TVJ-BL1          [Apply to order] │
│                                              │
│  💰  You'll earn 120 points on this order!  │
└──────────────────────────────────────────────┘
```

### Step 5 — Handle the "Redeem" button click

When a customer clicks "Redeem" on an available reward, call Endpoint 3 with their `customer_id` and the `reward_id` of the reward they selected.

```bash theme={null}
curl --request POST \
  --url "https://api.yukoapp.com/api/v1/public/customers/rewards/redeem" \
  --header "Authorization: Bearer py_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "Content-Type: application/json" \
  --data '{
    "customer_id": "9049402769586",
    "reward_id": "90424fa7-e8a9-4ef4-b43a-72c9b2028db5"
  }'
```

On a `201` response:

* Take the `coupon_code` from the response and apply it to the checkout order
* Update the displayed points balance: `new_balance = old_balance - points_spent`
* Remove the redeemed reward from the UI rewards list

<Note>
  The `reward_id` is the `id` field from the `rewards[]` array in the Endpoint 1 response. You should store these IDs when you render the rewards list so they're ready when the customer clicks.
</Note>

***

## Error Handling

<Warning>
  The loyalty panel is an **enhancement** to checkout — it must never block or break the purchase. If any Yuko API call fails, hide the loyalty panel silently and let checkout continue.
</Warning>

Full list of error codes → [Errors reference](/errod)

### HTTP status codes

| Code  | Meaning                                        | What to do                                                            |
| ----- | ---------------------------------------------- | --------------------------------------------------------------------- |
| `200` | Success                                        | Use the response data                                                 |
| `400` | Bad request — missing or malformed parameters  | Check your request structure                                          |
| `401` | Unauthorized — API key missing or incorrect    | Check the `Authorization` header and key value                        |
| `403` | Forbidden — token missing the right scope      | Ask the merchant to regenerate a token with `rewards:read` scope      |
| `404` | Customer not found                             | The `customer_id` doesn't exist in Yuko — hide the panel silently     |
| `422` | Validation error — logically incorrect request | Check the `message` field in the response                             |
| `429` | Rate limit exceeded                            | Wait 1 second and retry once — see [Rate Limits](#rate-limits)        |
| `500` | Yuko internal error                            | Retry once. If it persists, hide the panel and let checkout continue. |
| `503` | Yuko temporarily unavailable                   | Same as 500 — retry once, then fail gracefully                        |

### Error response format

All error responses from Yuko return a `message` field:

```json theme={null}
{
  "message": "Customer not found"
}
```

### Recommended handling pattern

```text theme={null}
401 → Log the error. Hide the loyalty panel.
404 → Customer has no Yuko account yet (common for new customers). Hide silently.
429 → Wait 1 second. Retry the same request once.
5xx → Retry once after a short delay. If it fails again, hide the panel.
Any other error → Hide the loyalty panel. Never block checkout.
```

***

## Rate Limits

See full details → [Rate Limits reference](/rate-limits)

| Limit                               | Value                        |
| ----------------------------------- | ---------------------------- |
| Requests per second (per API token) | 10                           |
| Error when exceeded                 | HTTP `429 Too Many Requests` |
| Reset interval                      | 1 second                     |

<Tip>
  **Cache the rewards list.** The `rewards[]` catalogue changes infrequently — you can cache it for 5–15 minutes. The `points_balance` and `customer_rewards[]` are customer-specific and should be fetched fresh each checkout session.
</Tip>

* **Don't poll.** Call both endpoints once when the customer arrives at checkout.
* **Retry with a delay.** On a 429, wait 1 second and retry once. Never retry immediately.

***

## Security Rules

<Warning>
  **These rules are not optional.** Violating them exposes the merchant's API key and puts their store at risk.
</Warning>

**Rule 1 — Never expose the API key on the frontend.** The key is a secret. It must only ever live on your backend server.

* ❌ Never put it in browser-side JavaScript
* ❌ Never put it in a mobile app binary
* ❌ Never commit it to a public code repository
* ✅ Make all Yuko API calls from your backend — pass only the results to your frontend

**Rule 2 — Use HTTPS everywhere.** All requests to Yuko must use `https://`. Plain HTTP will be rejected.

***

## Authentication Setup Options

Choose whichever approach fits your architecture.

<Tabs>
  <Tab title="Token-Based (Easy Quick Start)">
    This is the simplest approach. No OAuth flow required.

    **How it works:**

    <Steps>
      <Step title="Merchant generates a token">
        The merchant goes to **Shopify Admin → Apps → Yuko Loyalty → Account Settings → Integrations → REST API** and creates a token with `rewards:read` scope.
      </Step>

      <Step title="Merchant shares the token">
        The merchant copies the token and pastes it into your checkout platform's merchant settings.
      </Step>

      <Step title="Your backend uses the token">
        Your backend reads the token from your settings and includes it in every Yuko API request for that merchant.
      </Step>
    </Steps>

    **Pros:** Fast to set up. No OAuth flow required.\
    **Cons:** The merchant must manually copy-paste the token. If they regenerate it, it needs to be updated on your side.
  </Tab>

  <Tab title="OAuth-Based (Recommended)">
    This gives merchants a one-click **Connect Yuko** button inside your checkout dashboard — no manual copy-pasting.

    **How it works:**

    <Steps>
      <Step title="Add a Connect button">
        Add a "Connect Yuko" button in your merchant dashboard settings.
      </Step>

      <Step title="Redirect to Yuko">
        When the merchant clicks it, redirect them to Yuko's OAuth authorization page.
      </Step>

      <Step title="Merchant approves">
        The merchant reviews and approves the connection inside Yuko.
      </Step>

      <Step title="Exchange the code">
        Yuko redirects back to your platform with an authorization code. Your backend exchanges this code for an access token.
      </Step>

      <Step title="Store and use">
        Store the token securely. Use it for all Yuko API calls for that merchant going forward.
      </Step>
    </Steps>

    **Integration can be initiated from either side:**

    | Initiated by               | Description                                                                                   |
    | -------------------------- | --------------------------------------------------------------------------------------------- |
    | **Your checkout platform** | You add the "Connect to Yuko" button. The merchant clicks it from your dashboard.             |
    | **Yuko**                   | Yuko lists your platform as an option. The merchant connects from inside their Yuko settings. |

    <Note>
      To get started with OAuth, contact the Yuko team to receive your OAuth client credentials (client ID and client secret) and configure your redirect URI → [Book a call](https://cal.com/rameshelamathi/yuko-demo)
    </Note>
  </Tab>
</Tabs>

***

## Testing

### Step 1 — Get a test API key

Contact the Yuko team for a test key, or use a real merchant's key on a development Shopify store.

### Step 2 — Verify authentication

```bash theme={null}
curl --request GET \
  --url "https://api.yukoapp.com/api/v1/public/customers/rewards/redeemable?customer_id=9049402769586" \
  --header "Authorization: Bearer py_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

**Expected:** HTTP 200 with `data.points_balance` in the response body.\
If you get HTTP 401, the API key is wrong — check for extra spaces or incorrect values.

### Step 3 — Test Endpoint 1 with a real customer

```bash theme={null}
curl --request GET \
  --url "https://api.yukoapp.com/api/v1/public/customers/rewards/redeemable?customer_id=REAL_CUSTOMER_ID" \
  --header "Authorization: Bearer py_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

Verify:

* `points_balance` returns a number
* `rewards[]` is populated with the merchant's reward options
* `can_redeem` correctly reflects the customer's balance
* `customer_rewards[]` returns any existing coupons

### Step 4 — Test Endpoint 2 with real cart items

```bash theme={null}
curl --request POST \
  --url "https://api.yukoapp.com/api/v1/public/points/estimate" \
  --header "Authorization: Bearer py_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "Content-Type: application/json" \
  --data '{
    "customer": {
      "id": "REAL_SHOPIFY_CUSTOMER_ID"
    },
    "items": [
      {
        "product_id": "REAL_PRODUCT_ID",
        "variant_id": "REAL_VARIANT_ID"
      }
    ]
  }'
```

Verify: `points_earned` returns a non-zero value if the merchant has active earning campaigns configured.

### Step 5 — Test Endpoint 3 — Redeem a reward

Use a `reward_id` from the Endpoint 1 response (choose one where `can_redeem === true`).

```bash theme={null}
curl --request POST \
  --url "https://api.yukoapp.com/api/v1/public/customers/rewards/redeem" \
  --header "Authorization: Bearer py_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "Content-Type: application/json" \
  --data '{
    "customer_id": "REAL_SHOPIFY_CUSTOMER_ID",
    "reward_id": "REWARD_ID_FROM_ENDPOINT_1_RESPONSE"
  }'
```

Verify:

* Response is HTTP `201` (not 200)
* `coupon_code` is returned and is a valid Shopify discount code
* `points_spent` matches the `points_cost` of the reward you redeemed
* `expires_at` is present (or `null` if the reward has no expiry)

### Step 6 — Test Endpoint 2 for guest checkout

```bash theme={null}
curl --request POST \
  --url "https://api.yukoapp.com/api/v1/public/points/estimate" \
  --header "Authorization: Bearer py_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --header "Content-Type: application/json" \
  --data '{
    "items": [
      {
        "product_id": "REAL_PRODUCT_ID",
        "variant_id": "REAL_VARIANT_ID"
      }
    ]
  }'
```

Verify: Returns HTTP 200 with no `customer` field in the request body.

### Step 7 — Test error cases

| Test                            | How to trigger                                 | Expected result |
| ------------------------------- | ---------------------------------------------- | --------------- |
| Wrong API key                   | Use `Bearer wrong_key`                         | HTTP 401        |
| Unknown customer                | Use `customer_id=0000000`                      | HTTP 404        |
| Missing items                   | Send `"items": []`                             | HTTP 422        |
| Guest checkout                  | Omit `customer` entirely                       | HTTP 200        |
| Redeem with insufficient points | Use a `reward_id` where `can_redeem === false` | HTTP 422        |
| Redeem with invalid reward ID   | Use a made-up UUID as `reward_id`              | HTTP 422        |

### Step 8 — Test UI edge cases

* Customer with 0 points — panel shows rewards, all greyed out
* Customer with no prior redemptions — `customer_rewards[]` is an empty array — handle without crashing
* Guest customer — Endpoint 1 and Endpoint 3 are not called; only show earn estimate from Endpoint 2
* `points_earned === 0` — hide the earn message; do not show "0 points"
* After a successful redemption — UI updates the balance and removes the redeemed reward from the list

***

## Go-Live Checklist

**Security**

* API key stored as an environment variable — not hardcoded anywhere
* All Yuko API calls made from the backend server — not the browser
* HTTPS used for all requests

**API Integration**

* `GET /customers/rewards/redeemable` working and returning correct data
* `POST /points/estimate` working for both logged-in and guest customers
* `POST /customers/rewards/redeem` working — returns `201` with a valid `coupon_code`
* Endpoints 1 and 2 fire in parallel at checkout load — not sequentially
* Endpoint 3 is only called on an explicit customer "Redeem" click — not at page load
* If any call fails, checkout continues without breaking

**UI**

* Points balance displayed clearly
* Redeemable rewards shown with a "Redeem" button for rewards where `can_redeem === true`
* Rewards with insufficient points shown as greyed-out with "X more points needed"
* Unused coupon codes from `customer_rewards[]` surfaced with an apply action
* Points earn estimate shown when `points_earned > 0`
* After a successful redemption: balance updates, redeemed reward is removed from list, new coupon code is surfaced or auto-applied
* No loyalty UI shown for guest checkouts
* Empty `customer_rewards[]` handled gracefully — no crash, no blank space

**Rate Limiting**

* Retry logic in place for HTTP 429 — wait 1 second, retry once
* No polling within a single checkout session

**Authentication**

* Plan in place for token rotation if the merchant regenerates their API key

***

## API Reference Links

<CardGroup cols={2}>
  <Card icon="gift" href="/get-redeemable-rewards" title="Get Redeemable Rewards">
    Points balance, available rewards, and unused coupons for a customer.
  </Card>

  <Card icon="coins" href="/redeem-reward" title="Redeem a Reward">
    Burn a customer's points and generate a discount code.
  </Card>

  <Card icon="calculator" href="/checkout-points-estimate" title="Points Estimate">
    How many points a customer earns on a given cart.
  </Card>

  <Card icon="key" href="/authentication" title="Authentication">
    How Bearer Token authentication works in the Yuko API.
  </Card>

  <Card icon="circle-exclamation" href="/errod" title="Errors">
    Full list of HTTP error codes and what they mean.
  </Card>

  <Card icon="gauge" href="/rate-limits" title="Rate Limits">
    Request limits and how to handle 429 errors.
  </Card>
</CardGroup>

***

## Support

<CardGroup cols={2}>
  <Card icon="calendar" href="https://cal.com/rameshelamathi/yuko-demo" title="Book a call with Ramesh">
    For integration questions, OAuth setup, and onboarding support.
  </Card>

  <Card icon="code" href="https://docs.yuko.so" title="API Documentation">
    Full Yuko API reference.
  </Card>
</CardGroup>

***

*Built by Flycart — the team behind 100K+ WooCommerce plugin installs.*\
*Discount Rules · WPLoyalty · Yuko*
