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 getWhimState() which calls these endpoints and transforms the response. 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://checkout.fragile.co",
  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}/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 (digits only, no dashes or parentheses)
const res = await fetch('{baseUrl}/api/whim/{merchantId}/customer/exists?phone=5551234567');
ParameterInDescription
merchantIdpathYour Whim merchant ID
emailqueryCustomer email address (provide email, phone, or both)
phonequeryCustomer phone number (provide email, phone, or both)

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
  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

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.


Errors

All error responses return JSON with an error field.

StatusReason
400Missing or invalid merchantId
404Merchant not found, or no products configured for this merchant
// 404 example
{ "error": "Merchant 'unknown-merchant' not found." }