Whim SDK

HTTP API

REST endpoints for product catalogs and pricing. The script SDK and React package call these under the hood, but you can hit them directly too.

Base URL will be provided during onboarding.

Most integrations don't call these directly. The script SDK fetches pricing automatically on init, and @whim-sdk/react has getWhimConfig() and getWhimProduct() which call these endpoints and transform the responses. These docs are for custom integrations or debugging.

Endpoints

GET /api/whim/{merchantId}

Returns the full product catalog for a merchant: products, pricing, discount info, and currency.

// Fetch full catalog
fetch('{baseUrl}/api/whim/{merchantId}')

// Filter to specific products
fetch('{baseUrl}/api/whim/{merchantId}?products=product-a,product-b')
ParameterInDescription
merchantIdpathYour Whim merchant ID
productsqueryOptional. Comma-separated product names to filter the response

Response:

{
  merchantId: "your-merchant-id",
  currency: "USD",
  checkoutBaseUrl: "https://rent.your-store.com", // your checkout host, set during onboarding
  programVerb: "rent",                 // verb for teaser copy, e.g. "rent", "try"
  benefits: [                          // program benefit bullets
    "Enjoy as long as you like",
    "Cancel and return anytime"
  ],
  faqs: [
    { question: "Can I cancel anytime?", answer: "Yes, ..." }
  ],
  products: [
    {
      productName: "product-a",
      isOutOfStock: false,
      price: {
        amount: 4900,              // cents
        currencyCode: "USD",
        recurringInterval: "MONTH",
        recurringCount: 1
      },
      discountPercent: 20,         // null if no intro discount
      discountMonths: 3            // null if no intro discount
    },
    // ...
  ]
}

GET /api/whim/{merchantId}/products/{productName}

Returns a single product with pricing, variant attributes, and addons. For configurable products, you get the full variant tree and attribute definitions for client-side switching.

// Simple product
fetch('{baseUrl}/api/whim/{merchantId}/products/product-a')

// Configurable product: returns full variant tree + addons
fetch('{baseUrl}/api/whim/{merchantId}/products/product-b')
ParameterInDescription
merchantIdpathYour Whim merchant ID
productNamepathWhim product name or configurable product name

Simple product response:

{
  resolutionType: "simple",
  product: {
    name: "product-a",
    displayName: "Product A",
    images: ["https://..."],
    attributeValues: [],
    price: {
      amount: 1600,
      currencyCode: "USD",
      isRecurring: true,
      recurringInterval: "MONTH",
      recurringCount: 1,
      compareAtAmount: 24900
    },
    addonKeys: ["setup-kit"]
  },
  addons: [
    {
      name: "setup-kit",
      displayName: "Setup Kit",
      addonKey: "setup-kit",
      isOptional: false,
      defaultOptionKey: "setup-kit",
      type: "PRODUCT",
      options: [
        {
          optionKey: "setup-kit",
          product: { name: "setup-kit", price: { amount: 0, ... } }
        }
      ]
    }
  ]
}

Configurable product response:

