# Guide: Initiate a Basket with a Limio Offer

### Overview

[Purchase Links](https://docs.limio.com/product/checkout/how-to-configure-purchase-links) and standard [Offers Pages](https://docs.limio.com/product/pricing/how-to-create-offers-and-add-ons-to-attach-to-pages) are great for communicating acquisition journeys where every visitor sees the same public offer.

For win-back, renewal, upsell and other targeted flows, you usually want more control because you already know who the customer is. You might want to send them straight to checkout from an email, your CRM, or your own app instead of dropping them on to a landing page to choose an Offer.

In those cases, you can create a service using [Limio APIs](https://docs.limio.com/api) that will create and pre-populate a Limio basket for that specific customer before they ever click through. For example, you can:

* Decide if they’re eligible for a specific price or offer
* Prefill known details (email, account ID, address) so they don’t have to type them
* Add extra metadata to the Limio basket (cart) that only exists in your system, like campaign codes or CRM IDs

This gives you a few advantages:

* **No pricing/eligibility logic in the browser.** All rules stay on your side, not in public JavaScript.
* **Cleaner handoff into checkout.** The basket is already built, so the customer lands in a ready to pay state with minimal clicks.
* **Richer basket data.** You can attach custom fields and tracking info up front, instead of trying to stitch it together later.

### Prerequisites

* **Access to the Limio Commerce API** with a valid [**Bearer token**](https://docs.limio.com/developers/api/authentication-overview/oauth-bearer-token). All requests in this guide use `Authorization: Bearer <YOUR_TOKEN>`.
* A **published Offer** configured in Limio. If you’re new to Offers, start here: [*What are Offers and how to configure them?*](https://docs.limio.com/product/pricing/what-are-offers-and-how-to-configure-them)
* (Optional but recommended) One or more [**Custom Attributes**](https://docs.limio.com/product/settings/config-settings/templates-and-custom-attributes) on your Offer to help you query the right Offer for a campaign or journey. Learn how to add attributes to templates here.
* Limio **Shop** page with **Modular Checkout (Form)** to consume the created basket by `id`. See the [Limio SDK Basket](https://docs.limio.com/developers/limio-sdk/basket) page for how the cart/basket is used in components.

### What you’ll build

1. **Fetch Offers (V2)** and optionally filter by a **custom attribute** (e.g., a campaign code) to locate the exact Offer and Version you want.
2. **Initiate a checkout session (basket)** with that Offer’s `id` and `version`. The API returns a **basket `id`**.
3. **Send the shopper to checkout** with the basket `id` so your checkout page loads the pre-populated Offer.

### Fetch your Offers

Use [**Get Offers V2**](https://docs.limio.com/api/catalog-api/catalog#get-offers-v2) to retrieve standalone Offers. You can retrieve all your offers or use the **attributes** parameter to fetch only the Offers relevant to your campaign, defined in your Offer template (e.g., `campaign_code__limio=WIN001`).

#### Example — fetch offers with a campaign attribute (curl)

```bash
curl -s -G \
  'https://your-environment.prod.limio.com/api/offers/v2' \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  --data-urlencode 'attributes.campaign_code__limio=WIN001' \
  --data-urlencode 'reducedData=true' \
  --data-urlencode 'offersSource=published' \
  --data-urlencode 'opt.pageSize=10'
```

**Notes:**

* `attributes.<YOUR_ATTRIBUTE>` limits results to Offers that have this specific attribute value
* `offersSource=published` limits results to published Offers; use `catalog` to return all from your catalog.
* `reducedData=true` makes responses smaller when you only need keys like `id`, `version`, `path`, etc.

**Response (truncated):**

```json
{
  "hits": 1,
  "items": [
    {
      "id": "fab052ce94fbfd0d3663ec0cb9d977367a593684",
      "name": "Offer Digital",
      "path": "/offers2/Offer Digital",
      "version": "101d166f7386bb9f1c7635412424b51bbe393ccc",
      "record_type": "offer"
    }
  ]
}
```

> If you’re unfamiliar with configuring Offers and their attributes, see the [Offers](https://docs.limio.com/product/pricing/what-are-offers-and-how-to-configure-them) overview and [Templates & Custom Attributes](https://docs.limio.com/product/settings/config-settings/templates-and-custom-attributes) docs.

### Initiate the basket with your Offer

Use **Create or update a checkout session** to create a Limio basket that includes your chosen Offer. At minimum you’ll provide:

* `order.orderItems[].offer` with the Offer `id` and `version` you fetched in Step 1
* `order.external_id` — your external reference, this can be any random ID
* `order.country`, `order.source`, `order.order_type`
* Optionally `order.tracking` metadata (e.g., `offers`, `purchaseCountryCode`, CRM IDs) for analytics/CRM linking

#### Example — initiate a checkout session (curl)

```bash
curl -s -X POST \
  'https://your-environment.prod.limio.com/api/checkout/initiate' \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "order": {
      "orderItems": [
        {
          "offer": {
            "id": "fab052ce94fbfd0d3663ec0cb9d977367a593684",
            "version": "101d166f7386bb9f1c7635412424b51bbe393ccc"
          },
          "quantity": 1
        }
      ],
      "external_id": "WINBACK-2025-000123",
      "tracking": {
        "offers": ["/offers2/Offer Digital"],
        "purchaseCountryCode": "GB",
        "accountId": "0017x00000Q9O9qAAF",
        "contactId": "0037x00000F58M9AAJ",
        "userId": "0057x0000088Oh3AAE"
      },
      "country": "GB",
      "source": "shop",
      "order_type": "new"
    }
  }'
```

**Response:**

```json
{ "id": "basket-8cf72b2a-eb57-462d-8e55-981c3b5e5364" }
```

> The response `id` is the **basket id** for this checkout session. Keep it - you’ll pass it into your shop’s checkout.

### Send the customer to checkout with the basket id

Your shop/checkout page (e.g., Modular Checkout built with the Form component) should read the **basket id** and load the session. A typical pattern is:

```
https://your-environment-shop.prod.limio.com/checkout?basket=<BASKET_ID>
```

Use the [Limio SDK Basket helpers](https://docs.limio.com/developers/limio-sdk/basket) within your component(s) to read and render the basket/cart and present additional parameters you might have added to your Basket object.

> For checkout UI, see [**Component: Form (Modular Checkout)** ](https://docs.limio.com/components/component-library/modular-checkout-components/component-checkout-form)and related guidance on composing a checkout page with subcomponents.

### End-to-end example (Node.js)

Below is a minimal Node.js service that finds an Offer by attribute and initiates a basket.

```js
import express from "express";
import fetch from "node-fetch";

const app = express();
app.use(express.json());

const LIMIO_BASE = "https://your-environment.prod.limio.com";
const TOKEN = process.env.LIMIO_TOKEN;

// Find an Offer by custom attribute and initiate a checkout session
app.post("/winback", async (req, res) => {
  const { campaignCode, externalId, country = "GB" } = req.body;

  // 1) Get Offers V2 filtered by attribute
  const offersUrl = new URL(`${LIMIO_BASE}/api/offers/v2`);
  offersUrl.searchParams.set(`attributes.campaign_code__limio`, campaignCode);
  offersUrl.searchParams.set("offersSource", "published");
  offersUrl.searchParams.set("reducedData", "true");

  const offersResp = await fetch(offersUrl, {
    headers: { Authorization: `Bearer ${TOKEN}` },
  });
  if (!offersResp.ok) {
    const err = await offersResp.text();
    return res.status(offersResp.status).send(err);
  }
  const offers = await offersResp.json();
  const offer = offers.items?.[0];
  if (!offer) return res.status(404).send("Offer not found");

  // 2) Initiate checkout session with the Offer id + version
  const initiateResp = await fetch(`${LIMIO_BASE}/api/checkout/initiate`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      order: {
        orderItems: [
          {
            offer: { id: offer.id, version: offer.version },
            quantity: 1,
          },
        ],
        external_id: externalId,          // becomes checkoutId
        tracking: { offers: [offer.path], purchaseCountryCode: country },
        country,
        source: "shop",
        order_type: "new",
      },
    }),
  });

  if (!initiateResp.ok) {
    const err = await initiateResp.text();
    return res.status(initiateResp.status).send(err);
  }
  const { id: basketId } = await initiateResp.json();

  // 3) Return a checkout URL for the client/app/email flow
  const checkoutUrl = `https://your-environment-shop.prod.limio.com/checkout?basket=${encodeURIComponent(basketId)}`;
  res.json({ basketId, checkoutUrl });
});

app.listen(3000);
```

### Tips & troubleshooting

* **Can I page through lots of Offers?** Yes—use `opt.all=true` and follow `queryMore` pointers (`from` + `alias`) to retrieve subsequent pages.
* **Published vs catalog:** If your org uses *Published Offers*, set `offersSource=published` to restrict results to published records.
* **Basket anatomy:** To understand what’s inside a basket and how your shop components read it, review the [Basket SDK](https://docs.limio.com/developers/limio-sdk/basket) page.
* **Abandoned baskets:** If you’re running remarketing flows, see the [Abandoned Baskets API](https://docs.limio.com/api/checkout-api/checkout-and-baskets#get-abandoned-baskets) for retrieving in-progress but uncompleted sessions.
