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.
@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')
| Parameter | In | Description |
|---|---|---|
merchantId | path | Your Whim merchant ID |
products | query | Optional. 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')
| Parameter | In | Description |
|---|---|---|
merchantId | path | Your Whim merchant ID |
productName | path | Whim 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 } } }]
}
]
}
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')
| Parameter | In | Description |
|---|---|---|
merchantId | path | Your Whim merchant ID |
products | query | Required. 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.
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');
+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.
| Parameter | In | Description |
|---|---|---|
merchantId | path | Your Whim merchant ID |
email | query | Customer email address. Provide one identifier per request; if both are sent, email takes precedence. |
phone | query | Customer 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.
| Status | Reason |
|---|---|
400 | Missing 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 |
404 | Merchant not found, no products configured for this merchant, or product not found |
// 404 example
{ "error": "Merchant 'unknown-merchant' not found." }