{
  resolutionType: "configurable",
  // Default resolved variant
  product: {
    name: "product-b-small-black",
    displayName: "Product B (Small, Black)",
    images: ["https://..."],
    attributeValues: [
      { attribute: "size", option: { name: "small", value: "small" } },
      { attribute: "color", option: { name: "black", value: "black" } }
    ],
    price: {
      amount: 4900,
      currencyCode: "USD",
      isRecurring: true,
      recurringInterval: "MONTH",
      recurringCount: 1,
      compareAtAmount: 59900
    },
    addonKeys: ["case", "shipping-fee"]
  },
  // All variant products under this configurable
  products: [
    { name: "product-b-small-black", attributeValues: [...], price: {...}, addonKeys: ["case", "shipping-fee"] },
    { name: "product-b-small-white", attributeValues: [...], price: {...}, addonKeys: ["case", "shipping-fee"] },
    { name: "product-b-large-black", attributeValues: [...], price: {...}, addonKeys: ["ring", "case", "shipping-fee"] },
    // ...
  ],
  // Configurable product metadata
  configurableProduct: {
    name: "product-b",
    displayName: "Product B",
    productNames: ["product-b-small-black", "product-b-small-white", ...],
    attributes: [
      {
        name: "size",
        selectorType: "ONE_OF",
        options: [
          { name: "small", value: "small", urlValue: "small" },
          { name: "large", value: "large", urlValue: "large" }
        ]
      },
      {
        name: "color",
        selectorType: "ONE_OF",
        options: [
          { name: "black", value: "black", urlValue: "black" },
          { name: "white", value: "white", urlValue: "white" }
        ]
      }
    ]
  },
  // All addon definitions for this product
  addons: [
    {
      name: "case",
      displayName: "Protective Case",
      addonKey: "case",
      isOptional: true,
      defaultOptionKey: null,
      type: "PRODUCT",
      options: [
        { optionKey: "black", product: { name: "case-black", price: { amount: 1000, ... } } },
        { optionKey: "white", product: { name: "case-white", price: {...} } }
      ]
    },
    {
      name: "Shipping Fee",
      displayName: "Shipping Fee",
      addonKey: "shipping-fee",
      isOptional: false,          // required, auto-included
      defaultOptionKey: "shipping-fee",
      type: "FEE",
      options: [{ optionKey: "shipping-fee", product: { name: "shipping-fee", price: { amount: 1500, isRecurring: false } } }]
    }
  ]
}
Addon resolution: Each product's addonKeys tells you which addons from the top-level addons array apply to that variant. In checkout URLs, addons use key-value pairs: addons(case=brown,ring=r1) where the key is the addonKey and the value is the selected optionKey.

GET /api/whim/{merchantId}/prices

Returns current prices for a batch of products. This is the endpoint the script SDK's lazy catalog calls as product tiles scroll into view, useful for large catalogs where fetching the full merchant catalog up front is impractical.

// Up to 50 products per request
fetch('{baseUrl}/api/whim/{merchantId}/prices?products=product-a,product-b,product-c')
ParameterInDescription
merchantIdpathYour Whim merchant ID
productsqueryRequired. Comma-separated product names, max 50 per request. Duplicates are de-duplicated.

Response:

{
  merchantId: string;
  products: {                          // same shape as the catalog's products
    productName: string;
    isOutOfStock: boolean;
    price: {
      amount: number;                  // cents
      currencyCode: string;
      recurringInterval: string;
      recurringCount: number;
    };
    discountPercent: number | null;
    discountMonths: number | null;
  }[];
  retryable: string[];                 // names that failed transiently, retry these
}

Requested products that don't exist for the merchant are silently omitted from products; a name that's absent from both products and retryable is permanently unavailable. Names in retryable failed due to a transient upstream error and are worth requesting again; responses containing any retryable names are not cached.

Tip: The CDN caches on the exact URL, so the products list needs a stable order; the same names in a different order is a cache miss. The script SDK requests products in page (DOM) order, which is identical for every visitor to the same page. If you're calling the API directly, any consistent order works; just make sure it doesn't vary between requests.

GET /api/whim/{merchantId}/customer/exists

Checks if a customer with that email or phone number already has a Whim account. You can use this to conditionally render a portal link for returning customers that are already logged in to your site.

// Search by email
const res = await fetch('{baseUrl}/api/whim/{merchantId}/customer/exists?email=user@example.com');
const { exists } = await res.json();

// Or by phone: E.164 with country code; URL-encode the leading + as %2B
const res = await fetch('{baseUrl}/api/whim/{merchantId}/customer/exists?phone=%2B15551234567');
Phone format: Provide phone numbers in E.164 format with the country code, e.g. +15551234567. Matching is exact on the stored number, so formatting characters (spaces, dashes, parentheses) cause a miss. A bare national number like 5551234567 also matches US accounts, but E.164 is unambiguous across regions and recommended. In a raw query string the leading + must be percent-encoded as %2B, otherwise it decodes to a space.
ParameterInDescription
merchantIdpathYour Whim merchant ID
emailqueryCustomer email address. Provide one identifier per request; if both are sent, email takes precedence.
phonequeryCustomer phone number in E.164 (see note above). Provide one identifier per request; if both are sent, email takes precedence.

