# Subbly Developers
> Complete documentation for Large Language Models
---
## Document: /reference/subbly-sdk
URL: /reference/subbly-sdk
Subbly.js is Subbly's browser-side JavaScript library. It calls the Storefront
API for you, holds one cart per page, and keeps the cart ID and the customer
access token in browser cookies. `Subbly.init` resolves with an SDK instance;
every method in this reference is a method of that instance.
The SDK emits no events: it has no emitter and dispatches no DOM events. The
events you may have seen in Subbly belong to the
[cart widget](/reference/cart-widget), a separate product.
## Load
```js title="Start the SDK"
import Subbly from '@subbly/sdk'
const subbly = await Subbly.init({ apiKey: 'API_KEY' })
```
The package also ships a browser bundle,
`https://cdn.jsdelivr.net/npm/@subbly/sdk`, that defines the global `Subbly`.
It is the class, not an instance, so you still call `Subbly.init(config)`.
## Cookies
The SDK writes two cookies, each for 365 days on `path=/`:
- `subbly_cart_id` — the cart, written by `Subbly.init` when you pass
`loadCart`, and by `subbly.cart.create`.
- `subbly_access_token` — the customer access token, written by the
authentication methods and emptied by `subbly.auth.logout`.
Both are plain cookies. The SDK sets no `Secure`, `HttpOnly` or `SameSite`
attribute, so any script on the host can read them.
## Amounts and dates
Amounts are whole numbers in the minor unit of the currency: `1250` in a `USD`
cart means $12.50. Dates and timestamps are strings.
## Currency
The product, bundle and survey methods take an optional `headers` object as
their last argument. Set `x-currency` on it to price that one response in
another currency. When you leave it out, the SDK sends the currency you set with
[`subbly.setCurrency`](/reference/subbly-sdk/setup#set-currency); when you set
neither, it sends no currency header.
The subscription methods also take `headers`, but the SDK adds no currency to
them.
`setCurrency` does not change the cart. The cart has its own `currencyCode`;
change it with `subbly.cart.update({ currencyCode })`. When `Subbly.init` loads
a cart, it calls `setCurrency` with the cart currency for you.
```js title="Price one product in another currency"
subbly.setCurrency('USD')
const product = await subbly.products.load(123)
const inEuros = await subbly.products.load(123, {}, { 'x-currency': 'EUR' })
```
## Expand parameters
Most methods that return a cart or a subscription take an optional `params`
object with an `expand` list. Each entry names a relation the API should fill
in. A relation you do not name comes back `null`, so ask for what you need to
render.
The cart methods accept `coupon`, `gift_card`, `items.product`,
`items.product.parent`, `items.product.parent.pricings`,
`items.bundle.items.product`, `items.bundle.items.product.parent`,
`items.metadata`, `shipping_method`, `shipping_address`, `payment_method`,
`billing_address`, `metadata` and `shipping_carrier`. The funnel accepts that
return a cart take the same list.
The subscription methods accept `product`, `product.parent`,
`bundle.items.product`, `bundle.items.product.parent`, `metadata`, `addons`,
`discounts` and `cancellations`.
The funnel fetches accept `parent`, which loads the parent product of each
offered variant or plan.
```js title="Load a cart with its items and coupon"
await subbly.cart.load(cartId, {
expand: ['items.product', 'items.product.parent', 'coupon']
})
```
## Errors
A failed request rejects with a plain object, not an `Error`. So
`error instanceof Error` is `false` and `error.message` may be missing. Read
`error.code` instead.
```js title="Handle an error"
try {
await subbly.cart.addItem({ productId: 3301 })
} catch (error) {
if (error.code === 'out_of_stock') {
// tell the customer
}
}
```
The codes are:
| `code` | What happened |
| :-- | :-- |
| `not_found` | The record does not exist. |
| `bad_request` | The request or the payload is invalid. |
| `out_of_stock` | A product or a survey answer ran out. |
| `payment_requires_action` | The card needs 3-D Secure. The object also carries `paymentIntentId`. |
| `user_email_is_not_unique` | The email already has an account. |
| `unauthenticated` | There is no valid access token. |
When the response has no body, the rejection is a real `Error` carrying the HTTP
status text instead. Handle both shapes.
Two rejections are plain `Error` objects instead, with no `code`:
- `Error('Missing required apiKey config')` — `Subbly.init` with no argument, or
with an empty or missing `apiKey`. `init` is `async`, so it rejects.
- `Error('Cart ID is required')` — `subbly.cart.load` with no ID given and none
held.
To install, configure and update the library, see the
[Subbly.js SDK article](/developer-resources/subbly-js-sdk).
---
## Document: /reference/cart-widget
URL: /reference/cart-widget
SubblyCart.js is Subbly's cart and checkout widget. One script tag adds a cart,
a product configurator and a full checkout to any website, in your shop's
colours and languages. It talks to the Storefront API through
[Subbly.js](/reference/subbly-sdk), and it keeps the cart ID and the customer's
access token in cookies, so a cart survives a page load.
## Load
```html title="Embed the widget"
```
The widget reads `window.subblyConfig` the moment it runs, so write the config
object first. Every method and property in this reference belongs to
`window.subblyCart`, set once the `subbly-cart-initialized` event has fired on
`window`.
## Globals
| Global | What it is |
| --- | --- |
| `window.subblyConfig` | The config object you write, read once while the widget loads. |
| `window.SubblyCart` | The widget **class**. Call `SubblyCart.initialize(config)` on it when you set `init: false`. |
| `window.subblyCart` | The initialized widget **instance** — the object every method and property in this reference belongs to. `globalName` renames it. |
| `window.subblyCheckout` | Optional. The item to configure in standalone checkout mode: an object with a `payload` key holding a [`ConfigureItemPayload`](/reference/cart-widget/methods#configure-item), or `null`. |
## Cookies and storage
| Key | Store | What it holds |
| --- | --- | --- |
| `subbly_cart_id` | cookie, path `/`, 365 days | ID of the active cart. |
| `subbly_access_token` | cookie, path `/`, 365 days | Access token of the signed-in customer. |
| `referral_id` | cookie, 30 days | Referral ID from the `r` query parameter, kept for the next visit. |
| `subbly_checkout_payload` | `localStorage` | The item to configure in standalone checkout mode, tied to one shop and cart. |
## Errors
The widget throws a plain `Error` with a readable message —
`The cart is not initialized`, for example. Calls that reach the API reject
with the Subbly.js error object, which carries a `code`: `not_found`,
`bad_request`, `out_of_stock`, `payment_requires_action`,
`user_email_is_not_unique` or `unauthenticated`.
```js title="Handle a failure"
try {
await subblyCart.applyCoupon('WELCOME10')
} catch (err) {
console.warn(err.code ?? err.message)
}
```
To install the widget, wait for it, and set every key of `window.subblyConfig`,
see the [Cart widget article](/developer-resources/cart-widget).
---
## Document: Overview
Learn how Subbly's APIs and SDKs fit together and where to start.
URL: /get-started/overview
# Overview
Subbly gives you two HTTP APIs and a set of JavaScript tools for building
subscription commerce experiences.
## APIs
- **Storefront API**. Public endpoints for carts, checkout, customers, and
subscriptions. Use it from your storefront, app, or any customer-facing
surface. See the [Storefront API reference](/api/storefront).
- **Private API**. Admin endpoints for products, orders, invoices, coupons, and
more. Call it from your backend with a private key. See the
[Private API reference](/api/private).
## SDKs and widgets
- **Subbly.js SDK**. A browser client for the Storefront API.
[Install and manage](/developer-resources/subbly-js-sdk) ·
[Reference](/reference/subbly-sdk).
- **SubblyCart.js widget**. A drop-in cart and checkout for any website.
[Install and manage](/developer-resources/cart-widget) ·
[Reference](/reference/cart-widget).
- **Subbly's MCP server**. Lets an AI agent manage your shop through the
Private API. [Connect](/developer-resources/subbly-mcp/connect-a-client) ·
[Tools](/developer-resources/subbly-mcp/tools).
## Next steps
1. Get your API key and read how to send it in the
[Storefront API](/api/storefront#authentication) or
[Private API](/api/private#authentication) reference.
2. Install the SDK or the widget from the
[Developer resources](/developer-resources/overview) tab.
---
## Document: Subbly's MCP server
Connect an AI agent to your shop through the Subbly's MCP server at mcp.subbly.dev.
URL: /developer-resources/subbly-mcp
# Subbly's MCP server
The MCP server is currently only available to [Subbly X](https://www.subbly.co/x)
merchants. It may open up to more plans later, but there is no ETA. To ask
about access, contact [Subbly support](https://support.subbly.co).
The Subbly's MCP server is a [Model Context Protocol](https://modelcontextprotocol.io)
server. It exposes the Private API as tools, so an AI agent such as Claude
Code, Cursor or VS Code can list your products, ship orders, create coupons
and more, in plain language. Each tool wraps one Private API call and shares
its fields and its responses.
The server runs at `https://mcp.subbly.dev/mcp`. It speaks the streamable
HTTP transport and holds no state between requests. Your Private API key
travels with every request in the `X-API-KEY` header; the server never stores
it, and each key gets its own rate limit.
## In this section
- [Connect a client](/developer-resources/subbly-mcp/connect-a-client) — add
the server to Claude Code, Cursor or VS Code, then try it in the MCP
Inspector.
- [Tools](/developer-resources/subbly-mcp/tools) — the full list of tools,
grouped by the part of the shop they work on.
## Reference
The tools call the [Private API](/api/private). Its reference explains every
field a tool takes and returns.
---
## Document: Subbly.js SDK
Install, configure and update the JavaScript SDK that calls the Storefront API from the browser.
URL: /developer-resources/subbly-js-sdk
# Subbly.js SDK
Subbly.js is Subbly's browser-side JavaScript library, published on npm as
`@subbly/sdk`. It calls the Storefront API for you, holds one cart per page,
and keeps the cart ID and the customer's access token in browser cookies.
The library is browser-first. It reads and writes `document.cookie` directly,
so run it in the browser, not on a server.
## In this section
- [Install](/developer-resources/subbly-js-sdk/install) — add the package from
npm or load it from a CDN, then start it with `Subbly.init`.
- [Configure](/developer-resources/subbly-js-sdk/configure) — pass your API
key, a cart to load and a customer to authenticate.
- [Keep it up to date](/developer-resources/subbly-js-sdk/keep-it-up-to-date)
— pin a version and upgrade on your own schedule.
- [Build a checkout](/developer-resources/subbly-js-sdk/build-a-checkout) —
the short path from an empty cart to a paid order.
- [Handle errors](/developer-resources/subbly-js-sdk/handle-errors) — read the
`code` a failed request rejects with.
- [Run a funnel](/developer-resources/subbly-js-sdk/run-a-funnel) — show
upsell offers before, during and after checkout.
## Reference
The [Subbly.js SDK reference](/reference/subbly-sdk) lists every method, with
parameters, return values and examples.
---
## Document: Developer resources
Install and manage Subbly's SDKs and widgets, and find the references.
URL: /developer-resources/overview
# Developer resources
Subbly gives you two HTTP APIs, a browser SDK and a drop-in cart widget. The
articles in this tab show you how to install and manage the SDK and the widget.
The references list every method, event and setting.
## SDKs and widgets
- **Subbly.js SDK** — `@subbly/sdk`. A browser client for the Storefront API.
It holds one cart per page and keeps the cart ID and the customer's access
token in cookies. Install it from npm or load it from a CDN.
[Install and manage](/developer-resources/subbly-js-sdk) ·
[Run a funnel](/developer-resources/subbly-js-sdk/run-a-funnel) ·
[Reference](/reference/subbly-sdk)
- **SubblyCart.js widget** — `@subbly/cart`. A drop-in cart, product
configurator and checkout for any website. One script tag loads it from
`https://assets.subbly.co/cart/cart-widget.js`.
[Install and manage](/developer-resources/cart-widget) ·
[Reference](/reference/cart-widget)
- **Subbly's MCP server** — a Model Context Protocol server at
`https://mcp.subbly.dev/mcp` that exposes the Private API as tools, so an
AI agent such as Claude Code, Cursor or VS Code can manage your shop.
[Connect](/developer-resources/subbly-mcp/connect-a-client) ·
[Tools](/developer-resources/subbly-mcp/tools)
## APIs
- **Storefront API**. Public endpoints for carts, checkout, customers and
subscriptions, called with a Storefront API key.
[Reference](/api/storefront) ·
[Authentication](/api/storefront#authentication)
- **Private API**. Admin endpoints for products, orders, invoices, coupons and
more, called from your backend with a private key.
[Reference](/api/private) ·
[Authentication](/api/private#authentication)
## Help
Ask a question or get help from [Subbly support](https://support.subbly.co).
---
## Document: SubblyCart.js widget
Install, configure, wire and style the SubblyCart.js widget on any website.
URL: /developer-resources/cart-widget
# SubblyCart.js widget
SubblyCart.js adds a cart, a product configurator and a full checkout to any
website, in your shop's colours and languages. One script tag is enough. The
widget talks to the Storefront API through [Subbly.js](/reference/subbly-sdk),
and it keeps the cart ID and the customer's access token in cookies, so a cart
survives a page load.
## In this section
- [Install](/developer-resources/cart-widget/install) — embed the script and
wait for the widget to be ready.
- [Configure](/developer-resources/cart-widget/configure) — the keys of
`window.subblyConfig`, and how to boot the widget by hand.
- [Keep it up to date](/developer-resources/cart-widget/keep-it-up-to-date) —
how the hosted widget updates, and how to self-host it.
- [Wire it into your page](/developer-resources/cart-widget/wire-it-into-your-page)
— buy links, your own cart button and counter, the container, and the
content slots in the checkout.
- [Drive it from your code](/developer-resources/cart-widget/drive-it-from-your-code)
— call the methods and follow the events.
- [Style it](/developer-resources/cart-widget/style-it) — override the cart
settings at runtime or from your own CSS.
## Reference
The [SubblyCart.js reference](/reference/cart-widget) lists every method,
event, configuration key and CSS variable.
---
## Document: /api/storefront
URL: /api/storefront
The Storefront API is the API behind your storefront: the shop and its
settings, products and bundles, carts and checkout, and the account of the
signed-in customer. It is safe to call from the browser with a Storefront API
key; [Subbly.js](/reference/subbly-sdk) wraps it for you.
## Base URL
```text title="Base URL"
https://api.subbly.co/storefront/v1
```
Every path in this reference is relative to it. Requests and responses are
JSON; send `Content-Type: application/json` with a body.
## Authentication
Send your Storefront API key in the `X-API-KEY` header on every request.
Endpoints about one customer — their addresses, subscriptions, orders, wallet —
also need the customer's access token, in the `Authorization` header as a
bearer token. The [Auth](/api/storefront/auth) endpoints issue the token; it
expires after `expires_in` seconds.
```bash title="Both headers"
curl https://api.subbly.co/storefront/v1/customer \
-H 'X-API-KEY: ' \
-H 'Authorization: Bearer '
```
### Get your API key
Storefront API keys live in the Subbly admin under
[Settings › API keys](https://www.subbly.co/admin/settings/api-keys). In the
**Storefront API keys** card, click **Generate API key**, then copy the key
with the icon next to it. You can keep several keys; the first one is the key
the Subbly website builder uses. A Storefront key is safe to ship in browser
code: it can only read public shop data and act for the customer whose access
token comes with it.
## Pagination
List endpoints take `page` and `per_page` query parameters and return the
items in `data` next to a `pagination` object:
```json title="A page"
{
"pagination": {
"current_page": 1,
"last_page": 4,
"from": 1,
"to": 25,
"total": 92
},
"data": []
}
```
## Errors
The API answers with the usual HTTP status codes. Most `4xx` responses carry a
JSON body with a `message` and a `code` you can branch on; validation errors
(`422`) add an `errors` object keyed by field.
| Status | Meaning |
| --- | --- |
| `400` | The request is malformed, or names a record that is not the customer's. |
| `401` | The API key or the access token is missing or wrong. |
| `402` | The payment needs another step; the body carries the payment intent to confirm. |
| `404` | Nothing at this path, or it belongs to another shop. |
| `410` | The endpoint no longer applies to this record; the description names the replacement. |
| `422` | The body or the query failed validation. |
| `429` | Too many requests; wait and retry. |
There is no `403`. When a customer endpoint is handed a record that belongs to
someone else — another customer's address, pick-up info, payment method or
subscription — it answers `400` with `"code": "bad_request"`. A few guard rules
answer the same way, such as deleting the last payment method in the wallet.
The description of each endpoint lists the errors it can return.
---
## Document: /api/private
URL: /api/private
The Private API gives your server full access to the shop: customers,
subscriptions, orders, invoices, products and more. Call it from a backend
only; a private key in browser code can read and change every customer's data.
## Base URL
```text title="Base URL"
https://api.subbly.co/private/v1
```
Every path in this reference is relative to it. Requests and responses are
JSON; send `Content-Type: application/json` with a body.
## Authentication
Send your Private API key in the `X-API-KEY` header on every request.
```bash title="Authenticated request"
curl https://api.subbly.co/private/v1/customers \
-H 'X-API-KEY: '
```
### Get your API key
Private API keys live in the Subbly admin under
[Settings › API keys](https://www.subbly.co/admin/settings/api-keys). In the
**Private API keys** card, click **Generate API key**, then copy the key with
the icon next to it. You can keep several keys, so give each integration its
own; if one leaks, delete it from the same card and generate a new one. A
Private key can read and change everything in your shop, so keep it on your
server and out of files you commit.
## Pagination
List endpoints take `page` and `per_page` query parameters and return the
items in `data` next to a `pagination` object:
```json title="A page"
{
"pagination": {
"current_page": 1,
"last_page": 4,
"from": 1,
"to": 25,
"total": 92
},
"data": []
}
```
## Errors
The API answers with the usual HTTP status codes. `4xx` responses carry a JSON
body with a `message`; validation errors (`422`) add a `code` and an `errors`
object keyed by field.
| Status | Meaning |
| --- | --- |
| `400` | The request is malformed. |
| `401` | The API key is missing or wrong. |
| `403` | The key is valid but may not do this. |
| `404` | Nothing at this path, or it belongs to another shop. |
| `422` | The body or the query failed validation. |
| `429` | Too many requests; wait and retry. |
The description of each endpoint lists the errors it can return.
---
## Document: /api/orders
URL: /api/orders
The Orders API gives your server the orders of your shop, each with its
delivery address, customer, subscription and line items, and lets you mark
them as shipped with a carrier and a tracking number. It is made for shipping
and fulfillment integrations. Call it from a backend only; the key gives
access to every order and customer of your shop.
## Base URL
```text title="Base URL"
https://www.subbly.co/api/v1
```
Every path in this reference is relative to it. Requests and responses are
JSON; send `Content-Type: application/json` with a body.
## Authentication
Send the API key of your shop as a Bearer token in the `Authorization`
header on every request.
```bash title="Authenticated request"
curl https://www.subbly.co/api/v1/orders \
-H 'Authorization: Bearer '
```
Without a valid key the API answers `401` with `{"message": "Unauthorized
request"}`. The key is the shop API key of the Subbly admin, the same key the
Subbly Zapier app uses. It is not a Private API key; those work with the
[Private API](/api/private) only.
## Pagination
`GET /orders` takes `page` and `per_page` (1 to 100, 15 by default) and
returns the orders in `data` next to the page counters:
```json title="A page"
{
"data": [],
"current_page": 1,
"total": 32,
"first": 1,
"last": 3,
"per_page": 15
}
```
`first` and `last` are page numbers.
## Errors
The API answers with the usual HTTP status codes. Error responses carry a
JSON body with a `message`; validation errors (`422`) add an `errors` object
keyed by field.
| Status | Meaning |
| --- | --- |
| `401` | The API key is missing or wrong. |
| `404` | No order with this ID in your shop. |
| `422` | The body or the query failed validation. |
```json title="Validation error"
{
"message": "Validation Failed",
"errors": {
"per_page": ["The per page must be between 1 and 100."],
"status": ["The selected status is invalid."]
}
}
```
---
## Document: /reference/subbly-sdk/wallet
URL: /reference/subbly-sdk/wallet
import PaymentMethodFields from "../../../src/reference/subbly-sdk/types/PaymentMethod.mdx";
Gets the payment methods the signed-in customer saved for one currency. A
payment method belongs to a gateway, and a gateway belongs to a currency, so a
customer who pays in two currencies has two sets. Needs an authenticated
customer.
Currency to list, such as `USD`. Take it from
`shop.currencies[].abbreviation`.
The saved payment methods.
```js title="List the payment methods"
const paymentMethods = await subbly.wallet.list('USD')
await subbly.cart.update({ paymentMethodId: paymentMethods[0].id })
```
Gets the token a gateway widget needs before it can collect card details. Run it
first, hand the token to the gateway's own SDK, then save what the gateway gives
back with `subbly.wallet.store`.
The gateway to set up.
ID of the gateway, from `shop.currencies[].gateways[].id`.
The token for the gateway widget.
What the token is depends on the gateway. Stripe gives a client secret for
`stripe.confirmCardSetup`. Braintree gives a client token that starts the
Braintree SDK. PayPal gives an order ID for `createOrder` in
`paypal.Buttons`.
```js title="Start a Stripe card setup"
const [gateway] = subbly.shop.currencies[0].gateways
const { token } = await subbly.wallet.setup({ gatewayId: gateway.id })
const { setupIntent } = await stripe.confirmCardSetup(token, {
payment_method: { card: cardElement }
})
```
Saves a payment source for the customer from the token the gateway widget
returned. The saved method can then be set on the cart or on a subscription.
Needs an authenticated customer.
The source to save.
The gateway the token came from.
The token the gateway widget returned. Stripe gives a setup intent ID.
Braintree gives a payment method nonce. PayPal gives an order ID.
Authorize.net gives the encrypted payment data, Base64-encoded.
The payment method saved.
```js title="Save a Stripe card"
const paymentMethod = await subbly.wallet.store({
gatewayId: gateway.id,
token: setupIntent.id
})
await subbly.cart.update({ paymentMethodId: paymentMethod.id })
```
Gets a Subbly-hosted page where Stripe runs 3-D Secure on a new card. Send the
customer to `subblyUrl`; Stripe returns them to the `redirectUrl` you gave.
Needs an authenticated customer.
The gateway to verify with, and where to come back to.
ID of the Stripe gateway.
Page of yours that Stripe sends the customer back to, such as
`https://your-shop.com/checkout/CART_ID`.
Where to send the customer, and how long the link lasts.
ID of the setup intent.
The page you asked Stripe to return to.
When the hosted page stops working.
The Subbly-hosted page to send the customer to.
```js title="Verify a card with 3-D Secure"
const { subblyUrl } = await subbly.wallet.setupIntent({
gatewayId: gateway.id,
redirectUrl: `https://your-shop.com/checkout/${subbly.cart.id}`
})
window.location.assign(subblyUrl)
```
---
## Document: /reference/subbly-sdk/surveys
URL: /reference/subbly-sdk/surveys
import SurveyFields from "../../../src/reference/subbly-sdk/types/Survey.mdx";
Loads a survey with its questions, answers and styling. A survey belongs to a
plan: read `plan.survey.id` on the plan the customer chose, load the survey,
then send the answers as `options` on the cart item.
ID of the survey.
Extra data to include.
Relations to include. `questions.answers.product.parent` loads the
product behind each offer and plan answer, with its parent product.
Extra request headers. Set `x-currency` to price the answers in another
currency. Without it the SDK sends the currency from `subbly.setCurrency`.
The survey.
```js title="Load a survey"
const survey = await subbly.surveys.load(123, {
expand: ['questions.answers.product.parent']
})
```
```js title="Answer it and add the item to the cart"
const survey = await subbly.surveys.load(plan.survey.id)
const [question] = survey.questions
await subbly.cart.addItem({
productId: plan.id,
quantity: 1,
options: [{ questionId: question.id, answers: [{ id: question.answers[0].id }] }]
})
```
---
## Document: /reference/subbly-sdk/subscriptions
URL: /reference/subbly-sdk/subscriptions
import PaginationFields from "../../../src/reference/subbly-sdk/types/Pagination.mdx";
import SubscriptionFields from "../../../src/reference/subbly-sdk/types/Subscription.mdx";
import SubscriptionItemFields from "../../../src/reference/subbly-sdk/types/SubscriptionItem.mdx";
Every method here needs a signed-in customer, and reads or writes only that
customer's subscriptions.
A subscription has a plan, a quantity, and a list of **items**: the subscription
line itself plus any add-ons. Change the whole subscription with `update`,
`updateBundle` and `updatePreferences`; change one line with the `Item` methods.
Most methods take a `params.expand` list, and every method takes a final
`headers` object; see
[Expand parameters](/reference/subbly-sdk#expand-parameters) and
[Currency](/reference/subbly-sdk#currency).
Gets a page of the customer's subscriptions.
Which subscriptions to return. The argument is required by position, so pass
an empty object for the defaults.
Page to return, counted from 1.
How many subscriptions per page.
Keep only these statuses: `active`, `trial`, `pre_order`,
`gift_waiting_to_start`, `cancelled`, `switched` or `expired`.
Relations to include.
Extra request headers.
One page of subscriptions.
The subscriptions on this page.
Where this page sits in the full list.
```js title="List the active subscriptions"
const { data, pagination } = await subbly.subscriptions.list({
statuses: ['active', 'trial'],
expand: ['product.parent']
})
```
Loads one subscription.
ID of the subscription.
Extra data to include.
Relations to include.
Extra request headers.
The subscription.
```js title="Load a subscription"
const subscription = await subbly.subscriptions.load(123, {
expand: ['product.parent', 'discounts']
})
```
Changes the quantity or the metadata of a subscription. Quantity changes need
`shop.settings.subscriptionQuantitySelector`.
ID of the subscription.
The change to make.
New quantity. Send it even when only the metadata changes.
Metafield values to write. Each entry is `{ id, values }`, where `id` is
a metafield ID. A value is `{ id }` for a preset field and
`{ value }` for a free-text one.
Extra data to include in the response.
Relations to include.
Extra request headers.
The subscription, with the change applied.
```js title="Change the quantity"
await subbly.subscriptions.update(123, { quantity: 2 })
```
```js title="Write a metafield value"
await subbly.subscriptions.update(123, {
quantity: subscription.quantity,
metadata: [{ id: 45, values: [{ id: 67 }] }]
})
```
Rewrites the survey answers of a subscription. Send every answer you want to
keep: the call replaces the whole set. This method takes no `params` argument.
ID of the subscription.
The new answers.
One entry per question, as `{ questionId, answers }`. An answer is
`{ content }` for a text or email question, `{ id }` for a select,
multiple, offer or plan question, and `{ id, quantity }` for a quantity
question. Pass `null` to clear the answers.
`true` also applies the answers to orders Subbly already generated.
Extra request headers.
The subscription, with the new answers.
```js title="Answer the survey again"
await subbly.subscriptions.updatePreferences(123, {
preferences: [
{ questionId: 11, answers: [{ id: 22 }] },
{ questionId: 12, answers: [{ content: 'No nuts, please' }] }
],
updateOrders: true
})
```
Rewrites the bundle contents and the bundle preferences of a subscription. Send
the whole list of items: the call replaces what was there. This method takes no
`params` argument.
ID of the subscription.
The new bundle.
The plan the bundle belongs to. May be `null`.
New quantity of the bundle.
What goes in the bundle.
The items picked, as `{ productId, quantity }`. `productId` is a
bundle item's `productId`.
The preferences picked, as `{ attributeId, values }`, where `values`
holds attribute value IDs.
Extra request headers.
The subscription, with the new bundle.
```js title="Swap the bundle contents"
await subbly.subscriptions.updateBundle(123, {
productId: subscription.productId,
quantity: 1,
bundle: {
items: [
{ productId: 456, quantity: 2 },
{ productId: 789, quantity: 1 }
],
preferences: [{ attributeId: 5, values: [9] }]
}
})
```
Loads one item of a subscription: the subscription line itself, or an add-on.
ID of the subscription.
ID of the item, from `subscription.items[].id`.
Extra data to include.
Relations to include.
Extra request headers.
The item.
```js title="Load one item"
const item = await subbly.subscriptions.loadItem(123, 456, {
expand: ['product.parent']
})
```
Changes the quantity or the metadata of one subscription item.
ID of the subscription.
ID of the item.
The change to make.
New quantity of the item.
Metafield values to write, as `{ id, values }`.
Extra data to include in the response.
Relations to include.
Extra request headers.
The item, with the change applied.
```js title="Change the quantity of an add-on"
await subbly.subscriptions.updateItem(123, 456, { quantity: 3 })
```
Rewrites the bundle contents of one subscription item. Unlike `updateBundle`,
this call carries no quantity, and `bundle` is required.
ID of the subscription.
ID of the item.
The new bundle for the item.
The variant or plan the bundle belongs to. May be `null`.
What goes in the bundle.
The items picked, as `{ productId, quantity }`.
The preferences picked, as `{ attributeId, values }`.
Extra data to include in the response.
Relations to include.
Extra request headers.
The item, with the new bundle.
```js title="Swap the contents of one bundle item"
await subbly.subscriptions.updateItemBundle(123, 456, {
productId: item.productId,
bundle: {
items: [{ productId: 789, quantity: 2 }],
preferences: []
}
})
```
Rewrites the survey answers of one subscription item. This method takes no
`params` argument.
ID of the subscription.
ID of the item.
The new answers.
One entry per question, as `{ questionId, answers }`. Pass `null` to
clear the answers.
`true` also applies the answers to orders Subbly already generated.
Extra request headers.
The item, with the new answers.
```js title="Answer the survey for one item"
await subbly.subscriptions.updateItemPreferences(123, 456, {
preferences: [{ questionId: 11, answers: [{ id: 22 }] }]
})
```
---
## Document: /reference/subbly-sdk/stock
URL: /reference/subbly-sdk/stock
Asks Subbly to email the customer when a product or a survey answer is back in
stock. Watch for `stockCount: 0` on a variant, a plan or an answer, and offer
the alert there.
The shop must turn alerts on: `shop.settings.outOfStockEnabled` and
`shop.limits.outOfStock` must both be `true`.
What to watch, and where to send the alert.
Address the alert goes to.
The variant or plan that ran out. Send this or `answerId`, not both.
The survey answer that ran out. Send this or `productId`, not both.
How many units the customer wants.
The alert record.
ID of the record.
The shop the record belongs to.
The product being watched.
Address the alert goes to.
How many units the customer wants.
ID of the watched product or survey answer.
What is being watched.
When the item came back in stock. Empty until it does.
When the record was created.
When the record last changed.
```js title="Watch a variant"
await subbly.stock.subscribe({
email: 'me@example.com',
productId: 456,
quantity: 1
})
```
```js title="Watch a survey answer"
await subbly.stock.subscribe({
email: 'me@example.com',
answerId: 789
})
```
---
## Document: /reference/subbly-sdk/shop
URL: /reference/subbly-sdk/shop
import SubblyShopFields from "../../../src/reference/subbly-sdk/types/SubblyShop.mdx";
Loads the settings, branding, currencies, gateways and apps of the shop.
`Subbly.init` calls this for you, so `subbly.shop` already holds the fields
below by the time you get the SDK instance. Call it again only to pick up a
change.
`subbly.shop` is a model object: it carries the shop fields and the `load`
method together. `load` copies the response onto that same object and returns
it, so the value you get back and `subbly.shop` are one object.
The shop object, with its fields refreshed.
```js title="Load the shop"
const shop = await subbly.shop.load()
console.log(shop.name, shop.country.currencyCode)
```
```js title="Read a setting before you draw a form"
if (subbly.shop.settings.collectCompanyName) {
showCompanyNameField()
}
const currencies = subbly.shop.currencies.map((c) => c.abbreviation)
```
---
## Document: /reference/subbly-sdk/setup
URL: /reference/subbly-sdk/setup
Starts Subbly.js with your Storefront API key and resolves with the SDK
instance. Every other method in this reference is a method of that instance.
`init` always loads the shop first, so `subbly.shop` is filled in by the time
the promise resolves. It then checks the stored access token, and, when you pass
`loadCart`, loads or creates a cart and copies the cart currency onto the
instance. A failed shop load rejects the promise.
Call `init` once per page. The SDK builds one shared HTTP client and keeps it,
so a second call with a different `apiKey`, `apiUrl` or `lang` quietly reuses
the first one.
How to start the SDK. Leave it out, or pass an empty `apiKey`, and `init`
rejects with `Error('Missing required apiKey config')`.
Your Storefront API key. The SDK sends it as the `x-api-key` header on
every request, and you get it from
[Shop settings](https://subbly.co/admin/settings/api-keys) in Subbly Admin.
Base URL of the Storefront API. Defaults to
`https://api.subbly.co/storefront/v1/`. Change it only for a proxy or a
test environment. An empty string falls back to the default.
Language code, sent as the `accept-language` header. Empty by default, so
the server picks. Use a code from `subbly.shop.languages[].code`, and
change it later with `subbly.setLanguage`.
Cart to load at start-up. The SDK reads it only when `loadCart` is true,
and falls back to the `subbly_cart_id` cookie when you leave it out.
Load or create a cart during start-up. `false` by default. When it is
true, the SDK loads the cart named by `cartId` or the cookie, creates a
new cart when there is none or the load fails, writes the
`subbly_cart_id` cookie, and calls `subbly.setCurrency` with the cart
currency. When it is false, `subbly.cart` has no `id` and the cart
methods that need one fail.
Customer access token to verify at start-up. It beats the
`subbly_access_token` cookie, and the SDK writes it to that cookie when
the check passes. A failed check clears the cookie; it does not reject.
The SDK instance.
Version of the SDK package.
The `apiKey`, `apiUrl` and `lang` the SDK started with.
The cart: every field of the cart object, plus the cart methods. The
[Cart](/reference/subbly-sdk/cart) page writes the fields out.
The shop: every field of the shop object, plus `shop.load`. The
[Shop](/reference/subbly-sdk/shop) page writes the fields out.
The rest of the namespaces, one per group in this reference. They are all
built during `init` and are always present.
```js title="Start the SDK"
import Subbly from '@subbly/sdk'
const subbly = await Subbly.init({
apiKey: 'API_KEY',
})
```
```js title="Start with a cart and a customer"
const subbly = await Subbly.init({
apiKey: 'API_KEY',
cartId: 'aaea067c-6364-4157-82e9-71b6edfd84a0',
loadCart: true,
accessToken: 'eyJh...eyJ9',
})
```
Sets the currency for the prices in later product, bundle and survey responses.
The SDK sends it as the `x-currency` header.
It only fans the code out to `subbly.products.setCurrency`,
`subbly.bundles.setCurrency` and `subbly.surveys.setCurrency`, which each store
it for their own requests; an `x-currency` header you pass on a single call
still wins.
This does not change the cart currency. Use
`subbly.cart.update({ currencyCode })` for that. When `init` loads a cart, it
calls `setCurrency` with the cart currency itself.
A currency abbreviation the shop supports, such as `USD`. Read the list from
`subbly.shop.currencies[].abbreviation`.
Nothing.
```js title="Set the currency"
subbly.setCurrency('EUR')
// Later product prices come back in euros.
const products = await subbly.products.list()
```
Sets the `accept-language` header the SDK sends with every later request.
A language code the shop supports. Read the list from
`subbly.shop.languages[].code`.
Nothing.
```js title="Set the language"
subbly.setLanguage('fr')
```
A read-only property with the version of the `@subbly/sdk` package the page
loaded. Useful in a bug report.
The version of the `@subbly/sdk` package on the page.
```js title="Read the version"
console.log(subbly.VERSION)
```
The three client values `init` received, exactly as you passed them. It leaves
out `cartId`, `loadCart` and `accessToken`, and it does not follow a later
`setLanguage` call.
`subbly.config` is not the configuration in use. The SDK applies its defaults
deeper in, when it builds the HTTP client, so a value you left out stays
`undefined` here.
The values passed to `init`.
The Storefront API key you passed.
The base URL you passed, or `undefined`. The requests still go to
`https://api.subbly.co/storefront/v1/` in that case.
The language code you passed, or `undefined`. The server then picks the
language.
```js title="Read the configuration"
const subbly = await Subbly.init({ apiKey: 'API_KEY' })
subbly.config.apiKey // 'API_KEY'
subbly.config.apiUrl // undefined
subbly.config.lang // undefined
```
---
## Document: /reference/subbly-sdk/products
URL: /reference/subbly-sdk/products
import PaginationFields from "../../../src/reference/subbly-sdk/types/Pagination.mdx";
import ProductOneTimeFields from "../../../src/reference/subbly-sdk/types/ProductOneTime.mdx";
import ProductSubscriptionFields from "../../../src/reference/subbly-sdk/types/ProductSubscription.mdx";
import ProductVariantFields from "../../../src/reference/subbly-sdk/types/ProductVariant.mdx";
import ProductPlanFields from "../../../src/reference/subbly-sdk/types/ProductPlan.mdx";
A shop sells two kinds of product. A one-time product holds **variants**; a
subscription product holds **plans**. A parent product is what you list and show
on a page; a variant or a plan is what a customer buys, and its `id` is the
`productId` you send to `subbly.cart.addItem`.
Every method here takes an optional `headers` argument to price one response in
another currency; see [Currency](/reference/subbly-sdk#currency).
Gets a page of the shop's parent products.
Which products to return.
Page to return, counted from 1.
How many products per page.
Keep only products with these tags.
Keep only one-time products, or only subscription products.
Keep only products with these slugs.
`true` keeps only digital products, `false` only physical ones.
`true` keeps only gift cards, `false` leaves them out.
Relations to include: `variants`, `variants.parent`, `plans`,
`plans.parent` and `metadata`. Without them a product carries no
buyable children.
Extra request headers, such as `x-currency`.
One page of products.
The products on this page. Each one is a one-time product or a subscription
product; narrow on `type`.
Where this page sits in the full list.
```js title="List the products"
const { data, pagination } = await subbly.products.list({
page: 1,
perPage: 10,
expand: ['variants.parent']
})
```
```js title="List the subscription products only"
const { data } = await subbly.products.list({
type: 'subscription',
expand: ['plans']
})
```
Loads one parent product, with its variants or its plans.
ID of the parent product.
Extra data to include.
Relations to include: `variants`, `variants.parent`, `plans`,
`plans.parent` and `metadata`.
Extra request headers, such as `x-currency`.
The product. Narrow on `type` to read `variants` or `plans`.
```js title="Load a product"
const product = await subbly.products.load(123, {
expand: ['variants.parent', 'plans.parent']
})
const buyable = product.type === 'subscription' ? product.plans : product.variants
```
Loads one product variant, the buyable child of a one-time product.
Pass a **variant** ID, not the ID of its parent. The endpoint behind this method
also serves plans, so the declared result is a variant or a plan; use
`subbly.products.loadPlan` when you hold a plan ID.
ID of the variant.
Extra data to include.
Relations to include. `parent` loads the product the variant belongs to.
Extra request headers, such as `x-currency`.
The variant, or the plan when the ID belongs to one.
```js title="Load a variant"
const variant = await subbly.products.loadVariant(456, { expand: ['parent'] })
await subbly.cart.addItem({ productId: variant.id, quantity: 1 })
```
Loads one subscription plan, the buyable child of a subscription product. Older
material calls a plan a pricing.
`loadPlan` and `loadVariant` call the same endpoint; only the type of the result
differs. Use `loadPlan` when the ID belongs to a plan.
ID of the plan.
Extra data to include.
Relations to include. `parent` loads the product the plan belongs to.
Extra request headers, such as `x-currency`.
The plan.
```js title="Load a plan"
const plan = await subbly.products.loadPlan(789, { expand: ['parent'] })
console.log(plan.frequencyCount, plan.frequencyUnit)
```
---
## Document: /reference/subbly-sdk/pickup-info
URL: /reference/subbly-sdk/pickup-info
import CustomerPickupInfoFields from "../../../src/reference/subbly-sdk/types/CustomerPickupInfo.mdx";
Gets the pick-up contact records the signed-in customer saved. A record says who
collects the parcel, and which point they collect it from. Needs an
authenticated customer.
Which records to return. The argument is required by position, so pass an
empty object for all of them.
Keep only the records that work with this pick-up shipping method. In
practice this filters by country.
The saved records.
```js title="List every record"
const pickupInfos = await subbly.pickupInfo.list({})
```
```js title="List the records for one pick-up method"
const [method] = await subbly.cart.getLocalPickups()
const pickupInfos = await subbly.pickupInfo.list({ shippingMethodId: method.id })
```
Creates a pick-up contact record for the signed-in customer. Put its `id` on the
cart as `shippingAddressId` once the customer chose a pick-up point. Needs an
authenticated customer.
The record to create.
Given name of the person who collects the parcel.
Family name of that person.
Phone number of that person.
Set this only for a point in an external network. `mondial_relay` is
the one value the field takes.
ID of the point in that external network.
The record created.
```js title="Add a record"
const pickupInfo = await subbly.pickupInfo.store({
firstName: 'Ada',
lastName: 'Lovelace',
phone: '+15551234567'
})
await subbly.cart.update({ shippingAddressId: pickupInfo.id })
```
```js title="Add a record for a Mondial Relay point"
await subbly.pickupInfo.store({
firstName: 'Ada',
lastName: 'Lovelace',
phone: '+33123456789',
pickupPointType: 'mondial_relay',
pickupPointId: '005670'
})
```
Deletes a pick-up contact record. Needs an authenticated customer. The request
fails while a subscription still uses the record.
ID of the record to delete.
Nothing. The response body is empty.
```js title="Delete a record"
await subbly.pickupInfo.delete(123)
```
---
## Document: /reference/subbly-sdk/payment-intents
URL: /reference/subbly-sdk/payment-intents
import PaymentIntentResponseFields from "../../../src/reference/subbly-sdk/types/PaymentIntentResponse.mdx";
Gets a payment intent, including the client secret Stripe needs to run 3-D
Secure. Call it when `subbly.checkout.purchase` rejects with
`code: 'payment_requires_action'`; the rejection carries the ID to pass here.
Needs an authenticated customer.
ID of the payment intent. Read it from `err.paymentIntentId` on the purchase
error.
The payment intent.
```js title="Get the client secret"
const { token } = await subbly.paymentIntents.getPaymentIntent(err.paymentIntentId)
const { paymentIntent } = await stripe.confirmCardPayment(token)
```
Tells Subbly that Stripe finished verifying the payment intent. Call
`subbly.checkout.purchase` again afterwards to finish the order. Needs an
authenticated customer.
ID of the Subbly payment intent, from `err.paymentIntentId`.
ID Stripe gave back: the `paymentIntent.id` from `stripe.confirmCardPayment`.
The payment intent, with its new status.
```js title="Finish a 3-D Secure purchase"
try {
await subbly.checkout.purchase(subbly.cart.id)
} catch (err) {
if (err.code !== 'payment_requires_action') throw err
const { token } = await subbly.paymentIntents.getPaymentIntent(err.paymentIntentId)
const { paymentIntent } = await stripe.confirmCardPayment(token)
await subbly.paymentIntents.confirm(err.paymentIntentId, paymentIntent.id)
await subbly.checkout.purchase(subbly.cart.id)
}
```
---
## Document: /reference/subbly-sdk/metafields
URL: /reference/subbly-sdk/metafields
import MetafieldFields from "../../../src/reference/subbly-sdk/types/Metafield.mdx";
import PaginationFields from "../../../src/reference/subbly-sdk/types/Pagination.mdx";
Gets a page of the metafield definitions of the shop. Metafields are the custom
fields a shop adds to a customer, a subscription or a product. Read them to
build your own form, then write the answers back as `metadata` on the cart or
on a subscription.
The namespace is singular: `subbly.metafield`, not `subbly.metafields`.
Which definitions to return. The argument is required by position, so pass
an empty object for the defaults.
Keep only these metafield IDs.
Keep only these slugs.
Page to return, counted from 1.
How many definitions per page.
`true` keeps only preset fields, which hold a fixed list of choices.
`false` keeps only free-text fields.
Relations to include in the response.
One page of definitions.
The definitions on this page.
Where this page sits in the full list.
```js title="List the metafields"
const { data, pagination } = await subbly.metafield.list({
page: 1,
perPage: 20
})
```
```js title="Load one definition by slug"
const { data } = await subbly.metafield.list({ slugs: ['coffee-strength'] })
const [strength] = data
```
---
## Document: /reference/subbly-sdk/lead
URL: /reference/subbly-sdk/lead
Saves an email address as a lead. Use it to catch an address early, such as on
a newsletter box or the first step of a survey, before the customer reaches
checkout.
The lead to save.
Email address of the lead.
Given name of the lead.
Family name of the lead.
Your own data to keep with the lead, such as the page it came from.
The lead record Subbly saved.
ID of the lead.
The email address saved.
Given name of the lead.
Family name of the lead.
IP address Subbly saw on the request.
Browser Subbly saw on the request.
```js title="Capture a lead"
const lead = await subbly.lead.subscribe({
email: 'me@example.com',
firstName: 'Ada',
metadata: { source: 'footer-form' }
})
```
---
## Document: /reference/subbly-sdk/funnels
URL: /reference/subbly-sdk/funnels
import FunnelStepFields from "../../../src/reference/subbly-sdk/types/FunnelStep.mdx";
import SubblyCartFields from "../../../src/reference/subbly-sdk/types/SubblyCart.mdx";
import CheckoutPurchaseResponseFields from "../../../src/reference/subbly-sdk/types/CheckoutPurchaseResponse.mdx";
A funnel has three phases: **pre-purchase**, **mid-purchase** and
**post-purchase**. In each phase you fetch the next step, then accept it or skip
it, and fetch again until `step` is `null`. For the full flow, with one example
per phase, see [Run a funnel](/developer-resources/subbly-js-sdk/run-a-funnel).
Gets the next pre-purchase offer for the cart. `step` comes back `null` once the
customer has seen every offer.
ID of the cart, usually `subbly.cart.id`.
Extra data to include.
Relations to include. `parent` loads the parent product of each offered
variant or plan.
The next offer, or an empty one.
The offer to show. `null` means there is nothing left to offer.
```js title="Show the next offer"
const { step } = await subbly.funnels.prePurchaseFetch(subbly.cart.id, {
expand: ['parent']
})
if (step) render(step)
```
Accepts a pre-purchase offer and adds its products to the cart.
The cart it returns is a plain object, not the `subbly.cart` model, so
`subbly.cart` still holds the old contents. Reload it with `subbly.cart.load()`.
ID of the cart.
ID of the step the customer accepted, from `step.id`.
The products the customer took.
One entry per product taken.
The variant or plan the customer picked, from
`step.offer.products[].id`.
Survey answers, when the offered plan has a survey. Each entry is
`{ questionId, answers }`. An answer is `{ content }` for a text or
email question, `{ id }` for a select, multiple, offer or plan
question, and `{ id, quantity }` for a quantity question.
Extra data to include on the cart that comes back.
Cart relations to include, such as `items.product` and
`items.product.parent`.
The cart after the offer went in.
```js title="Accept an offer"
await subbly.funnels.prePurchaseAccept(subbly.cart.id, step.id, {
offers: [{ productId: step.offer.products[0].id }]
})
await subbly.cart.load()
```
Skips a pre-purchase offer. Subbly records the refusal, so the next fetch moves
on to the following step.
ID of the cart.
ID of the step the customer skipped.
Nothing. The response body is empty.
```js title="Skip an offer"
await subbly.funnels.prePurchaseReject(subbly.cart.id, step.id)
const { step: next } = await subbly.funnels.prePurchaseFetch(subbly.cart.id)
```
Clears which pre-purchase offers the customer accepted or skipped, so the
sequence starts from the first step again. Only the pre-purchase phase has this.
ID of the cart.
Nothing you need to read. Treat the response as empty.
```js title="Start the offers again"
await subbly.funnels.prePurchaseRefresh(subbly.cart.id)
```
Gets the next mid-purchase offer for the cart. Show these while the customer
works through checkout.
ID of the cart.
Extra data to include.
Relations to include. `parent` loads the parent product of each offered
variant or plan.
The next offer, or an empty one.
The offer to show. `null` means there is nothing left to offer.
```js title="Show the next offer"
const { step } = await subbly.funnels.midPurchaseFetch(subbly.cart.id)
```
Accepts a mid-purchase offer and adds its products to the cart. As with the
pre-purchase accept, reload `subbly.cart` afterwards.
ID of the cart.
ID of the step the customer accepted.
The products the customer took.
One entry per product taken.
The variant or plan the customer picked.
Survey answers, when the offered plan has a survey. Each entry is
`{ questionId, answers }`.
Extra data to include on the cart that comes back.
Cart relations to include.
The cart after the offer went in.
```js title="Accept an offer"
await subbly.funnels.midPurchaseAccept(subbly.cart.id, step.id, {
offers: [{ productId: step.offer.products[0].id }]
})
await subbly.cart.load()
```
Skips a mid-purchase offer so the next fetch moves on.
ID of the cart.
ID of the step the customer skipped.
Nothing. The response body is empty.
```js title="Skip an offer"
await subbly.funnels.midPurchaseReject(subbly.cart.id, step.id)
```
Gets the next post-purchase offer for the cart. These run only after
`subbly.checkout.purchase` succeeds, on the thank-you page.
ID of the cart that was paid.
Extra data to include.
Relations to include. `parent` loads the parent product of each offered
variant or plan.
The next offer, or an empty one.
The offer to show. `null` means there is nothing left to offer.
```js title="Show the next offer after checkout"
await subbly.checkout.purchase(subbly.cart.id)
const { step } = await subbly.funnels.postPurchaseFetch(subbly.cart.id)
```
Accepts a post-purchase offer and charges it as a new purchase on the card the
customer already used. Unlike the other two accepts, this one returns the
invoice, subscription and orders it created. Needs an authenticated customer.
ID of the cart that was paid.
ID of the step the customer accepted.
The products taken, and anything the purchase still needs.
One entry per product taken.
The variant or plan the customer picked.
Survey answers, when the offered plan has a survey.
Address to ship to. Send it when the checkout did not set one.
Delivery option to use. Send it when the checkout did not set one.
The payment intent you confirmed, when you retry after 3-D Secure.
Extra data to include in the response.
Relations to include, such as `subscription.product` and
`order.metadata`.
What the extra purchase created.
```js title="Accept a post-purchase offer"
const { invoice, orders } = await subbly.funnels.postPurchaseAccept(
subbly.cart.id,
step.id,
{ offers: [{ productId: step.offer.products[0].id }] }
)
```
Skips a post-purchase offer so the next fetch moves on.
ID of the cart that was paid.
ID of the step the customer skipped.
Nothing. The response body is empty.
```js title="Skip an offer"
await subbly.funnels.postPurchaseReject(subbly.cart.id, step.id)
```
---
## Document: /reference/subbly-sdk/customers
URL: /reference/subbly-sdk/customers
import CustomerFields from "../../../src/reference/subbly-sdk/types/SubblyCustomer.mdx";
Every method here needs a signed-in customer. Sign one in with the
[authentication methods](/reference/subbly-sdk/auth) first.
Gets the profile of the signed-in customer. `subbly.auth.checkAuthenticated`
calls it behind the scenes, so a failure here means the token no longer works.
The customer profile.
```js title="Get the customer profile"
const customer = await subbly.customers.me()
```
Changes the profile of the signed-in customer. Send only the fields you change.
The fields to change. Every one is optional.
Given name.
Family name.
Whether the customer agreed to marketing email.
Whether the customer accepted the terms. Sending `true` stamps
`tosConsentAt` with the current time.
Your own ID for this customer.
The updated profile.
```js title="Update the customer"
const customer = await subbly.customers.update({
firstName: 'Ada',
marketingConsent: true,
})
```
Gets the referral link of the signed-in customer, to show on a "refer a friend"
page. Referrals must be switched on for the shop; check
`subbly.shop.referralSettings.enabled` first.
The referral link.
The URL the customer shares.
```js title="Get the referral link"
const { referralLink } = await subbly.customers.referral()
```
---
## Document: /reference/subbly-sdk/countries
URL: /reference/subbly-sdk/countries
import CountryFields from "../../../src/reference/subbly-sdk/types/Country.mdx";
Gets every country Subbly supports, with its regions. Use it to fill a country
select, and use the `id` of the chosen country as `countryId` in addresses and
in `subbly.cart.getShippingMethods`.
This is the full list. The countries your shop actually ships to are in
`subbly.shop.shippingCountries`, which has the same shape.
The countries.
```js title="List the countries"
const countries = await subbly.countries.list()
```
```js title="Offer only the countries the shop ships to"
const options = subbly.shop.shippingCountries.map(({ id, name }) => ({
value: id,
label: name,
}))
```
---
## Document: /reference/subbly-sdk/checkout
URL: /reference/subbly-sdk/checkout
import PurchaseFields from "../../../src/reference/subbly-sdk/types/CheckoutPurchaseResponse.mdx";
Charges the cart and creates the subscription, the invoice and the orders. It
needs a signed-in customer, and the cart must be ready: a customer attached, a
delivery method set and a payment method chosen.
The SDK does not touch `subbly.cart` afterwards. The cart is complete on the
server, so drop your local copy and create a new one for the next order.
A card that needs 3-D Secure rejects with `code: 'payment_requires_action'` and
a `paymentIntentId`. Take the customer through the verification with
`subbly.paymentIntents.getPaymentIntent` and
`subbly.paymentIntents.confirm`, then call `purchase` again with the same
`paymentIntentId`. The call can also reject with `out_of_stock` or
`bad_request`.
UUID of the cart to charge, usually `subbly.cart.id`.
How to pay. Leave it out to charge the payment method already on the cart.
Payment method to charge, from `subbly.wallet.list`. Falls back to
`cart.paymentMethodId`.
The payment intent the customer just verified. Send it when you retry
after 3-D Secure.
Relations to include in the response.
`subscription.product`, `subscription.product.parent`,
`subscription.product.parent.bundle`, `subscription.metadata`,
`order.metadata`, `order.items.metadata`, `invoice.metadata` and
`invoice.items.metadata`.
What the purchase created.
```js title="Purchase the cart"
const { invoice, subscription, orders } = await subbly.checkout.purchase(
subbly.cart.id,
)
```
```js title="Handle 3-D Secure"
try {
await subbly.checkout.purchase(subbly.cart.id)
} catch (error) {
if (error.code !== 'payment_requires_action') throw error
const intent = await subbly.paymentIntents.getPaymentIntent(
error.paymentIntentId,
)
// Verify `intent.token` with the payment gateway, then:
await subbly.paymentIntents.confirm(intent.id, externalPaymentId)
await subbly.checkout.purchase(subbly.cart.id, {
paymentIntentId: intent.id,
})
}
```
---
## Document: /reference/subbly-sdk/cart
URL: /reference/subbly-sdk/cart
import CartFields from "../../../src/reference/subbly-sdk/types/SubblyCart.mdx";
import AddressPayloadFields from "../../../src/reference/subbly-sdk/types/AddressStorePayload.mdx";
import SurveyAnswerFields from "../../../src/reference/subbly-sdk/types/SurveyAnswers.mdx";
import MetadataFields from "../../../src/reference/subbly-sdk/types/MetadataObject.mdx";
import DeliveryFields from "../../../src/reference/subbly-sdk/types/ShippingMethodDelivery.mdx";
import PickupFields from "../../../src/reference/subbly-sdk/types/ShippingMethodPickup.mdx";
import StartDateFields from "../../../src/reference/subbly-sdk/types/StartDates.mdx";
`subbly.cart` is both the data and the methods. It carries every cart field
straight on the object — `subbly.cart.id`, `subbly.cart.items`,
`subbly.cart.total` — and the methods below change it. Each of those methods
copies the response onto the same object and resolves with it, so the value you
`await` and `subbly.cart` are one and the same. There is only ever one cart per
SDK instance, and fields the response leaves out keep their old values.
Amounts are whole numbers in the minor unit of the cart currency; see
[Amounts and dates](/reference/subbly-sdk#amounts-and-dates).
Most methods take an optional `params.expand` list; the values it accepts are
under [Expand parameters](/reference/subbly-sdk#expand-parameters).
Creates a new cart and stores its ID in the `subbly_cart_id` cookie for 365
days. It replaces whatever cart the SDK already held, so call it once, and use
`subbly.cart.load` to come back to a cart you already have.
The cookie is a plain one on `path=/`, with no `Secure`, `HttpOnly` or
`SameSite` attribute.
What the new cart starts with. Pass `null` or leave it out for an empty
cart. The TypeScript type is the wider update payload, but the create
endpoint reads only the four fields below; set anything else with
`subbly.cart.update` afterwards.
Currency of the cart, such as `EUR`. Defaults to the shop currency.
Coupon code to apply straight away.
Gift card code to apply straight away.
ID of the referral that led to this cart.
Relations to include.
The relations to fill in. The values are listed at the top of this page.
The cart, which is `subbly.cart` itself.
```js title="Create a cart"
const cart = await subbly.cart.create()
```
```js title="Create a cart in a currency, with a coupon"
const cart = await subbly.cart.create(
{ currencyCode: 'EUR', couponCode: 'WELCOME10' },
{ expand: ['coupon', 'items.product'] },
)
```
Loads a cart by ID into `subbly.cart`. Leave the ID out to refresh the cart the
SDK already holds.
Unlike `create`, this does not write the cookie. `Subbly.init` writes it itself
after a successful load.
UUID of the cart. Leave it out to reload the cart already held. With no ID and
no cart held, the call throws `Error('Cart ID is required')` at once, without
reaching the network.
Relations to include.
The relations to fill in. The values are listed at the top of this page.
The cart, which is `subbly.cart` itself. Its fields are written out under
[Create a cart](#create).
```js title="Load a cart by ID"
const cart = await subbly.cart.load('aaea067c-6364-4157-82e9-71b6edfd84a0')
```
```js title="Refresh the cart in hand"
await subbly.cart.load(undefined, { expand: ['items.product'] })
```
Changes the cart itself rather than its items: who is buying, where it ships,
how it ships, which discounts apply, and when a subscription starts. Send only
the fields you change.
Most of it works for a guest. Three fields point at records only a signed-in
customer has: `paymentMethodId`, `shippingAddressId` and `billingAddressId`.
The fields to change. Every one is optional.
Guest details on the cart. Use it before the customer signs in.
Email address.
Given name.
Family name.
Whether the guest agreed to marketing email.
Whether the guest accepted the terms.
Your own ID for this guest.
A delivery address written straight onto the cart, for a guest with no
saved addresses.
A billing address written straight onto the cart. Same fields as
`shippingAddress`.
Who collects the order at a pick-up point.
Given name of the person collecting.
Family name of the person collecting.
Phone number.
`mondial_relay` when the point comes from Mondial Relay.
ID of the point at the provider.
Currency of the cart. This is the only way to change it.
Coupon code to apply. Send `null` to take the coupon off.
Gift card code to apply. Send `null` to take the gift card off.
ID of the referral that led to this cart.
Gift settings for the cart.
When the gift starts, from `subbly.cart.getGiftingDates()`.
How many shipments the gift covers.
Message for the recipient.
Email of the recipient.
When the subscription starts, from `subbly.cart.getStartDates()`.
ID of the delivery option, from `subbly.cart.getShippingMethods` or
`subbly.cart.getLocalPickups`.
ID of the carrier, from the `carrier` of a shipping option.
Carrier service code, from `carrier.serviceCodes`.
ID of a saved address or pick-up info of the signed-in customer.
ID of a saved address of the signed-in customer.
ID of a saved payment method, from `subbly.wallet.list`.
ID of the onboarding template to apply.
Metafield values to save on the cart.
Relations to include.
The relations to fill in. The values are listed at the top of this page.
The updated cart, which is `subbly.cart` itself. Its fields are written out
under [Create a cart](#create).
```js title="Set the guest details"
await subbly.cart.update({
customer: {
email: 'ada@example.com',
firstName: 'Ada',
lastName: 'Lovelace',
tosConsent: true,
},
})
```
```js title="Set the delivery address and the method"
await subbly.cart.update({
shippingAddress: {
firstName: 'Ada',
lastName: 'Lovelace',
phone: '+442071234567',
addressOne: '12 Marylebone Road',
addressTwo: null,
city: 'London',
zip: 'NW1 5JD',
countryId: 826,
},
shippingMethodId: 91,
})
```
```js title="Remove the coupon"
await subbly.cart.update({ couponCode: null })
```
Adds a product, a plan or a bundle to the cart. Adding the same one-time item
again raises its quantity instead of making a second line.
The payload has a shape per kind of item. All three share `productId`,
`quantity` and `metadata`; the rest depends on what you are adding.
The item to add.
What to add: a variant ID for a one-time product, a plan ID for a
subscription.
How many. Defaults to 1.
Metafield values to save on the line.
`true` adds the item alongside a subscription already in the cart.
How long the add-on rides along: `1` for one charge, `0` for every
charge.
Who a gift card goes to.
Email of the recipient.
Name of the recipient.
Message for the recipient.
The customer's answers to the plan's survey, one entry per question.
What goes inside the bundle.
The picked items, each `{ productId, quantity }` from
`subbly.bundles.loadItems`.
The answers to the bundle preferences, each
`{ attributeId, values }`. Send `[]` when there are none.
Survey answers, when the bundle plan also has a survey. Same shape as
for a subscription.
Relations to include.
The relations to fill in. The values are listed at the top of this page.
The updated cart, which is `subbly.cart` itself. Its fields are written out
under [Create a cart](#create). The call rejects with `code: 'out_of_stock'`
when the product ran out.
```js title="Add a one-time product"
await subbly.cart.addItem({ productId: 3301, quantity: 2 })
```
```js title="Add a subscription with survey answers"
await subbly.cart.addItem({
productId: 8801,
options: [
{ questionId: 11, answers: [{ id: 41 }] },
{ questionId: 12, answers: [{ content: 'No nuts, please' }] },
],
})
```
```js title="Add a bundle"
await subbly.cart.addItem({
productId: 8801,
quantity: 1,
bundle: {
items: [
{ productId: 3301, quantity: 2 },
{ productId: 3307, quantity: 1 },
],
preferences: [{ attributeId: 9, values: [31] }],
},
})
```
Changes one line in the cart: the quantity, the survey answers, the gift details
or the bundle contents. To change which product a line is for, remove it and add
the new one.
UUID of the line, from `cart.items[].id`.
What to change. Every field is optional, and the shape follows the kind of
item the line holds.
How many.
`true` keeps the item alongside a subscription in the cart.
`1` for one charge, `0` for every charge.
Who a gift card goes to, with `customerEmail`, `customerName` and
`message`.
The survey answers, one entry per question. Send the full set; it
replaces what was there.
The new bundle contents, with `items` and `preferences` exactly as in
`subbly.cart.addItem`.
Survey answers, when the bundle plan also has a survey.
Relations to include.
The relations to fill in. The values are listed at the top of this page.
The updated cart, which is `subbly.cart` itself. Its fields are written out
under [Create a cart](#create).
```js title="Change the quantity"
const [line] = subbly.cart.items
await subbly.cart.updateItem(line.id, { quantity: 3 })
```
Removes one line from the cart.
UUID of the line, from `cart.items[].id`.
Relations to include.
The relations to fill in. The values are listed at the top of this page.
The updated cart, which is `subbly.cart` itself. Its fields are written out
under [Create a cart](#create).
```js title="Remove an item"
const [line] = subbly.cart.items
await subbly.cart.removeItem(line.id)
```
Gets the dates on which the subscription in the cart can start. Send the one the
customer picks to `subbly.cart.update` as `startsAt`.
It uses the cart the SDK holds, so load or create a cart first.
The dates allowed. The API fills one of the two fields and sets the other to
`null`: a fixed list of dates, or an open range. Handle both.
```js title="Get the start dates"
const { availableDates, range } = await subbly.cart.getStartDates()
const startsAt = availableDates?.length ? availableDates[0] : range?.[0]
await subbly.cart.update({ startsAt })
```
Gets the dates on which a gift subscription in the cart can start. Send the one
the customer picks to `subbly.cart.update` as `giftInfo.startsAt`.
The dates allowed, in the same shape `subbly.cart.getStartDates` returns.
```js title="Get the gifting dates"
const { availableDates, range } = await subbly.cart.getGiftingDates()
const startsAt = availableDates?.length ? availableDates[0] : range?.[0]
await subbly.cart.update({
giftInfo: {
startsAt,
numberOfOrders: 3,
message: 'Happy birthday',
recipientEmail: 'grace@example.com',
},
})
```
Gets the delivery options available for the cart at a destination. Send the ID
of the one the customer picks to `subbly.cart.update` as `shippingMethodId`.
Only `countryId` and `zip` are needed. Pass the rest when a carrier quotes on
the full address.
Where the order is going.
ID of the country, from `subbly.countries.list()`. The field is
`countryId`, not `country`.
Postal or ZIP code of the destination.
ID of the region, from the country's `regions[].id`.
Given name of the recipient.
Family name of the recipient.
Phone number of the recipient.
First address line.
Second address line.
City.
Relations to include: `method` and `carrier`.
The delivery options. Read `type` first: it is `shipping_option` for a carrier
delivery and `local_delivery` for the shop's own, and it decides which of the
fields below are set.
```js title="Get the shipping methods"
const methods = await subbly.cart.getShippingMethods({
countryId: 826,
zip: 'NW1 5JD',
expand: ['carrier'],
})
await subbly.cart.update({ shippingMethodId: methods[0].id })
```
Gets the pick-up points available for the cart. Send the ID of the one the
customer picks to `subbly.cart.update` as `shippingMethodId`, along with
`pickupInfo` for who collects the order.
The pick-up points. The array is empty when the shop offers none.
```js title="Get the pick-up points"
const points = await subbly.cart.getLocalPickups()
await subbly.cart.update({
shippingMethodId: points[0].id,
pickupInfo: {
firstName: 'Ada',
lastName: 'Lovelace',
phone: '+442071234567',
},
})
```
Assigns the cart to the signed-in customer. It sets `attached` to `true` and
`customerId` to their ID, and clears the guest details in `customer`. Call it
after the customer signs in and before you charge the cart.
It needs a signed-in customer.
Relations to include.
The relations to fill in. The values are listed at the top of this page.
The attached cart, which is `subbly.cart` itself. Its fields are written out
under [Create a cart](#create).
```js title="Attach the customer after a log-in"
await subbly.auth.login({ email, password })
await subbly.cart.attachCustomer({ expand: ['items.product'] })
```
---
## Document: /reference/subbly-sdk/bundles
URL: /reference/subbly-sdk/bundles
import BundleFields from "../../../src/reference/subbly-sdk/types/Bundle.mdx";
import BundleItemFields from "../../../src/reference/subbly-sdk/types/BundleItem.mdx";
import BundleGroupFields from "../../../src/reference/subbly-sdk/types/BundleGroup.mdx";
import BundleQuoteFields from "../../../src/reference/subbly-sdk/types/BundleQuote.mdx";
import PaginationFields from "../../../src/reference/subbly-sdk/types/Pagination.mdx";
Every method here takes an optional `headers` argument to price one call in
another currency, and every amount is a whole number in the minor unit; see
[Currency](/reference/subbly-sdk#currency) and
[Amounts and dates](/reference/subbly-sdk#amounts-and-dates).
A typical flow: `load` the bundle for its plans and rules, `loadItems` for what
the customer can pick, `quote` to show the price, then `subbly.cart.addItem` to
buy it.
Gets a paginated list of the shop's bundles.
Paging and filters. Pass `{}` for the defaults — the argument is required by
position even though every field inside it is optional.
Page to fetch, counting from 1.
How many bundles per page.
Keep only bundles carrying these tags.
Keep only bundles with these slugs.
Keep only these bundle IDs.
`true` keeps only digital bundles, `false` only physical ones.
`true` keeps only bundles the customer configures, `false` only fixed ones.
Relations to include: `plans.variant`, `plans.variant.parent`,
`plans.plan`, `plans.plan.parent` and `metadata`.
Extra request headers.
Currency for the prices in the response, such as `EUR`.
One page of bundles.
The bundles on this page.
Where this page sits in the list.
```js title="List the bundles"
const { data, pagination } = await subbly.bundles.list({})
```
```js title="List the configurable bundles with their plans"
const { data } = await subbly.bundles.list({
perPage: 24,
configurable: true,
expand: ['plans.plan', 'plans.variant'],
})
```
Gets one bundle with its plans, prices, discounts, rulesets, filters and
preferences — everything the picker needs except the items themselves.
ID of the bundle.
Relations to include.
`plans.variant`, `plans.variant.parent`, `plans.plan`, `plans.plan.parent`
and `metadata`.
Extra request headers.
Currency for the prices in the response.
The bundle.
```js title="Load a bundle"
const bundle = await subbly.bundles.load(512, {
expand: ['plans.plan', 'plans.plan.parent'],
})
```
Gets a paginated, filterable list of the products a customer can pick for the
bundle. Send the picks to `quote` and to `subbly.cart.addItem` as
`{ productId, quantity }`.
ID of the bundle.
Paging, filters and relations.
Page to fetch, counting from 1.
How many items per page.
Attribute filters, built from `bundle.filters`.
ID of the attribute, from `bundle.filters[].attributeId`.
IDs of the chosen values, from `bundle.filters[].values[].id`.
Keep only these variant IDs.
Free-text search over the item names.
`product` and `product.parent`.
Extra request headers.
Currency for the prices in the response.
One page of bundle items.
The items on this page.
Where this page sits in the list.
```js title="Load the bundle items"
const { data } = await subbly.bundles.loadItems(512, {
perPage: 50,
expand: ['product'],
})
```
```js title="Filter the items by an attribute"
const { data } = await subbly.bundles.loadItems(512, {
filters: [{ attributeId: 9, values: [31, 32] }],
})
```
Gets the item groups of a bundle. A group gathers the variants of one product
and sets its own minimum and maximum quantity, so a customer can be asked for,
say, two coffees and one tea.
ID of the bundle.
Paging and relations.
Page to fetch, counting from 1.
How many groups per page.
`product`, `items.product` and `items.product.parent`.
Extra request headers.
Currency for the prices in the response.
One page of groups.
The groups on this page.
Where this page sits in the list.
```js title="Load the bundle groups"
const { data } = await subbly.bundles.loadGroups(512, {
expand: ['items.product'],
})
```
Prices a bundle for the exact items and preferences a customer picked, before it
goes in the cart. Use it to show a running total in the picker.
ID of the bundle.
What the customer picked. `items` and `preferences` are both required; send
an empty array when the bundle has no preferences.
The plan or variant the customer chose, from `bundle.plans[].plan.id` or
`bundle.plans[].variant.id`.
How many bundles.
The picked items.
ID of the picked variant, from `bundleItem.productId`.
How many of it.
The answers to the bundle preferences. Send `[]` when there are none.
ID of the attribute, from `bundle.preferences[].attributeId`.
IDs of the chosen values.
Relations to include.
`items.product` and `items.product.parent`.
Extra request headers.
Currency for the prices in the response.
The priced bundle, with a line for every picked item.
```js title="Quote a bundle"
const quote = await subbly.bundles.quote(512, {
productId: 8801,
quantity: 1,
items: [
{ productId: 3301, quantity: 2 },
{ productId: 3307, quantity: 1 },
],
preferences: [],
})
```
---
## Document: /reference/subbly-sdk/auth
URL: /reference/subbly-sdk/auth
import LoginResponseFields from "../../../src/reference/subbly-sdk/types/AuthResourceLoginResponse.mdx";
A successful sign-in writes the access token to the `subbly_access_token`
cookie, sets the `Authorization` header on the shared HTTP client, and flips
`subbly.auth.isAuthenticated` to `true`. The cookie lasts 365 days and is set on
`path=/`. It is a plain cookie: the SDK sets no `Secure`, `HttpOnly` or
`SameSite` attribute, so treat it as readable by any script on the host.
A `boolean` property, not a method. It is `true` after a successful
`checkAuthenticated`, `login`, `register`, `otpLogin` or `social`, and `false`
after `logout`. Nothing watches it, so read it after an authentication call
rather than expecting it to update on its own.
`true` when the SDK holds a token it has checked.
```js title="Branch on the authentication state"
if (subbly.auth.isAuthenticated) {
const customer = await subbly.customers.me()
}
```
Verifies that the SDK can call the methods that need a signed-in customer.
`Subbly.init` calls it for you at start-up.
The check costs one request: it calls `subbly.customers.me()` behind the scenes.
When it passes, the SDK sets the `Authorization` header and, if you passed a
token, writes it to the cookie. When it fails, the SDK clears the header and the
cookie and logs the error to the console.
An access token to check. It beats the token in the cookie. Leave it out to
check the token already stored.
`true` when the customer is signed in. It resolves with `false` rather than
rejecting when there is no token or the token is rejected.
```js title="Check the stored token"
const signedIn = await subbly.auth.checkAuthenticated()
```
```js title="Check a token you hold"
const signedIn = await subbly.auth.checkAuthenticated('eyJh...eyJ9')
```
Signs the customer in with an email and a password.
The credentials.
Email address of the customer.
Password of the customer.
The new token. The SDK stores it for you.
```js title="Log in"
await subbly.auth.login({
email: 'ada@example.com',
password: 'correct horse battery staple',
})
```
Creates an account and signs the new customer in. It stores the token exactly as
`login` does.
The new customer.
Email address. It must not already have a Subbly account.
Given name.
Family name.
Password. Leave it out and Subbly generates one and emails it to the
customer.
The new token, in the same shape `login` returns.
```js title="Register a customer"
await subbly.auth.register({
email: 'ada@example.com',
firstName: 'Ada',
lastName: 'Lovelace',
})
```
Reports whether an email already has a Subbly account. Use it to send the
customer to the sign-in form instead of the sign-up form.
The email to look up.
Email address to check.
The answer.
`true` when an account already exists for the email.
```js title="Check an email"
const { registered } = await subbly.auth.registered({
email: 'ada@example.com',
})
```
Emails a one-time password to the address you give. Pass the code the customer
types back to `subbly.auth.otpLogin`.
Where to send the code.
Email address to send the one-time password to.
Nothing. The response body is empty.
```js title="Send a one-time password"
await subbly.auth.otp({ email: 'ada@example.com' })
```
Signs the customer in with the one-time password `subbly.auth.otp` emailed. It
stores the token exactly as `login` does.
The email and the code.
The address the code went to.
The one-time password the customer typed, such as `123456`.
The new token, in the same shape `login` returns.
```js title="Log in with a one-time password"
await subbly.auth.otpLogin({
email: 'ada@example.com',
token: '123456',
})
```
Signs the customer in with a Google or Facebook token. The provider must be
switched on for the shop; look for a `google_login` or `facebook_login` entry in
`subbly.shop.apps`. It stores the token exactly as `login` does.
The token from the provider.
The ID token the provider's own SDK gave you.
Which service issued the token: `google` or `facebook`.
The new token, in the same shape `login` returns.
```js title="Log in with Google"
await subbly.auth.social({
token: googleIdToken,
provider: 'google',
})
```
Clears the authentication state in the browser: it sets `isAuthenticated` to
`false`, drops the `Authorization` header, and empties the
`subbly_access_token` cookie.
It sends no request, so the token itself stays valid until it expires. Do not
rely on `logout` to lock anything down on the server.
Nothing. This method is not a promise.
```js title="Log out"
subbly.auth.logout()
```
Reads the access token out of the `subbly_access_token` cookie. It is
synchronous and makes no request. Use it to hand the token to your own backend.
The stored token. `null` when there is no cookie, and an empty string after
`subbly.auth.logout()`. The TypeScript type says `string | undefined`; the SDK
never returns `undefined`.
```js title="Read the access token"
const token = subbly.auth.getAccessToken()
```
---
## Document: /reference/subbly-sdk/addresses
URL: /reference/subbly-sdk/addresses
import AddressFields from "../../../src/reference/subbly-sdk/types/CustomerAddress.mdx";
import AddressPayloadFields from "../../../src/reference/subbly-sdk/types/AddressStorePayload.mdx";
Every method here needs a signed-in customer. Sign one in with the
[authentication methods](/reference/subbly-sdk/auth) first.
Gets every saved address of the signed-in customer.
The saved addresses. The array is empty when the customer has none.
```js title="List the addresses"
const addresses = await subbly.addresses.list()
```
Creates a new saved address for the signed-in customer. Pass the new `id` to
`subbly.cart.update` as `shippingAddressId` or `billingAddressId`.
The address to save.
The saved address.
```js title="Add an address"
const address = await subbly.addresses.store({
firstName: 'Ada',
lastName: 'Lovelace',
phone: '+442071234567',
addressOne: '12 Marylebone Road',
addressTwo: null,
city: 'London',
zip: 'NW1 5JD',
countryId: 826,
})
```
Deletes a saved address. The call fails when the address is still attached to a
subscription.
ID of the address to delete.
Nothing. The response body is empty.
```js title="Delete an address"
await subbly.addresses.delete(4821)
```
---
## Document: /reference/cart-widget/theming
URL: /reference/cart-widget/theming
The custom properties the widget computes from the cart settings and writes
on its container. A property whose setting is empty is not written, and the
widget falls back to the [base token](#base-tokens). To override them from
your own CSS, see
[Style the widget](/developer-resources/cart-widget/style-it).
From `accentColor`, else the shop's brand colour. Default `var(--black)`.
The accent at 10% opacity. Default `var(--black-alpha-01)`.
The accent at 80% opacity. Default `var(--black-alpha-08)`.
Text on the accent. The widget picks dark text on a light accent, white on
a dark one. Default `var(--white)`.
From `backgroundColor`. The screen behind the widget. Default
`var(--black-alpha-06)`.
From `widgetBackgroundColor`, and a gradient down to
`widgetBackgroundDarker` when that is set. Default `#EAEFF3`.
From `fontStyle`, in front of the system stack. Default the system stack.
From `fontColorPrimary`. Default `var(--black)`.
From `fontColorSecondary`. Default `var(--gray-600)`.
The secondary colour at 80% opacity. Default `#464646aa`.
The secondary colour at 10% opacity. Default `#4646461a`.
From `buttonStyle`, in pixels. Default `8px`.
From `cardBackgroundColor`. Default `var(--white)`.
From `cardBorderColor`. Default `#D3D9DE`.
From `cardStyle`, in pixels. Default `8px`.
From `cardDividerColor`. Default `#D6D1C2`.
No setting drives it. Default `14px`.
Computed from `cardBackgroundColor`: a shade darker on a light card,
brighter on a dark one. Default `var(--gray-125)`.
From `headerHeight`, in pixels. Default `52px`.
From `headerBackgroundColor`, else `widgetBackgroundColor`. Default
`var(--widget-bg)`.
From `headerFontColor`. Default `var(--font-color-primary)`.
From `footerBackground`, else `widgetBackgroundDarker`. Default
`var(--widget-bg)`.
From `footerFontColor`, else `fontColorPrimary`. Default
`var(--font-color-primary)`.
From `inputBackground`. Default `var(--white)`.
From `inputBorderColor`. Default `var(--gray-300)`.
From `inputFontColor`. Default `var(--black)`.
From `segmentControlBackground`. Default `var(--accent)`.
Text on the segmented control, computed from its background. Default
`var(--accent-font)`.
Icon on the floating cart button, computed from the accent. Default
`#F9FDFF`.
Written from `accentColor` and its computed text colour, but no stylesheet
reads them. The floating cart button paints its background with
`var(--accent)`. Override the accent to change it.
```css title="Override the accent"
#subbly-cart-widget {
--accent: #701eff !important;
--button-border-radius: 24px !important;
}
```
The palette the widget's own styles are built on. No setting changes these.
They are declared on the container class `.SubblyAppContainer`, so a rule on
`#subbly-cart-widget` overrides them without `!important`.
`#000` and `#fff`.
Black at five alpha steps: `#0001`, `#0002`, `#0004`, `#0006` and `#0008`,
which is about 7%, 13%, 27%, 40% and 53% opacity.
The grey ramp: `100`, `125`, `150`, `175`, `200`, `250`, `300`, `350`,
`375`, `400`, `450`, `500`, `600` and `800`, from `#F7F7F7` down to
`#1B1B1B`.
`#1624A4`.
The error colours: `#FCF0ED`, `#E49D92` and `#C92E2E`.
`#D35307`, for warnings.
The success colours: `#E8F2ED`, `#82CEA2` and `#16A480`.
`var(--green-700)`.
`var(--blue-700)`. The ring around a focused control.
```css title="Match the widget to your own palette"
#subbly-cart-widget {
--gray-100: #f5f3ef;
--focus-outline: #701eff;
}
```
---
## Document: /reference/cart-widget/settings
URL: /reference/cart-widget/settings
The object the widget reads once, the moment its script runs. Write it before
the script tag.
[`SubblyCart.initialize(config)`](/reference/cart-widget/methods#initialize)
takes the same object. Only `apiKey` is required.
Your Storefront API key, from the Subbly admin under Settings, Developers.
A missing key throws `Missing required apiKey config`.
API the widget calls. Default `https://api.subbly.co/storefront/v1/`. An
empty string falls back to the default.
Page-integration settings: which links the widget catches, which element
opens it, which element shows the item count. Every key is on
[Settings keys](#settings). Default `{}`.
Two-letter language of the widget. Without it, the widget takes the `lang`
query parameter of the page, then the `lang` attribute of ``, then
the browser's language, and keeps the first two characters.
Start from a cart you already know. Default the `subbly_cart_id` cookie.
Sign a customer in at start-up. A valid token is written to the
`subbly_access_token` cookie. Default that cookie.
`false` stops the automatic start-up; you then call
[`SubblyCart.initialize`](/reference/cart-widget/methods#initialize)
yourself. Only the exact value `false` does this. Default `true`.
Name of the global that gets the instance. A value that is not a non-empty
string is ignored. Default `subblyCart`.
A Subbly.js client you already built. The widget uses it instead of
creating its own.
Standalone checkout. The widget opens full screen and configures the item
from `window.subblyCheckout.payload` at once. Default `false`.
Page on your site that owns the checkout. The widget sends the customer
there instead of opening its own, and stands down on that path. It accepts
`/checkout` and `/checkout/{cartId}`; a bare page gets the current cart ID
appended on redirect.
Page on your site that owns the cart summary. Same behaviour as
`checkoutUrl`.
Paths on which the widget stands down. Default `checkoutUrl` (without its
cart ID) and `cartSummaryUrl`; an empty array keeps that default. Only the
pathname is matched, case-insensitively. `*` matches inside one segment,
`**` across segments. An entry without a wildcard also matches on a segment
boundary, so `/checkout` covers `/checkout/success` but not
`/checkout-foo`.
```html title="A full config object"
```
Page-integration settings. They tie the widget to the markup on your own page:
which links it catches, which element opens it, which element shows the item
count. Set them in `window.subblyConfig` before the script runs, or pass them
to `SubblyCart.initialize()`.
These are not the theme settings. Colours, typography and checkout behaviour
live in the shop's cart settings, which you override with
[`setSettings`](/reference/cart-widget/methods#set-settings).
Catch clicks on Subbly buy links anywhere on the page, cancel the
navigation, and open the widget instead. See
[Sell from your own links](/developer-resources/cart-widget/wire-it-into-your-page#sell-from-your-own-links). Default `false`.
Before the widget finishes loading, a stub handler cancels these clicks.
It does not remember them, so a click that early is dropped.
Show the widget's own floating cart button. Default `false`. The button is
suppressed when the page already holds an `a.subbly-cart` element.
URL of an image for the floating cart button, in place of the built-in
icon. It works only with `cartButton: true`. Default `null`; anything that
is not a string is ignored.
CSS selector or element that opens and closes the widget. The default click
action is cancelled. Clicks are caught on `document`, so elements added
later still work. Default `''`.
CSS selector or element whose content the widget replaces with the number
of active items. It updates on `CART_READY` and `CART_UPDATED`, and after a
`popstate` event, where it waits up to 5 seconds for the element and then
300 ms more before writing. Default `''`.
The widget also listens for a `pushstate` event, but nothing fires one: its
own patch of `history.pushState` dispatches `locationchange` instead. On a
framework that routes with `pushState`, update the element yourself from
`CART_UPDATED`.
Show the currency and language picker in the widget header. Default
`false`.
Hide the cart summary and use the checkout as the first view. Default
`false`. The `?disableSummaryView=1` query parameter does the same.
Hide the products inside a bundle, in the summary and at the checkout.
Default `false`. The `?hideBundleItems=1` query parameter does the same.
```html title="Configure the widget"
```
Query parameters the widget reads from the page URL when it starts. They make
a plain link enough to drive the widget — a campaign link that applies a
coupon, a referral link, a translated landing page.
Four of them are used once and then remembered as used: `gift`, `coupon`, `r`
and `custom_setup_return_url`. A bank confirmation that sends the customer back
to the same URL does not apply them twice.
Language of the widget, when `languageCode` is not in the config. The
widget takes the first two characters.
Coupon code to hold and apply once the cart fits it, as
[`setPendingCoupon`](/reference/cart-widget/methods#set-pending-coupon)
does. Used once.
Any value opens the gift view. Used once.
ID of the referring customer. Used once.
Prefills the checkout email. `customerEmail` is a deprecated fallback.
Prefills the first name.
Prefills the last name.
Prefills the marketing opt-in.
Prefills the terms checkbox.
Your own ID for the customer. It is stored on the customer profile.
Any value hides the summary and uses the checkout as the first view.
Any value hides the products inside a bundle.
URL of your own receipt page, in place of the widget's.
URL the customer returns to after a Stripe 3-D Secure check. Used once.
`test` runs the checkout in test mode.
Set by Stripe on the return redirect. `payment_method_id` and
`redirect_status` come with it.
```html title="A campaign link"
10% off your first box
```
---
## Document: /reference/cart-widget/properties
URL: /reference/cart-widget/properties
import SubblyCartFields from "../../../src/reference/cart-widget/types/SubblyCart.mdx";
import CartModelMethods from "../../../src/reference/cart-widget/types/CartModel.mdx";
import SubblyShopFields from "../../../src/reference/cart-widget/types/SubblyShop.mdx";
The current cart: every cart field, plus the cart methods of the SDK. It is one
live object, not a snapshot — the SDK writes the new data into the same object
after every call, so a reference you store keeps showing current data. Clone it
when you need a snapshot.
Treat it as read-only and change the cart through the widget methods. The SDK
methods on it skip the widget's own store, so the panel does not refresh and no
event fires.
The cart is ready as soon as the widget is initialized.
The cart.
```js title="Read the cart"
const { items, total, currencyCode } = subblyCart.cart
// Amounts live on the summary items, joined on itemId
const amount = subblyCart.cart.summaryItems.find(
(summaryItem) => summaryItem.itemId === items[0].id
).total
```
Public information about the shop: branding, currencies, languages, settings,
plan limits and shipping countries. The widget loads it once while it starts.
Like the cart, it is one live object; `loadShop()` refreshes it in place.
The shop.
```js title="List the currencies the shop sells in"
const codes = subblyCart.shop.currencies.map(
(currency) => currency.abbreviation
)
```
The currency and the language the widget runs on. Reading it builds a fresh
plain object every time, so what you get is a snapshot.
Currency of the cart, as an ISO 4217 code. It falls back to the shop's
default currency while the cart has none.
Two-letter code of the language the widget shows, such as `en`.
```js title="Read the state"
const { currencyCode, languageCode } = subblyCart.state
```
The [Subbly.js](/reference/subbly-sdk) client the widget uses, for Storefront
API calls of your own. Sharing one client keeps the widget and your code on the
same cart, customer, language and currency.
Calls you make on the client do not go through the widget's store, so the panel
does not refresh and no widget event fires. Use the widget methods to change
the cart.
Version of the SDK.
The client configuration: `apiKey`, `apiUrl` and `lang`.
Re-prices the products, bundles and surveys the client loads next. It does
not change the cart; use `subblyCart.setCurrency` for that.
Sets the language the API answers in.
`list`, `store`, `delete`.
`isAuthenticated`, `checkAuthenticated`, `login`, `registered`, `register`,
`otp`, `otpLogin`, `social`, `logout`, `getAccessToken`.
`list`, `load`, `loadItems`, `loadGroups`, `quote`, `setCurrency`.
The same object as `subblyCart.cart`.
`purchase`.
`list`.
`update`, `me`, `referral`. All three need a signed-in customer.
`prePurchaseFetch`, `prePurchaseRefresh`, `prePurchaseAccept`,
`prePurchaseReject`, `midPurchaseFetch`, `midPurchaseAccept`,
`midPurchaseReject`, `postPurchaseFetch`, `postPurchaseAccept`,
`postPurchaseReject`.
`subscribe`.
`list`.
`getPaymentIntent`, `confirm`.
`list`, `store`, `delete`.
`list`, `load`, `loadVariant`, `loadPlan`, `setCurrency`.
`load`. The same object as `subblyCart.shop`.
`subscribe`.
`list`, `load`, `update`, `updatePreferences`, `updateBundle`, `loadItem`,
`updateItem`, `updateItemBundle`, `updateItemPreferences`.
`load`, `setCurrency`.
`store`, `list`, `setup`, `setupIntent`.
```js title="Call the Storefront API"
const { data } = await subblyCart.sdk.products.list({ perPage: 100 })
```
The event emitter of the widget. `events.type` holds the event names, and every
name equals its own value, so `events.type.CART_OPEN` is `'CART_OPEN'`. The
emitter comes from `tiny-typed-emitter`, which follows the Node
`EventEmitter` API.
The event names: `CART_READY`, `CART_UPDATED`, `CART_OPEN`, `CART_CLOSE`,
`CART_RESET`, `EMAIL_COLLECTED`, `PURCHASE_COMPLETED`, `SIGN_IN`,
`SIGN_UP` and `SIGN_OUT`.
Adds a handler. `addListener` is an alias.
Adds a handler that runs for one emission.
Removes a handler. `removeListener` is an alias.
Removes every handler, of one event or of all of them.
Adds a handler at the front of the list. `prependOnceListener` does the
same for one emission.
Lists the events that have handlers.
Counts the handlers of an event.
Lists the handlers of an event. `rawListeners` keeps the `once` wrappers.
Sets how many handlers one event takes before Node warns.
`getMaxListeners` reads it back.
Internal. The widget emits its own events; do not call this.
```js title="Subscribe and unsubscribe"
const { events } = subblyCart
const onUpdate = (err, cart) => console.log(cart.items.length)
events.on(events.type.CART_UPDATED, onUpdate)
events.off(events.type.CART_UPDATED, onUpdate)
```
---
## Document: /reference/cart-widget/methods
URL: /reference/cart-widget/methods
import SubblyCartFields from "../../../src/reference/cart-widget/types/SubblyCart.mdx";
import SubblyShopFields from "../../../src/reference/cart-widget/types/SubblyShop.mdx";
import CartUpdatePayloadFields from "../../../src/reference/cart-widget/types/CartUpdatePayload.mdx";
import ConfigureItemPayloadFields from "../../../src/reference/cart-widget/types/ConfigureItemPayload.mdx";
import CartItemAddPayloadFields from "../../../src/reference/cart-widget/types/CartItemAddPayload.mdx";
import CartUpdateItemPayloadFields from "../../../src/reference/cart-widget/types/CartResourceUpdateItemPayload.mdx";
import ShopCartSettingsFields from "../../../src/reference/cart-widget/types/ShopCartSettings.mdx";
Opens the widget on the cart summary, or on the checkout. The call does
nothing when the widget is already open. It emits `CART_OPEN`.
Which view to open. Default `"summary"`.
- `"summary"` shows the cart summary and closes the checkout panel.
- `"checkout"` opens the checkout. When `checkoutUrl` is configured, the
widget sends the page to that URL with the cart ID appended and stops, so
nothing opens. The embedded checkout view needs both the
`checkoutWidget` and the `embedCheckout` limits on the shop; without
them the widget sends the page to the hosted Subbly checkout instead.
- Any other string opens the widget without changing the view.
- `true` opens the widget with the summary shown, `false` with it hidden.
Both leave the active customization view mounted.
Every string value unmounts the customization view, unless `"checkout"`
hands over to another page.
```js title="Open the widget"
subblyCart.open()
// Open the checkout
subblyCart.open('checkout')
// Open without the summary
subblyCart.open(false)
```
Closes the widget. It hides the summary, unmounts any customization view, and
clears the widget error. The call does nothing when the widget is already
closed. It emits `CART_CLOSE`.
```js title="Close the widget"
subblyCart.close()
```
Opens the widget when it is closed, closes it when it is open. It always opens
on the summary. It emits `CART_OPEN` or `CART_CLOSE`.
```js title="Toggle the widget"
subblyCart.toggle()
```
Returns the shop the widget already holds. It refreshes `subblyCart.shop` in
place, so every reference you hold sees the same object. It calls the API only
when no shop is loaded yet, which cannot happen after `initialize`, so in
practice it never re-reads the shop.
The shop.
```js title="Reload the shop"
const shop = await subblyCart.loadShop()
```
Applies cart-level changes — currency, coupon, addresses, shipping, gift
details — and refreshes the widget, open or closed. Calls run in order through
an internal queue, so concurrent updates do not race each other. A cart that
was already purchased is reset first, which emits `CART_RESET`. The method
throws `The cart is not initialized` when the widget holds no cart. It emits
`CART_UPDATED`.
The cart fields to change. Every field is optional.
The updated cart.
```js title="Set the shipping address"
const cart = await subblyCart.updateCart({
shippingAddress: {
firstName: 'Ada',
lastName: 'Lovelace',
addressOne: '12 Bishop Street',
addressTwo: null,
city: 'Bristol',
zip: 'BS2 8EE',
countryId: 826
}
})
```
Adds a product, a bundle, or a survey, and asks the customer for what is
missing. Use it when you hold a parent product ID, a bundle ID, or a survey ID,
and `addItem` when you already hold a variant or a plan.
The widget creates a cart when there is none, and opens itself unless the shop
setting `afterItemAdded` is `close`. It then mounts the view the item needs:
pricing, variant, survey, survey flow, bundle, bundle plan, voucher,
subscription replacement, out of stock, or error. It emits `CART_UPDATED` once
the item lands in the cart.
What to add. Pass one of `productId`, `bundleId` or `surveyId`.
The promise stays pending while the customization view is open, and resolves
once the customer closes it. It never resolves with `finalized: false`, so the
useful signal is when it resolves, not what it carries.
`true`. The item is in the cart.
```js title="Add a subscription product"
const { finalized } = await subblyCart.configureItem({
productId: 555,
quantity: 1
})
```
```js title="Start a bundle or a survey"
await subblyCart.configureItem({ bundleId: 42 })
await subblyCart.configureItem({ surveyId: 7 })
```
Adds an item that needs no further input straight to the cart. The `productId`
must be a variant ID or a plan ID. A cart that was already purchased is reset
first, which emits `CART_RESET`. On failure the widget shows its error view
**and** rejects, so your `catch` runs as well. It emits `CART_UPDATED`.
The item to add.
The updated cart.
```js title="Add a variant"
const cart = await subblyCart.addItem({
productId: 8123,
quantity: 2
})
```
Changes a line that is already in the cart. The payload has no required field,
so TypeScript cannot tell the three shapes apart; pick the one that matches the
line. There is no `productId` and no `metadata` — both are add-time only. It
emits `CART_UPDATED`.
ID of the line, as a UUID. Read it from `subblyCart.cart.items[].id`.
The fields to change.
The updated cart.
```js title="Change the quantity"
const [item] = subblyCart.cart.items
const cart = await subblyCart.updateItem(item.id, { quantity: 3 })
```
Removes a line from the cart. It emits `CART_UPDATED`.
ID of the line, as a UUID. Read it from `subblyCart.cart.items[].id`.
The updated cart.
```js title="Remove an item"
const [item] = subblyCart.cart.items
const cart = await subblyCart.removeItem(item.id)
```
Applies a coupon code to the cart. It is shorthand for
`updateCart({ couponCode })`, and it emits `CART_UPDATED`. The call rejects
when the coupon is invalid or does not fit what the cart holds, so a coupon
tied to a product fails on an empty cart. Use `setPendingCoupon` to hold such a
code until the product is in the cart.
The code the customer typed.
The updated cart.
```js title="Apply a coupon"
const cart = await subblyCart.applyCoupon('WELCOME10')
```
Holds a coupon code and applies it once the cart holds the right products.
Nothing happens at once: the widget applies the code the next time the summary
or the checkout renders, so a product-restricted coupon is checked against a
cart that already holds the product.
The code is dropped after one attempt, and skipped when it matches the coupon
already on the cart. Failures are silent. This is what a `?coupon=CODE` buy
link uses.
The code to hold.
```js title="Queue a coupon, then add the product"
subblyCart.setPendingCoupon('WELCOME10')
await subblyCart.configureItem({ productId: 555 })
```
Removes the coupon from the cart. It calls `updateCart({ couponCode: null })`
and emits `CART_UPDATED`.
The updated cart.
```js title="Remove the coupon"
const cart = await subblyCart.removeCoupon()
```
Applies a gift card code to the cart. It is shorthand for
`updateCart({ giftCardCode })`, and it emits `CART_UPDATED`.
The gift card code, as a UUID.
The updated cart.
```js title="Apply a gift card"
const cart = await subblyCart.applyGiftCard(
'0b5c0f1a-8f3c-4f2a-9f0e-6a2f6a1f2b3c'
)
```
Removes the gift card from the cart. It calls
`updateCart({ giftCardCode: null })` and emits `CART_UPDATED`.
The updated cart.
```js title="Remove the gift card"
const cart = await subblyCart.removeGiftCard()
```
Switches the language of the widget and asks the API for translated responses.
The shop must publish the language; otherwise the widget falls back to the
shop's own languages, then to `en`. It updates `subblyCart.state.languageCode`
and emits no event. An empty value throws
`Cart Widget:setLanguage Language code is required`.
Without this call the widget takes the language from `languageCode` in the
config. If that is missing it tries the `lang` query parameter, then the `lang`
attribute of ``, then the browser.
Two-letter language code, such as `fr`. It must be one of
`subblyCart.shop.languages[].code`.
```js title="Switch to French"
subblyCart.setLanguage('fr')
```
Switches the currency of the cart and re-prices every line. It also re-prices
the products, bundles and surveys the SDK loads next, and updates
`subblyCart.state.currencyCode`. An empty value throws
`Cart Widget:setCurrency currency code is required`. It emits `CART_UPDATED`.
ISO 4217 code, such as `GBP`. It must be one of
`subblyCart.shop.currencies[].abbreviation`.
The updated cart.
```js title="Switch to pounds"
const cart = await subblyCart.setCurrency('GBP')
```
Discards the cart and creates an empty one with the same currency. The new cart
ID replaces the old one in the `subbly_cart_id` cookie. Any open customization
view is unmounted. It emits `CART_UPDATED`, then `CART_RESET`.
The new cart.
```js title="Start a new cart"
const cart = await subblyCart.resetCart()
```
Overrides the shop's cart settings at runtime: colours, typography, layout and
behaviour. Keys you omit keep their value. The widget recomputes the CSS
custom properties on its container, and loads the Google font named by
`fontStyle` by adding a `` to the head. Nothing is stored, so a page
reload brings the shop's own settings back.
The settings to change.
The method runs asynchronously and returns nothing worth waiting for.
```js title="Restyle the widget"
subblyCart.setSettings({
accentColor: '#701eff',
fontStyle: 'Inter',
buttonStyle: 24,
afterItemAdded: 'checkout'
})
```
Mounts the gift view, now or after the current customization. It does not open
the widget on its own.
Pass `null`. Any other value logs `Payload is not supported yet in the
configureGift method.` and is ignored.
`true` mounts the gift view now. `false` queues it, so it appears once the
current customization view closes.
```js title="Ask for gift details after the item"
const { finalized } = await subblyCart.configureItem({ productId: 555 })
subblyCart.configureGift(null, finalized)
```
Prefills the checkout with an email and consent flags for a guest. The call
does nothing when a customer is already signed in. The same data can come from
the `email`, `marketingConsent` and `tosConsent` query parameters.
What to prefill.
Email address for the checkout field.
`true` ticks the marketing opt-in.
`true` ticks the terms checkbox.
```js title="Prefill the checkout"
subblyCart.setCustomerData({
email: 'ada@example.com',
marketingConsent: true
})
```
Signs a customer in with a Subbly access token. The widget checks the token
against the API first. A token you pass is written to the `subbly_access_token`
cookie; a token that fails the check is cleared. On success the widget fetches
the customer and emits `SIGN_IN`.
A customer access token. Without it the widget checks the token in the
`subbly_access_token` cookie.
The promise rejects with `Cart Widget:authenticate Authentication failed. The
provided token is invalid or expired.` when the token does not check out.
```js title="Sign a customer in"
await subblyCart.authenticate(myAccessToken)
```
Signs the current customer out of the widget. It clears the widget's sign-in
state and the `subbly_access_token` cookie, and emits `SIGN_OUT`. The cart
stays as it is.
```js title="Sign the customer out"
await subblyCart.signOut()
```
Re-reads the cart and the sign-in state from the API. Use it on a page that
rebuilds the cart itself, such as your own checkout. The widget re-checks the
shared access token and signs itself out when the token is gone, without
touching the cart; a failed check is ignored and the cart still reloads. It
emits `CART_UPDATED`.
Point the widget at this cart, as a UUID, and write it to the
`subbly_cart_id` cookie for 365 days. Without it the widget reloads the
cart it already holds.
```js title="Adopt a cart made outside the widget"
await subblyCart.reload('14920f9c-f261-4879-a3a9-e1b75993eeed')
```
Merges new URL configuration into the running widget, and checks the current
URL against it at once. Each field merges over the current value, so a field
you leave out keeps what the config set at init. `disableUrls` keeps the list
the widget already holds: it is not rebuilt from the new `checkoutUrl` or
`cartSummaryUrl`. Only an empty list falls back to those URLs.
The URL configuration to merge.
Page on your site that owns the checkout. The widget sends the customer
here instead of opening its own checkout. It accepts `/checkout` and
`/checkout/{cartId}`; a plain path gets the current cart ID appended.
Page on your site that owns the cart summary. It behaves like
`checkoutUrl`.
Paths on which the widget stands down. `*` matches inside one path
segment and `**` across segments. An entry without a wildcard also
matches on a segment boundary, so `/checkout` covers
`/checkout/success` but not `/checkout-foo`. Only the pathname is
matched, case-insensitively; the query and the hash are ignored.
```js title="Hand checkout to your own page"
subblyCart.configure({
checkoutUrl: '/checkout',
disableUrls: ['/checkout', '/cart']
})
```
Makes the widget stand down: no panel, no cart button, no checkout. The
override sits on top of the URL rules, so the widget stays disabled while
either this override is set or the URL matches a `disableUrls` pattern.
Older deployments may not have the method yet, so call it with optional
chaining.
```js title="Stand the widget down"
window.subblyCart?.disable?.()
```
Clears the disable override. The widget comes back only when the current URL no
longer matches a disable pattern. A widget that first mounted disabled runs its
one-time start-up now.
```js title="Bring the widget back"
window.subblyCart?.enable?.()
```
Boots the widget by hand. You need it only with `init: false`, and you call it
on the class at `window.SubblyCart`, not on an instance.
It creates the container element, starts the SDK, mounts the app, applies the
settings, and emits `CART_READY`. It does not set a global: the name in
`globalName` is only assigned on the automatic path, so keep the instance the
promise gives you.
The same object you would put in `window.subblyConfig`. See
[Configuration keys](/reference/cart-widget/settings#config) for every key.
The widget instance: the object every method and property on these pages
belongs to.
```js title="Initialize manually"
window.addEventListener('subbly-cart-loaded', async () => {
const cart = await window.SubblyCart.initialize({
apiKey: 'YOUR_STOREFRONT_API_KEY'
})
cart.open()
})
```
---
## Document: /reference/cart-widget/events
URL: /reference/cart-widget/events
import SubblyCartFields from "../../../src/reference/cart-widget/types/SubblyCart.mdx";
import SubblyCustomerFields from "../../../src/reference/cart-widget/types/SubblyCustomer.mdx";
import PurchaseFields from "../../../src/reference/cart-widget/types/CheckoutPurchaseResponse.mdx";
Every handler takes two arguments: an error slot first, then the payload. The
widget passes `null` as the error today, so read the second argument. Payloads
are deep-cloned before they are emitted, so a handler cannot change the widget
state through them. `CART_READY` is the exception: it carries the live cart
object.
Subscribes to a widget event. `once` adds a handler for one emission;
`addListener` is an alias of `on`.
Name of the event. Use `subblyCart.events.type.CART_UPDATED` and the other
names below.
What to run. The payload shape follows the event.
The emitter, so calls chain.
```js title="Listen to an event"
const { events } = subblyCart
events.on(events.type.CART_UPDATED, (err, cart) => {
console.log(cart.items.length)
})
```
Removes a handler you added. Pass the same function you passed to `on`.
`removeListener` is an alias; `removeAllListeners(type?)` clears them all.
Name of the event.
The handler to remove.
The emitter, so calls chain.
```js title="Stop listening"
const { events } = subblyCart
const onOpen = () => console.log('open')
events.on(events.type.CART_OPEN, onOpen)
events.off(events.type.CART_OPEN, onOpen)
```
The widget finished initializing and the cart is loaded. It fires once, inside
`initialize()`, before that promise resolves with the instance. So no page code
can subscribe in time: neither a listener added after the
`subbly-cart-initialized` window event, nor one added after
`SubblyCart.initialize()` resolves with `init: false`. To act on the loaded
cart, wait for `subbly-cart-initialized` and read `subblyCart.cart`.
This is the one payload the widget does not clone: it is the live cart object.
Always `null`.
The loaded cart.
```js title="Read the cart once the widget is ready"
window.addEventListener('subbly-cart-initialized', () => {
console.log(window.subblyCart.cart.id)
})
```
The cart changed. It fires on every change: an item added, updated or removed,
a coupon, a gift card, a currency, an address, a reload, a reset.
Always `null`.
The updated cart, as a deep clone.
```js title="Track the item count"
const { events } = subblyCart
events.on(events.type.CART_UPDATED, (err, cart) => {
document.querySelector('.cart-count').textContent = cart.items.length
})
```
The widget panel opened. It carries no payload.
Always `null`.
```js title="Listen for the panel opening"
const { events } = subblyCart
events.on(events.type.CART_OPEN, () => {
document.body.classList.add('cart-open')
})
```
The widget panel closed. It carries no payload.
Always `null`.
```js title="Listen for the panel closing"
const { events } = subblyCart
events.on(events.type.CART_CLOSE, () => {
document.body.classList.remove('cart-open')
})
```
A new cart replaced the old one, after `resetCart()` or after an attempt to add
to a cart that was already purchased. `CART_UPDATED` comes first: the widget
stores the new cart before it emits this event.
Always `null`.
The new cart, as a deep clone.
```js title="Notice a new cart"
const { events } = subblyCart
events.on(events.type.CART_RESET, (err, cart) => {
console.log('new cart', cart.id)
})
```
The customer's email was captured. It fires when a guest's email is checked at
the checkout, and when the checkout form opens for a customer who is already
signed in.
Always `null`.
The email.
The email address the customer gave.
```js title="Send the email to your own tooling"
const { events } = subblyCart
events.on(events.type.EMAIL_COLLECTED, (err, data) => {
identify(data.email)
})
```
A purchase succeeded. Which members of `purchase` are filled depends on the
cart: a future-dated subscription returns the subscription alone, and a cart of
one-time products returns an invoice and orders with no subscription.
Always `null`.
The cart that was bought, and what the purchase created.
The cart at the moment of the purchase.
What the purchase created.
```js title="Track a purchase"
const { events } = subblyCart
events.on(events.type.PURCHASE_COMPLETED, (err, data) => {
const { cart, purchase } = data
track('purchase', {
value: purchase.invoice?.total ?? 0,
currency: cart.currencyCode
})
})
```
A customer signed in, from the password form, the one-time code form, or
`subblyCart.authenticate()`.
Always `null`.
The customer.
The customer who signed in.
```js title="Greet a returning customer"
const { events } = subblyCart
events.on(events.type.SIGN_IN, (err, data) => {
console.log('welcome back', data.customer.firstName)
})
```
A customer account was created at the checkout.
Always `null`.
The customer.
The customer who signed up.
```js title="Track a sign-up"
const { events } = subblyCart
events.on(events.type.SIGN_UP, (err, data) => {
track('sign_up', { customerId: data.customer.id })
})
```
`subblyCart.signOut()` ran. It carries no payload, and the cart stays as it
is. No control in the widget emits this event, and neither does
`subblyCart.reload()`, which also clears the sign-in state.
Always `null`.
```js title="Clear your own session"
const { events } = subblyCart
events.on(events.type.SIGN_OUT, () => {
clearLocalSession()
})
```
---
## Document: Tools
Every tool the Subbly's MCP server exposes, with a link to the Private API endpoint behind it.
URL: /developer-resources/subbly-mcp/tools
# Tools
The server exposes 106 tools. Each one wraps one Private API
endpoint and takes the same fields, so the endpoint's reference page is where
you read what a field means and what comes back. The tables below map every
tool to its endpoint.
Tools that archive or delete something cannot be undone, and the tool
descriptions tell the agent to confirm with you first. Publish and unpublish
are the safe way to hide something and bring it back.
## Products
| Tool | Private API endpoint |
| --- | --- |
| `list_products` | [Get list of products](/api/private/product#get-list-of-products) |
| `get_product` | [Get product by ID](/api/private/product#get-product-by-id) |
| `create_one_time_product` | [Create one-time product](/api/private/product#create-one-time-product) |
| `update_one_time_product` | [Update one-time product](/api/private/product#update-one-time-product) |
| `publish_one_time_product` | [Publish one-time product](/api/private/product#publish-one-time-product) |
| `unpublish_one_time_product` | [Unpublish one-time product](/api/private/product#unpublish-one-time-product) |
| `archive_one_time_product` | [Archive one-time product](/api/private/product#archive-one-time-product) |
| `set_one_time_product_metadata` | [Sync one-time product metadata](/api/private/product#sync-one-time-product-metadata) |
| `create_subscription_product` | [Create subscription product](/api/private/product#create-subscription-product) |
| `update_subscription_product` | [Update subscription product](/api/private/product#update-subscription-product) |
| `publish_subscription_product` | [Publish subscription product](/api/private/product#publish-subscription-product) |
| `unpublish_subscription_product` | [Unpublish subscription product](/api/private/product#unpublish-subscription-product) |
| `archive_subscription_product` | [Archive subscription product](/api/private/product#archive-subscription-product) |
| `set_subscription_product_metadata` | [Sync subscription product metadata](/api/private/product#sync-subscription-product-metadata) |
## Variants
| Tool | Private API endpoint |
| --- | --- |
| `get_variant` | [Get variant by ID](/api/private/product#get-variant-by-id) |
| `create_variant` | [Create variant](/api/private/product#create-variant) |
| `update_variant` | [Update variant](/api/private/product#update-variant) |
| `archive_variant` | [Archive variant](/api/private/product#archive-variant) |
| `batch_variants` | [Batch create/update/archive variants](/api/private/product#batch-create-update-archive-variants) |
## Plans
| Tool | Private API endpoint |
| --- | --- |
| `get_plan` | [Get plan by ID](/api/private/product#get-plan-by-id) |
| `create_plan` | [Create plan](/api/private/product#create-plan) |
| `update_plan` | [Update plan](/api/private/product#update-plan) |
| `archive_plan` | [Archive plan](/api/private/product#archive-plan) |
## Bundles
| Tool | Private API endpoint |
| --- | --- |
| `list_bundles` | [Get list of bundles](/api/private/bundle#get-list-of-bundles) |
| `get_bundle` | [Get bundle by ID](/api/private/bundle#get-bundle-by-id) |
| `create_bundle` | [Create a new bundle](/api/private/bundle#create-a-new-bundle) |
| `update_bundle` | [Update a bundle](/api/private/bundle#update-a-bundle) |
| `publish_bundle` | [Publish a bundle](/api/private/bundle#publish-a-bundle) |
| `unpublish_bundle` | [Unpublish a bundle](/api/private/bundle#unpublish-a-bundle) |
| `archive_bundle` | [Archive a bundle](/api/private/bundle#archive-a-bundle) |
| `set_bundle_metadata` | [Sync bundle metadata](/api/private/bundle#sync-bundle-metadata) |
| `list_bundle_groups` | [Get list of bundle groups](/api/private/bundle#get-list-of-bundle-groups) |
## Bundle items
| Tool | Private API endpoint |
| --- | --- |
| `list_bundle_items` | [Get list of bundle items](/api/private/bundle#get-list-of-bundle-items) |
| `get_bundle_item` | [Get bundle item by ID](/api/private/bundle#get-bundle-item-by-id) |
| `create_bundle_item` | [Create a bundle item](/api/private/bundle#create-a-bundle-item) |
| `update_bundle_item` | [Update a bundle item](/api/private/bundle#update-a-bundle-item) |
| `delete_bundle_item` | [Delete a bundle item](/api/private/bundle#delete-a-bundle-item) |
| `batch_bundle_items` | [Batch create bundle items](/api/private/bundle#batch-create-bundle-items) |
## Bundle plans
| Tool | Private API endpoint |
| --- | --- |
| `get_bundle_plan` | [Get bundle plan by ID](/api/private/bundle#get-bundle-plan-by-id) |
| `create_bundle_plan` | [Create a bundle plan](/api/private/bundle#create-a-bundle-plan) |
| `update_bundle_plan` | [Update a bundle plan](/api/private/bundle#update-a-bundle-plan) |
| `archive_bundle_plan` | [Archive a bundle plan](/api/private/bundle#archive-a-bundle-plan) |
## Collections
| Tool | Private API endpoint |
| --- | --- |
| `list_collections` | [Get list of product collections](/api/private/product-collection#get-list-of-product-collections) |
| `get_collection` | [Get product collection by ID](/api/private/product-collection#get-product-collection-by-id) |
| `create_collection` | [Create a product collection](/api/private/product-collection#create-a-product-collection) |
| `update_collection` | [Update a product collection](/api/private/product-collection#update-a-product-collection) |
| `publish_collection` | [Publish a product collection](/api/private/product-collection#publish-a-product-collection) |
| `unpublish_collection` | [Unpublish a product collection](/api/private/product-collection#unpublish-a-product-collection) |
| `archive_collection` | [Archive a product collection](/api/private/product-collection#archive-a-product-collection) |
| `list_collection_items` | [Get list of collection groups with items](/api/private/product-collection#get-list-of-collection-groups-with-items) |
| `batch_collection_items` | [Sync collection items](/api/private/product-collection#sync-collection-items) |
## Subscriptions
| Tool | Private API endpoint |
| --- | --- |
| `list_subscriptions` | [Get list of subscriptions](/api/private/subscription#get-list-of-subscriptions) |
| `get_subscription` | [Get subscription by ID.](/api/private/subscription#get-subscription-by-id) |
| `cancel_subscription` | [Cancel a subscription](/api/private/subscription#cancel-a-subscription) |
| `undo_subscription_cancellation` | [Undo cancellation](/api/private/subscription#undo-cancellation) |
## Subscription items
| Tool | Private API endpoint |
| --- | --- |
| `create_subscription_item` | [Create a subscription item.](/api/private/subscription#create-a-subscription-item) |
| `get_subscription_item` | [Show a subscription item.](/api/private/subscription#show-a-subscription-item) |
| `update_subscription_item` | [Update a subscription item.](/api/private/subscription#update-a-subscription-item) |
| `delete_subscription_item` | [Remove a subscription item.](/api/private/subscription#remove-a-subscription-item) |
| `update_subscription_item_preferences` | [Update a subscription item's preferences.](/api/private/subscription#update-a-subscription-items-preferences) |
| `update_subscription_item_bundle` | [Update a subscription item's bundle.](/api/private/subscription#update-a-subscription-items-bundle) |
## Invoices
| Tool | Private API endpoint |
| --- | --- |
| `list_invoices` | [Get list of invoices.](/api/private/invoice#get-list-of-invoices) |
| `get_invoice` | [Get invoice by ID.](/api/private/invoice#get-invoice-by-id) |
| `schedule_invoice_payment` | [Schedule the next payment for invoice](/api/private/invoice#schedule-the-next-payment-for-invoice) |
| `void_invoice` | [Void an invoice.](/api/private/invoice#void-an-invoice) |
| `forgive_invoice` | [Forgive an invoice.](/api/private/invoice#forgive-an-invoice) |
| `mark_invoice_as_paid` | [Mark an invoice as paid.](/api/private/invoice#mark-an-invoice-as-paid) |
## Orders
| Tool | Private API endpoint |
| --- | --- |
| `list_orders` | [Get list of orders.](/api/private/order#get-list-of-orders) |
| `get_order` | [Get order by ID.](/api/private/order#get-order-by-id) |
| `update_order` | [Update an order.](/api/private/order#update-an-order) |
| `ship_order` | [Ship an order](/api/private/order#ship-an-order) |
| `cancel_order` | [Cancel an order](/api/private/order#cancel-an-order) |
| `mark_order_as_pending` | [Mark an order as pending](/api/private/order#mark-an-order-as-pending) |
| `archive_order` | [Archive an order](/api/private/order#archive-an-order) |
| `mark_order_as_future_shipment` | [Mark an order as a future shipment](/api/private/order#mark-an-order-as-a-future-shipment) |
| `batch_order_shipping_items` | [Sync order shipping items.](/api/private/order#sync-order-shipping-items) |
## Customers and payments
| Tool | Private API endpoint |
| --- | --- |
| `list_customers` | [Get list of customers](/api/private/customer#get-list-of-customers) |
| `get_customer` | [Get customer by ID.](/api/private/customer#get-customer-by-id) |
| `get_payment_method` | [Get payment method by ID.](/api/private/payment-method#get-payment-method-by-id) |
| `list_transactions` | [Get list of transactions.](/api/private/transaction#get-list-of-transactions) |
| `get_transaction` | [Get transaction by ID.](/api/private/transaction#get-transaction-by-id) |
| `list_cancellation_reasons` | [Get shop cancellation reasons](/api/private/cancellation#get-shop-cancellation-reasons) |
## Coupons
| Tool | Private API endpoint |
| --- | --- |
| `list_coupons` | [Get list of coupons](/api/private/coupon#get-list-of-coupons) |
| `get_coupon` | [Get coupon by ID](/api/private/coupon#get-coupon-by-id) |
| `create_coupon` | [Create a coupon](/api/private/coupon#create-a-coupon) |
| `update_coupon` | [Update a coupon](/api/private/coupon#update-a-coupon) |
| `archive_coupon` | [Archive a coupon](/api/private/coupon#archive-a-coupon) |
## Inventory
| Tool | Private API endpoint |
| --- | --- |
| `list_inventory_items` | [Get list of inventory items](/api/private/inventory#get-list-of-inventory-items) |
| `get_inventory_item` | [Get inventory item by ID](/api/private/inventory#get-inventory-item-by-id) |
| `create_inventory_item` | [Create a new inventory item](/api/private/inventory#create-a-new-inventory-item) |
| `update_inventory_item` | [Update an inventory item](/api/private/inventory#update-an-inventory-item) |
| `archive_inventory_item` | [Archive an inventory item](/api/private/inventory#archive-an-inventory-item) |
| `list_inventory_groups` | [Get list of inventory groups](/api/private/inventory#get-list-of-inventory-groups) |
| `get_inventory_group` | [Get inventory group by ID](/api/private/inventory#get-inventory-group-by-id) |
| `create_inventory_group` | [Create a new inventory group](/api/private/inventory#create-a-new-inventory-group) |
| `update_inventory_group` | [Update an inventory group](/api/private/inventory#update-an-inventory-group) |
## Metafields
| Tool | Private API endpoint |
| --- | --- |
| `list_metafields` | [Get shop's metafields](/api/private/metafield#get-shops-metafields) |
| `create_metafield` | [Create a new metadata](/api/private/metafield#create-a-new-metadata) |
| `update_metafield` | [Update the metadata](/api/private/metafield#update-the-metadata) |
## Shop settings
| Tool | Private API endpoint |
| --- | --- |
| `list_tags` | [Get list of tags](/api/private/tag#get-list-of-tags) |
| `list_surveys` | [Get list of surveys](/api/private/survey#get-list-of-surveys) |
| `get_survey` | [Get survey by ID](/api/private/survey#get-survey-by-id) |
| `list_funnels` | [Get list of funnels](/api/private/funnel#get-list-of-funnels) |
| `get_funnel` | [Get funnel by ID](/api/private/funnel#get-funnel-by-id) |
| `list_options` | [Get list of options](/api/private/option#get-list-of-options) |
| `list_countries` | [Get list of countries](/api/private/country#get-list-of-countries) |
## Next steps
- [Connect a client](/developer-resources/subbly-mcp/connect-a-client) — add
the server to Claude Code, Cursor or VS Code.
- [Private API reference](/api/private) — every endpoint, with fields,
responses and examples.
---
## Document: Connect a client
Get a Private API key, then add the Subbly's MCP server to Claude Code, Cursor or VS Code.
URL: /developer-resources/subbly-mcp/connect-a-client
# Connect a client
The server runs at one URL and speaks the streamable HTTP transport, so any
MCP client that supports HTTP servers with a custom header can use it.
```text title="MCP server URL"
https://mcp.subbly.dev/mcp
```
Every request must carry your Private API key in the `X-API-KEY` header. The
server does not store the key; it creates a fresh Private API client for each
tool call with the key that came in.
## Get your API key
Private API keys live in the Subbly admin under
[Settings › API keys](https://www.subbly.co/admin/settings/api-keys). In the
**Private API keys** card, click **Generate API key** to create one for the
agent, then copy it with the icon next to it. You can keep several keys, so
give the agent its own; if it leaks, delete that key from the same card and
generate a new one. A key can read and change everything in your shop, so
treat it as a password and keep it out of files you commit.
Each client below can read the key from an environment variable. Export it
once in your shell:
```bash title="Export the key"
export SUBBLY_PRIVATE_API_KEY='PRIVATE-API-KEY'
```
## Claude Code
One command adds the server. Use `--scope user` to make it available in every
project, or leave the default to keep it to the current one.
```bash title="Add the server"
claude mcp add --transport http subbly https://mcp.subbly.dev/mcp \
--header "X-API-KEY: ${SUBBLY_PRIVATE_API_KEY}"
```
To share the setup with a team without sharing the key, commit a `.mcp.json`
in the project root instead. Claude Code expands `${VAR}` from the
environment when it reads the file.
```json title=".mcp.json"
{
"mcpServers": {
"subbly": {
"type": "http",
"url": "https://mcp.subbly.dev/mcp",
"headers": {
"X-API-KEY": "${SUBBLY_PRIVATE_API_KEY}"
}
}
}
}
```
Run `claude mcp list` to check that the server shows as connected, then ask
Claude something like "list my published subscription products".
## Cursor
Add to Cursor
opens Cursor with the server filled in; replace `PRIVATE-API-KEY` with your
key before you save.
Or add it by hand to `.cursor/mcp.json` in the project, or to
`~/.cursor/mcp.json` for every project. Cursor reads environment variables
with `${env:VAR}`.
```json title=".cursor/mcp.json"
{
"mcpServers": {
"subbly": {
"url": "https://mcp.subbly.dev/mcp",
"headers": {
"X-API-KEY": "${env:SUBBLY_PRIVATE_API_KEY}"
}
}
}
}
```
The server appears under Settings › MCP with its tools listed. Toggle it on
if it is off.
## VS Code
Install in VS Code
opens VS Code with the server filled in; replace `PRIVATE-API-KEY` with your
key before you save.
Or add it by hand to `.vscode/mcp.json`, or run **MCP: Open User
Configuration** to add it for every workspace. An `inputs` entry asks for the
key once and stores it in the secret store, so it never lands in the file.
```json title=".vscode/mcp.json"
{
"inputs": [
{
"type": "promptString",
"id": "subbly-api-key",
"description": "Subbly Private API key",
"password": true
}
],
"servers": {
"subbly": {
"type": "http",
"url": "https://mcp.subbly.dev/mcp",
"headers": {
"X-API-KEY": "${input:subbly-api-key}"
}
}
}
}
```
## Any other client
Most clients take the same JSON shape. Give it the URL and the header:
```json title="Generic config"
{
"mcpServers": {
"subbly": {
"url": "https://mcp.subbly.dev/mcp",
"headers": {
"X-API-KEY": "PRIVATE-API-KEY"
}
}
}
}
```
## Try it in the MCP Inspector
The Inspector is a browser UI for calling tools by hand. It is the quickest
way to see what a tool returns before you hand it to an agent.
```bash title="Open the Inspector"
npx @modelcontextprotocol/inspector
```
In the Inspector pick **Streamable HTTP**, enter the server URL, add an
`X-API-KEY` header under Authentication and click **Connect**. The
**Tools** tab lists every tool with its input schema.
## What the client sees
- **Tools only.** The server offers tools, no prompts or resources.
- **Structured output.** Every tool returns its result twice: as JSON text in
`content` and as `structuredContent`, typed by the tool's output schema.
- **Stateless.** Each request is its own session. Nothing is remembered
between calls, so a client can reconnect at any time.
- **Errors.** A tool that fails returns an MCP error whose message starts
with `Tool execution failed:` followed by the Private API's message. A
missing key returns `API key is required`.
## Clients that need OAuth
Claude.ai, Claude Desktop and ChatGPT add remote servers as connectors, and
a connector cannot send a custom header. Those clients need an OAuth flow,
which the server does not offer today. Use one of the clients above.
## Next steps
- [Tools](/developer-resources/subbly-mcp/tools) — everything the agent can
now do.
- [Private API reference](/api/private) — the meaning of every field a tool
takes.
---
## Document: Run a funnel
Show the offers of a funnel before, during and after checkout with the Subbly.js SDK.
URL: /developer-resources/subbly-js-sdk/run-a-funnel
# Run a funnel
A funnel is a sequence of offers you set up in Subbly. Each offer is a
**step**: a headline, a description, and one or more variants or plans, often
at a discount. The Subbly.js SDK gives you the next step for a cart; you draw
it, and the customer takes it or skips it.
## The three phases
A funnel has three phases, and each phase has its own set of methods:
- **Pre-purchase** offers come before checkout, while the customer still edits
the cart.
- **Mid-purchase** offers come during checkout.
- **Post-purchase** offers come after the customer pays. They run only after
`subbly.checkout.purchase` succeeds, on the thank-you page.
## The loop
Every phase works the same way:
1. Fetch the next step for the cart with the phase's `Fetch` method. The
response is `{ step }`.
2. If `step` is `null`, the customer has seen every offer. Stop.
3. Show the offer: `step.title`, `step.description`, and the variants and plans
in `step.offer.products`. `step.offer.type` says how many the customer may
take: `single`, `multiple` or `one_click`.
4. Call the phase's `Accept` method with `step.id` and the `productId` of each
product taken, or the phase's `Reject` method with `step.id` to skip.
5. Fetch again.
Accepting a pre-purchase or a mid-purchase offer adds the products to the cart
and returns a plain cart, not the `subbly.cart` model. `subbly.cart` still holds
the old contents, so call `subbly.cart.load()` afterwards.
Accepting a post-purchase offer charges a new purchase on the card the customer
already used. It returns the invoice, the subscription and the orders it
created, not a cart.
The pre-purchase phase also has `prePurchaseRefresh`, which clears what the
customer accepted or skipped so the sequence starts again from the first step.
## Pre-purchase
Run this loop on the cart page, or wherever the customer reviews the cart before
checkout.
```js title="Offer before checkout"
async function runPrePurchase() {
const cartId = subbly.cart.id
while (true) {
const { step } = await subbly.funnels.prePurchaseFetch(cartId, {
expand: ['parent']
})
if (!step) break
const productId = await showOffer(step) // resolves with an id, or null
if (productId) {
await subbly.funnels.prePurchaseAccept(cartId, step.id, {
offers: [{ productId }]
})
await subbly.cart.load()
} else {
await subbly.funnels.prePurchaseReject(cartId, step.id)
}
}
}
```
`showOffer` is your own function. It draws the step and resolves with the `id`
of the product the customer picked from `step.offer.products`, or `null` when
they skip. With `expand: ['parent']`, each product also carries its parent
product, so you can show its name and images.
## Mid-purchase
Run the same loop during checkout, with the `midPurchase` methods. The payload
and the return value are the same as in the pre-purchase phase, so reload
`subbly.cart` after each accept here too.
```js title="Offer during checkout"
async function runMidPurchase() {
const cartId = subbly.cart.id
while (true) {
const { step } = await subbly.funnels.midPurchaseFetch(cartId)
if (!step) break
const productId = await showOffer(step)
if (productId) {
await subbly.funnels.midPurchaseAccept(cartId, step.id, {
offers: [{ productId }]
})
await subbly.cart.load()
} else {
await subbly.funnels.midPurchaseReject(cartId, step.id)
}
}
}
```
## Post-purchase
Run this loop after `subbly.checkout.purchase` resolves. Pass the ID of the
cart that was paid. The accept needs a signed-in customer, and each accepted
offer becomes its own purchase, so the result carries its own `invoice`,
`subscription` and `orders`.
When the checkout did not set a shipping address or a shipping method, send
`shippingAddressId` and `shippingMethodId` in the payload. When the charge asks
for 3-D Secure, the call rejects with `payment_requires_action`; finish the
verification with the [payment intent methods](/reference/subbly-sdk/payment-intents),
then call the accept again with `paymentIntentId`.
```js title="Offer after checkout"
async function runPostPurchase(cartId) {
while (true) {
const { step } = await subbly.funnels.postPurchaseFetch(cartId)
if (!step) break
const productId = await showOffer(step)
if (productId) {
const { invoice, subscription, orders } =
await subbly.funnels.postPurchaseAccept(cartId, step.id, {
offers: [{ productId }]
})
showReceipt(invoice, subscription, orders)
} else {
await subbly.funnels.postPurchaseReject(cartId, step.id)
}
}
}
const cartId = subbly.cart.id
await subbly.checkout.purchase(cartId)
await runPostPurchase(cartId)
```
## Next steps
- [Funnels reference](/reference/subbly-sdk/funnels) — every method, with the
fields of a step and of the accept payloads.
- [Cart reference](/reference/subbly-sdk/cart) — `subbly.cart.load` and the
other cart methods.
- [Checkout reference](/reference/subbly-sdk/checkout) — `subbly.checkout.purchase`,
which the post-purchase phase follows.
---
## Document: Keep Subbly.js up to date
Pin a version of the Subbly.js SDK in production and upgrade on your own schedule.
URL: /developer-resources/subbly-js-sdk/keep-it-up-to-date
# Keep Subbly.js up to date
Pin a version in production, so a new release cannot change your storefront
before you test it.
- **npm.** `npm install` writes the exact version to your lock file. To
upgrade, run `npm install @subbly/sdk@latest`, then test and commit the new
lock file.
- **CDN.** The bare URL always serves the latest release. Put the version in
the URL instead, and change it when you upgrade:
```html title="Pin the CDN version"
```
To see which version a page runs, read
[`subbly.VERSION`](/reference/subbly-sdk/setup#version).
---
## Document: Install Subbly.js
Add the Subbly.js SDK from npm or load it from a CDN, then start it with Subbly.init.
URL: /developer-resources/subbly-js-sdk/install
# Install Subbly.js
Subbly.js ships as an npm package and as a browser bundle. Pick one, then
start the SDK with `Subbly.init`.
## Install from npm
```bash title="Install the package"
npm install @subbly/sdk
```
Import it with ES modules. This is the way to use Subbly.js with a modern
JavaScript framework, such as React or Vue. The package ships its own
TypeScript types.
```js title="Start the SDK"
import Subbly from '@subbly/sdk'
const subbly = await Subbly.init({
apiKey: 'API_KEY',
})
```
`Subbly.init` is the only supported entry point. It loads the shop before it
resolves, so `subbly.shop` is ready when you get the instance. Call it once per
page: the SDK keeps one shared HTTP client, and a second call with a different
key quietly reuses the first one.
## Load from a CDN
The package also ships a browser bundle. Load it on your HTML page and it
defines the global `Subbly`. `window.Subbly` is the class, not an instance, so
you still call `Subbly.init`. Load it once per page.
```html title="Load the script"
```
## Next steps
- [Configure](/developer-resources/subbly-js-sdk/configure) — every option
`Subbly.init` takes.
- [Keep it up to date](/developer-resources/subbly-js-sdk/keep-it-up-to-date)
— pin a version before you go to production.
---
## Document: Handle errors
Read the error code a failed Subbly.js request rejects with.
URL: /developer-resources/subbly-js-sdk/handle-errors
# Handle errors
A failed request rejects with a plain object carrying a `code`, not an `Error`.
```js title="Read the error code"
try {
await subbly.cart.addItem({ productId: 3301 })
} catch (error) {
if (error.code === 'out_of_stock') {
// tell the customer
}
}
```
The full list of codes is on the
[Subbly.js SDK reference](/reference/subbly-sdk#errors).
---
## Document: Configure Subbly.js
Pass your Storefront API key, a cart to load and a customer to authenticate to Subbly.init.
URL: /developer-resources/subbly-js-sdk/configure
# Configure Subbly.js
`Subbly.init` takes your Storefront API key, and optionally a cart to load and a
customer to authenticate. For every field, see
[`Subbly.init(config)`](/reference/subbly-sdk/setup#init).
```js title="Start with a cart and a customer"
const subbly = await Subbly.init({
apiKey: 'API_KEY',
cartId: 'aaea067c-6364-4157-82e9-71b6edfd84a0',
loadCart: true,
accessToken: 'eyJh...eyJ9',
})
```
With `loadCart: true`, the SDK loads the cart named by `cartId` or by the
`subbly_cart_id` cookie, creates a new one when there is none, and copies the
cart currency onto the instance.
Your Storefront API key is in the Subbly admin, under Settings, Developers.
## Next steps
- [Build a checkout](/developer-resources/subbly-js-sdk/build-a-checkout) —
from an empty cart to a paid order.
- [Setup reference](/reference/subbly-sdk/setup) — `Subbly.init`, `VERSION`
and the instance properties.
---
## Document: Build a checkout
The short path from an empty cart to a paid order with the Subbly.js SDK.
URL: /developer-resources/subbly-js-sdk/build-a-checkout
# Build a checkout
A short path from an empty cart to a paid order:
```js title="From cart to purchase"
await subbly.cart.create()
await subbly.cart.addItem({ productId: 3301, quantity: 1 })
await subbly.cart.update({
customer: { email: 'ada@example.com', tosConsent: true },
shippingAddress: { /* … */ },
})
const methods = await subbly.cart.getShippingMethods({
countryId: 826,
zip: 'NW1 5JD',
})
await subbly.cart.update({ shippingMethodId: methods[0].id })
await subbly.auth.login({ email, password })
await subbly.cart.attachCustomer()
const { invoice } = await subbly.checkout.purchase(subbly.cart.id)
```
Each step has its own page:
[cart](/reference/subbly-sdk/cart#create),
[items](/reference/subbly-sdk/cart#add-item),
[shipping](/reference/subbly-sdk/cart#get-shipping-methods),
[authentication](/reference/subbly-sdk/auth#login) and
[checkout](/reference/subbly-sdk/checkout#purchase).
## Next steps
- [Handle errors](/developer-resources/subbly-js-sdk/handle-errors) — what a
failed call rejects with.
- [Run a funnel](/developer-resources/subbly-js-sdk/run-a-funnel) — add
upsell offers before, during and after checkout.
- [Storefront API](/api/storefront) — the HTTP API the SDK calls.
---
## Document: Wire the widget into your page
Buy links, your own cart button and counter, the widget container, and the content slots in the checkout.
URL: /developer-resources/cart-widget/wire-it-into-your-page
# Wire the widget into your page
The widget reads four kinds of markup on your page: buy links, a cart button
and a counter, its own container, and content slots in the checkout.
## Sell from your own links
Turn on `interceptProductLinks` and the widget watches every click on the
page. When the link points at a Subbly product, bundle or survey, the widget
cancels the navigation and calls
[`configureItem`](/reference/cart-widget/methods#configure-item) with a
quantity of `1`. So a buy button is an ordinary link:
```html title="Buy buttons"
SubscribeBuild your boxTake the quizSend as a gift
```
The widget reads the nearest `` above the clicked element, ignores the host
name, and matches case-insensitively, so a link to the Subbly-hosted checkout
works as well as a path on your own site. `add` and `buy` are both accepted.
| Link | What it does |
| :-- | :-- |
| `/checkout/buy/{productId}` | Adds a product. The widget asks for the plan or the variant when the customer still has a choice to make. |
| `/checkout/buy/bundle/{bundleId}` | Opens the bundle builder. |
| `/checkout/buy/survey/{surveyId}` | Starts the survey flow. |
| `?gift=1` | Any value opens the gift view after the item is added. |
| `?coupon=CODE` | Holds the coupon and applies it once the cart fits it. |
When a link cannot carry a Subbly URL, put the product ID (digits only) in a
`data-subbly-product` attribute instead. The widget reads it from the nearest
`` above the clicked element; a Subbly URL in the `href` wins when the link
carries both.
```html title="A button with no Subbly URL"
Subscribe
```
Before the widget has loaded, a stub handler cancels clicks on these links but
does not remember them. A click that early is dropped.
## Show your own cart button and item count
The widget can draw a floating cart button (`cartButton: true`), but most
sites have a cart icon of their own. Point the widget at it with
`cartToggleEl`, and at the badge that shows the number of items with
`cartCounterEl`. Both take a CSS selector or an element:
```html title="Your own cart button"
Cart (0)
```
A click on a toggle match opens or closes the widget, and the default click
action is cancelled. The widget replaces the content of every counter match
with the number of active items on every cart change. Clicks are caught on
`document`, so a header your framework renders later still works. On a
framework that routes with `pushState`, update the counter yourself from
[`CART_UPDATED`](/reference/cart-widget/events#cart-updated).
One more selector needs no setting: when the page holds an `a.subbly-cart`,
the widget hides its own floating cart button.
Both keys are on [Settings keys](/reference/cart-widget/settings#settings).
## Place the widget
The widget mounts in a `div#subbly-cart-widget`. By default it creates the
element, appends it to ``, and gives it `position: fixed`,
`z-index: 2147483000` and the class `SubblyAppContainer`. The app renders
inside, under `.SubblyApp`.
To choose where the element sits, put an empty one in your markup and the
widget reuses it in place. Any other element with that ID is left alone, and
the widget creates its own with a random suffix, such as
`subbly-cart-widget-a1b2c`.
```html title="Place the container yourself"
```
The class names inside are hashed at build time and change between releases.
Style the widget through its
[CSS variables](/reference/cart-widget/theming#css-variables), not its
classes.
## Add content to the checkout
The checkout and the receipt render empty elements with stable IDs at fixed
points. Each collapses while it is empty, so filling one leaves no gap when
you do not.
Fill them from Checkout JS, which you write in the Subbly admin under Shop
settings, Checkout JS. It reaches the page as `subblyCart.shop.checkoutJs`.
The widget injects it only in standalone checkout mode (`checkoutOnly: true`):
it re-creates inline `
```
Your Storefront API key is in the Subbly admin, under Settings, Developers.
The script tag carries no `data-` attributes; everything goes through the
config object. `cart-widget.js` is a small loader that imports the real bundle
and its stylesheet, so do not cache it. The files it pulls from `assets/`
carry a hash in the name and can be cached for a long time.
## Wait for the widget
The script loads asynchronously. The widget fires two events on `window`.
Neither carries data.
| Event | Meaning |
| --- | --- |
| `subbly-cart-loaded` | The code is downloaded and `window.SubblyCart`, the class, is set. There is no instance yet. |
| `subbly-cart-initialized` | The widget is up and `window.subblyCart`, the instance, is set. **Wait for this one.** |
There is no ready callback and no command queue. Guard against the event having
already fired:
```js title="Run code once the widget is ready"
const onReady = (fn) => {
if (window.subblyCart) {
fn(window.subblyCart)
return
}
window.addEventListener('subbly-cart-initialized', () =>
fn(window.subblyCart)
)
}
onReady((cart) => {
cart.open()
})
```
The other pages in this section use this `onReady` helper.
The widget also emits its own
[`CART_READY`](/reference/cart-widget/events#cart-ready) event, but it fires
inside `initialize()`, before the instance is handed out, so no handler of
yours can see it. Read the cart from `subblyCart.cart` once
`subbly-cart-initialized` has fired instead.
## Next steps
- [Configure](/developer-resources/cart-widget/configure) — the keys of
`window.subblyConfig`.
- [Wire it into your page](/developer-resources/cart-widget/wire-it-into-your-page)
— buy links, your own cart button and the container.
---
## Document: Drive the widget from your code
Call the SubblyCart.js methods and follow what the customer does through its events.
URL: /developer-resources/cart-widget/drive-it-from-your-code
# Drive the widget from your code
Once the widget is ready, every method is on `window.subblyCart`. Use the
`onReady` helper from
[Wait for the widget](/developer-resources/cart-widget/install#wait-for-the-widget).
## Call a method
```js title="Open the checkout from your own button"
onReady(async (cart) => {
await cart.configureItem({ productId: 555 })
cart.open('checkout')
})
```
Every method is on [Methods](/reference/cart-widget/methods).
## Follow what the customer does
The widget emits an event for every step the customer takes: the cart
changes, the panel opens, an email is captured, a purchase completes.
```js title="Track purchases"
onReady(({ events }) => {
events.on(events.type.PURCHASE_COMPLETED, (err, data) => {
track('purchase', {
value: data.purchase.invoice?.total ?? 0,
currency: data.cart.currencyCode
})
})
})
```
Every event and its payload is on
[Events](/reference/cart-widget/events#on).
---
## Document: Configure the widget
The keys of window.subblyConfig, and how to boot the SubblyCart.js widget by hand.
URL: /developer-resources/cart-widget/configure
# Configure the widget
`window.subblyConfig` takes a dozen keys. Five of them cover most first
integrations:
- `apiKey`: your Storefront API key. The only required key.
- `languageCode`: a two-letter language. Without it, the widget takes the
`lang` query parameter, then the `lang` attribute of ``, then the
browser's language.
- `settings`: the page hooks — which links the widget catches, which element
opens it, which element shows the item count. See
[Wire it into your page](/developer-resources/cart-widget/wire-it-into-your-page).
- `init`: set it to `false` to boot the widget yourself.
- `checkoutUrl`: a page on your site that owns the checkout. The widget sends
the customer there and stands down on that path.
```html title="A typical config"
```
## Boot the widget by hand
With `init: false`, the loader sets no global. Call
[`SubblyCart.initialize`](/reference/cart-widget/methods#initialize) yourself
and keep the instance it returns:
```js title="Boot the widget by hand"
window.subblyConfig = { init: false }
window.addEventListener('subbly-cart-loaded', async () => {
const cart = await window.SubblyCart.initialize({
apiKey: 'YOUR_STOREFRONT_API_KEY'
})
cart.events.on(cart.events.type.CART_UPDATED, () => {
console.log('cart changed')
})
})
```
Every key, with its type and default, is on
[Configuration keys](/reference/cart-widget/settings#config).
[`SubblyCart.initialize(config)`](/reference/cart-widget/methods#initialize)
takes the same object, and you can change the URL keys later with
[`configure`](/reference/cart-widget/methods#configure).