> For the complete documentation index, see [llms.txt](https://docs.limio.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.limio.com/guides/developer-guides/guide-initiate-a-basket-with-a-limio-offer.md).

# 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) carrying the **admin** scope. 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 complete the order. 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. **Create a checkout basket** with that Offer’s `id` and `version`. The API returns the basket `id` and a **recovery link**.
3. **Send the shopper to checkout** using that recovery link, so they land on a ready-to-pay checkout with the Offer already in the basket.

{% hint style="info" %}
This guide uses the [**Create checkout basket for new subscription**](https://docs.limio.com/api/checkout-baskets-api/new-subscription#create-checkout-basket-for-new-subscription) API (`POST /api/admin/checkout/initiate`). It is the server-to-server endpoint: it authenticates with a bearer token, so it can be called from your backend, your CRM, or a scheduled job.

Do not use the shop’s own `/api/checkout/initiate` for this. That endpoint is for the browser: it relies on the shopper’s session cookie, which your backend does not have.
{% endhint %}

### 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. The attribute is a custom attribute that you define in your Offer template (e.g., `campaign_code=WIN001`). Note that the `__limio` suffix is reserved for Limio's own attributes, so do not use it when naming your own.

#### 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=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.

### Create the basket with your Offer

Use [**Create checkout basket for new subscription**](https://docs.limio.com/api/checkout-baskets-api/new-subscription#create-checkout-basket-for-new-subscription) to create a Limio basket that includes your chosen Offer. These fields are required:

* `order.orderItems[].offer` — the Offer `id` and `version` you fetched in Step 1
* `order.external_id` — your external reference. It becomes the `checkoutId` of the order, so use something you can trace back to your system
* `order.country`, `order.source`, `order.order_type` (`"new"` here)

And these are optional:

* `order.tracking` — analytics and CRM metadata (`offers`, `purchaseCountryCode`, Salesforce IDs). **Passing `tracking.accountId` also assigns the basket an owner**, which changes how you send the customer to checkout — see below
* `journey.checkout` — send the customer to a custom checkout page instead of the default
* `expiresAfter` — how long the basket stays alive. Defaults to 14 days
* To apply a promo code, create the basket first, then call `POST /api/admin/v2/promo_code` with the `basketId` from the response

#### Example — create a checkout basket (curl)

```bash
curl -s -X POST \
  'https://your-environment.prod.limio.com/api/admin/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"
    },
    "expiresAfter": { "days": 7 }
  }'
```

**Response:**

```json
{
  "id": "basket-8cf72b2a-eb57-462d-8e55-981c3b5e5364",
  "recoveryLink": "/api/checkout/recover?basketId=basket-8cf72b2a-eb57-462d-8e55-981c3b5e5364&recover=eyJ...",
  "assistedCheckoutLink": "/api/checkout/assisted?basketId=basket-8cf72b2a-eb57-462d-8e55-981c3b5e5364&cl=eyJ...",
  "order": {
    "orderItems": [ /* verified items with resolved pricing */ ],
    "total": { "currency": "GBP", "amount": 99.0 },
    "orderVersion": "a1b2c3..."
  }
}
```

* `recoveryLink` — a signed URL, valid 30 days. This is how you send the customer to checkout.
* `assistedCheckoutLink` — returned **only** when you passed `tracking.accountId`. The example above does, so it gets one.
* `order` — the order as Limio resolved it, with real pricing. Worth logging: this is the source of truth, not what you sent.

### Send the customer to checkout

Send them to the **`recoveryLink`**, appended to your shop domain:

```
https://your-environment-shop.prod.limio.com/api/checkout/recover?basketId=basket-8cf72b2a...&recover=eyJ...
```

That link does two things a bare basket id cannot: it establishes the shopper’s checkout session in their browser, then redirects them to the checkout page with the basket loaded.

{% hint style="warning" %}
Do not build your own `?basket=<id>` URL. Your backend created this basket, so the shopper’s browser has no session for it, and a basket id alone does not create one. The signed recovery link is what carries the session across.
{% endhint %}

If you passed `tracking.accountId`, use the **`assistedCheckoutLink`** instead. It does the same thing, but also signs the customer in on behalf of that account (an OBO flow), so an agent or a CRM-driven journey lands them straight into an authenticated checkout.

Once they arrive, your checkout page renders the basket as normal. Use the [Limio SDK Basket helpers](https://docs.limio.com/developers/limio-sdk/basket) in your components to read the cart and any custom fields you attached.

> 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, creates a basket, and returns a link you can drop into an email or your CRM.

```js
import express from "express";

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

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

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

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

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

  // 2) Create the basket with the Offer id + version
  const initiateResp = await fetch(`${LIMIO_BASE}/api/admin/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 the checkoutId of the order
        tracking: {
          offers: [offer.path],
          purchaseCountryCode: country,
          // Passing accountId assigns the basket an owner and returns an
          // assistedCheckoutLink, which signs the customer in on their behalf.
          ...(accountId && { accountId }),
        },
        country,
        source: "shop",
        order_type: "new",
      },
      expiresAfter: { days: 7 },
    }),
  });

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

  // 3) Build the link to send the customer. Both establish their checkout
  //    session and redirect them into checkout with the basket loaded.
  const checkoutUrl = `${SHOP_BASE}${assistedCheckoutLink ?? recoveryLink}`;
  res.json({ basketId, checkoutUrl });
});

app.listen(3000);
```

### Tips & troubleshooting

* **Why not just pass the basket id to my checkout page?** Because your backend created the basket, the shopper’s browser has no session for it. The `recoveryLink` establishes that session and then redirects; a bare id does not.
* **How long does the link last?** The signed token in `recoveryLink` and `assistedCheckoutLink` is valid for **30 days**. The basket itself expires after **14 days** unless you set `expiresAfter`.
* **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/abandoned-baskets-api#list-abandoned-baskets) for retrieving in-progress but uncompleted baskets. It returns a `recoveryLink` for each one, the same mechanism this guide uses.
* **Renewals:** To build a basket for an existing subscription’s renewal rather than a new purchase, use [Create checkout basket to renew subscription](https://docs.limio.com/api/checkout-baskets-api/renew-subscription).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.limio.com/guides/developer-guides/guide-initiate-a-basket-with-a-limio-offer.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