Response:

{ exists: boolean }

Responses are not cached (Cache-Control: private, no-store) since customer status can change at any time.

Response Types

Catalog Response

Returned by GET /api/whim/{merchantId}.

{
  merchantId: string;
  currency: string;                    // e.g. "USD"
  checkoutBaseUrl: string;             // checkout domain for this merchant
  programVerb?: string;                // verb for teaser copy, e.g. "rent"
  benefits?: string[];                 // program benefit bullets
  faqs?: { question: string, answer: string }[];
  products: {
    productName: string;
    isOutOfStock: boolean;             // true if currently unavailable
    price: {
      amount: number;                  // cents
      currencyCode: string;
      recurringInterval: string;       // "MONTH", "YEAR", etc.
      recurringCount: number;
    };
    discountPercent: number | null;     // intro discount percentage
    discountMonths: number | null;     // intro discount duration
  }[];
}

Product Response

Returned by GET /api/whim/{merchantId}/products/{productName}. The resolutionType field tells you whether this is a simple product or a configurable product with variants.

// Common product shape (used for variants and simple products)
interface Product {
  name: string;
  displayName: string;
  isOutOfStock: boolean;
  images: string[];
  attributeValues: {
    attribute: string;                 // attribute name, e.g. "size", "color"
    option: { name: string; value: string; };
  }[];
  price: {
    amount: number;                    // cents
    currencyCode: string;
    isRecurring: boolean;
    recurringInterval: string | null;  // "MONTH", null if !isRecurring
    recurringCount: number | null;
    compareAtAmount: number | null;    // buy price reference in cents
  };
  addonKeys?: string[];                // which addons apply to this product
}

Configurable Product

Metadata about the parent configurable product. Contains attribute definitions used for variant resolution.

interface ConfigurableProduct {
  name: string;
  displayName: string;
  productNames: string[];              // variant product names
  attributes: {
    name: string;                      // "size", "color"
    selectorType: "ONE_OF";
    options: {
      name: string;
      value: string;                   // used for variant matching
      urlValue: string;                // used in checkout URL
    }[];
  }[];
}

interface AddonDef {
  name: string;
  displayName: string;
  addonKey: string;                    // checkout URL key: "case", "warranty"
  isOptional: boolean;
  defaultOptionKey: string | null;
  type: "FEE" | "PRODUCT";
  options: {
    optionKey: string;                 // checkout URL value: "black", "w1"
    product: Product;
  }[];
}

Details

Checkout URLs

Checkout URLs are constructed client-side since they depend on which product and addon options the user selects. Use the checkoutBaseUrl from the catalog response as the base.

// Single product
{checkoutBaseUrl}/{merchantId}/checkout?products=product-a

// With addon selections (addonKey=optionKey)
{checkoutBaseUrl}/{merchantId}/checkout?products=product-b-small-black(addons(case=black,ring=r1))

// Multiple products
{checkoutBaseUrl}/{merchantId}/checkout?products=product-b-small-black(addons(case=black)),product-a

Addon keys and option keys come from the product response's addons array. Required addons (isOptional: false) are auto-included at checkout, so you don't need to add them to the URL.


Caching

Catalog and product responses include cache headers: Cache-Control: public, max-age=300, s-maxage=600. Browser caches for 5 minutes, CDN caches for 10 minutes. Prices update on a schedule, not in real-time, so caching is safe.

The batch prices endpoint uses the same 5-minute browser / 10-minute CDN freshness but adds stale-while-revalidate=300, and disables caching entirely (no-store) when the response contains retryable names, so a transient upstream failure is never pinned in a shared cache. Customer existence checks are never cached.


Errors

All error responses return JSON with an error field.

StatusReason
400Missing or invalid merchantId; missing required parameters (no email/phone on the customer check, no products on the batch prices endpoint); or more than 50 products requested per batch
404Merchant not found, no products configured for this merchant, or product not found
// 404 example
{ "error": "Merchant 'unknown-merchant' not found." }