Behio Storefront SDK
Advanced

TypeScript Types

Complete type reference for the SDK

TypeScript Types

Complete reference of all types exported by @behio/storefront-sdk. All types are fully exported and can be imported directly:

import type { ProductDetail, Cart, CheckoutInput } from "@behio/storefront-sdk";

Types match the API exactly (SDK 2.14.0)

Every type is checked against the public API schema on each backend change, and responses are checked against it at runtime. Since 2.14.0 a field the API returns as null is typed | null (for example imageUrl, description, gtin, compareAtPrice, Cart.discount). The API sent these nulls before as well; only the types now say so. If your code passes such a value where string | undefined is expected, write value ?? undefined.


Configuration

BehioStorefrontConfig

interface BehioStorefrontConfig {
  /** API key (public: pk_live_xxx or private: sk_live_xxx) */
  apiKey: string;
  /** Backend base URL. Default: https://be.behio.com */
  baseUrl?: string;
  /** Default locale for catalog requests. Default: none (uses shop default) */
  locale?: string;
  /** Default currency for price resolution. Default: none (uses shop default) */
  currency?: string;
  /** Custom fetch implementation (for Node.js < 18 or testing) */
  fetch?: typeof fetch;
  /** Request timeout in milliseconds. Default: 30000 (30s) */
  timeout?: number;
  /** Number of retries on network/5xx errors. Default: 1 */
  retries?: number;
  /** Base delay between retries in ms (multiplied by attempt). Default: 1000 */
  retryDelay?: number;
  /**
   * Real visitor IP for server-side rendering, sent as X-Behio-Visitor-Ip so
   * the backend rate-limits per visitor instead of per rendering server. The
   * Next.js adapter fills this automatically from the request headers; only
   * trusted hosting IPs may set it, a browser sending it gains nothing.
   */
  visitorIp?: string | (() => string | null | undefined | Promise<string | null | undefined>);
}

Products

ProductListItem

Represents a product in list/collection views.

Popisy produktu jsou HTML

shortDescription i longDescription píše obchodník v rich text editoru v administraci, takže API vrací HTML (&lt;h3&gt;...&lt;/h3&gt;&lt;p&gt;...&lt;/p&gt;), nikdy holý text. Když je vypíšete jako {product.shortDescription}, React je zaescapuje a návštěvník uvidí doslova značky.

API obsah sanitizuje při čtení (allowlist: nadpisy, seznamy, odkazy, obrázky, tabulky; skripty, event handlery a javascript: odkazy jdou pryč), takže se dá vložit přes dangerouslySetInnerHTML. Šablony Behio na to mají komponentu RichText. Do meta description, JSON-LD, alt textů a jednořádkových popisků na kartách patří čistý text, ne HTML.

interface ProductListItem {
  id: string;
  slug: string;
  name: string;
  /** HTML z rich text editoru (sanitizované), ne holý text. */
  shortDescription?: string;
  sku: string;
  gtin?: string;
  price: ProductPrice;
  inStock: boolean;
  stockQuantity?: number;
  /** "Only X left". Non-null ONLY when the merchant enabled the nudge AND 0 < stock <= threshold. */
  lowStockRemaining?: number | null;
  /** Resolved availability (merchant preset or derived in-stock / sold-out), label localized. */
  availability: ProductAvailability;
  image?: string | null;
  labels: ProductLabel[];
  isFeatured: boolean;
}

ProductAvailability

interface ProductAvailability {
  /** Stable machine code: preset slug or derived 'in-stock' / 'sold-out'. */
  code: string;
  /** Localized display label, render as-is. */
  label: string;
  /** Optional merchant hex badge color. */
  color?: string | null;
  /** "Available from" date (epoch ms) for preorder-style states. */
  restockAt?: number | null;
}

ProductDetail

Full product detail, extends ProductListItem.

interface ProductDetail extends ProductListItem {
  /** HTML z rich text editoru (sanitizované), ne holý text. */
  longDescription?: string;
  images: Array<{ url: string; alt?: string | null; order: number; role?: string }>;
  categories: Array<{ id: string; slug: string; name: string }>;
  variants: ProductVariant[];
  volumePricing: ProductVolumePrice[];
  /** Curated spec table. Array order is the display order; empty = render nothing. */
  parameterGroups: ProductParameterGroup[];
  seo: {
    title?: string | null;
    description?: string | null;
    keywords?: string | null;
  };
  productGroups?: Array<{
    slug: string;
    name: string;
    products: ProductListItem[];
  }>;
  /** Product weight (from warehouse item) */
  weight?: number | null;
  /** Weight unit: GRAM, KILOGRAM, TONNE */
  weightUnit?: string | null;
}

ProductPrice

interface ProductPrice {
  amount: number;
  currency: string;
  compareAtPrice?: number | null;
}

ProductVolumePrice

interface ProductVolumePrice {
  minQuantity: number;
  price: number;
  currency?: string;
}

ProductVariant

interface ProductVariant {
  id: string;
  sku: string;
  /** Localized name of this variant. */
  name: string;
  /**
   * Deep-link token, NOT a URL: a variant has no page of its own, so it lives at
   * `/product/{product.slug}?variant={variantSlug}`. Since SDK 1.1.0.
   */
  variantSlug: string;
  /** Own texts; null = inherit the parent's. HTML. Since SDK 1.1.0. */
  shortDescription?: string | null;
  longDescription?: string | null;
  /** Own gallery; empty = keep showing the parent's. Since SDK 1.1.0. */
  images: Array<{url: string; alt?: string | null; order: number; role?: string}>;
  /** Own SEO/OG overrides, null when none. Since SDK 1.1.0. */
  seo?: {
    title?: string | null;
    description?: string | null;
    keywords?: string | null;
    ogTitle?: string | null;
    ogDescription?: string | null;
    ogImage?: string | null;
  } | null;
  /** Axis name/value pairs, e.g. [{name: 'Barva', value: 'modrá'}]. */
  attributes: Array<{name: string; value: string}>;
  price: ProductPrice | null;
  inStock: boolean;
  stockQuantity?: number;
  /** "Only X left" for this variant, same contract as ProductListItem. */
  lowStockRemaining?: number | null;
  /** Most pieces the cart accepts for this variant; null = uncapped or hidden stock. SDK 2.13.0. */
  maxPurchasableQuantity?: number | null;
  /** Variant's own spec table: own value where it has one, parent's elsewhere. */
  parameterGroups: ProductParameterGroup[];
}

ProductParameterGroup

One block of the merchant's curated spec table. Groups and parameters arrive in display order, there is nothing to sort by.

interface ProductParameterGroup {
  /** Derived from the merchant's own group name, stable, usable in URLs. */
  slug: string;
  /** Localized group heading, render as-is. */
  name: string;
  parameters: ProductParameter[];
}

ProductParameter

interface ProductParameter {
  /** Merchant-written label in the requested language. */
  label: string;
  /** Preformatted value. null ONLY for boolean parameters. */
  value: string | null;
  /** Non-null ONLY for boolean parameters, and then `value` is null. */
  booleanValue: boolean | null;
  /** Display unit: "cm", "g", a currency code, "%". null when the parameter has none. */
  unit: string | null;
}
/** Response of catalog.getProductParameters(slug, {locale}). */
interface ProductParametersResponse {
  groups: ProductParameterGroup[];
}

Parameters without a value are omitted server-side, so the array never contains an empty row. Warehouse data groups are not part of the public surface: no data-group id, no field key and no warehouse field type ever appears in a payload.

ProductLabel

interface ProductLabel {
  id: string;
  slug: string;
  name: string;
  color?: string;
}

ProductsQuery

Query parameters for listing products.

interface ProductsQuery {
  page?: number;
  limit?: number;
  /** Single category slug (OR logic with categories array) */
  category?: string;
  /** Multiple category slugs (OR logic) */
  categories?: string[];
  /** Single label slug */
  label?: string;
  priceMin?: number;
  priceMax?: number;
  currency?: string;
  locale?: string;
  sort?: ProductSortValue;
  inStock?: boolean;
  search?: string;
  /**
   * Parameter filters, keyed by parameter slug (FilterField.key / Facet.key).
   * Array = multi-select, OR within the key. Ranges use `<slug>_min` / `<slug>_max`.
   * Only parameters the merchant marked filterable are accepted.
   * e.g. { material: 'bavlna', barva: ['cerna', 'bila'], hmotnost_min: 100 }
   */
  parameters?: Record<string, unknown>;
  /**
   * Slug-based facet selection for SEO URLs: parameter slug -> value slugs,
   * e.g. { barva: ['cerna'] }. Resolved server-side to the underlying values.
   */
  facets?: Record<string, string[]>;
  /** Filter by specific product IDs */
  ids?: string[];
  /** Filter by specific product slugs */
  slugs?: string[];
  /** Multiple labels (AND logic, product must have ALL) */
  labels?: string[];
  /** Exclude specific product IDs */
  excludeIds?: string[];
  /** Exclude products in specific categories */
  excludeCategories?: string[];
  /** Only products with compareAtPrice (on sale) */
  hasDiscount?: boolean;
  /** Only featured products */
  isFeatured?: boolean;
  /** Products created after timestamp (epoch ms) */
  createdAfter?: number;
}

ProductSort

Available sort values (const object for runtime + autocomplete):

const ProductSort = {
  PRICE_ASC: "price_asc",
  PRICE_DESC: "price_desc",
  NAME_ASC: "name_asc",
  NAME_DESC: "name_desc",
  NEWEST: "newest",
  FEATURED: "featured",
} as const;

type ProductSortValue = (typeof ProductSort)[keyof typeof ProductSort];

FilterField

One filterable parameter, as returned by getFilters({locale}).

interface FilterField {
  /** Parameter slug. The key you send back in ProductsQuery.parameters. */
  key: string;
  /** Localized parameter label. */
  name: string;
  /** How to render the control, not how the value is stored. */
  type: "enum" | "range" | "boolean";
  /** Parameter group the field belongs to (same grouping as the spec table). */
  groupSlug: string;
  /** Localized group name. */
  groupName: string;
  /** Display unit next to the input: "cm", "g", "%", a currency code. */
  unit?: string | null;
  /** enum only: the selectable values, localized. */
  values?: string[];
}

Only parameters the merchant explicitly marked filterable are ever returned, so a visitor cannot filter by a field they cannot see on the product.


Categories

Category

interface Category {
  id: string;
  slug: string;
  name: string;
  description?: string;
  imageUrl?: string;
  children: Category[];
  productCount?: number;
}

CategoryDetail

The category page. It carries SEO fields and no children: read subcategories from catalog.getCategories().

interface CategoryDetail extends Omit<Category, "children"> {
  /** @deprecated Never sent on the detail. */
  children?: Category[];
  /** Null when the category has no text in the requested language. */
  seo?: {
    title?: string | null;
    description?: string | null;
    /** Always null for categories. */
    keywords?: string | null;
    ogTitle?: string | null;
    ogDescription?: string | null;
    ogImage?: string | null;
  } | null;
}

Cart

Cart

interface Cart {
  id: string;
  sessionToken?: string;
  items: CartItem[];
  /** Lines at catalog prices (VAT in or out, per the shop's price mode). */
  subtotal: number;
  discountTotal: number;
  discount?: CartDiscount;
  /** The payable amount for the goods, VAT included, after discounts. */
  grandTotal: number;
  /** Goods VAT before the order discount (for the goods subtotal). */
  taxTotal: number;
  /** VAT contained in grandTotal, after the discount, as on the invoice. SDK 2.12.0. */
  vatTotal?: number;
  /** VAT of the payable amount by rate, sums to vatTotal. */
  taxBreakdown: TaxBreakdownLine[];
  currency: string;
  itemCount: number;
}

grandTotal is what the customer pays for the cart lines, VAT included. Never add taxTotal to it, that double counts the VAT. Shipping and the payment-method fee are chosen at checkout and appear only in order.grandTotal. The same holds in both price modes: with INCL_VAT the VAT is extracted out of the catalog price, with EXCL_VAT it is added on top, and grandTotal already reflects whichever applies.

Discounts are always taken from the VAT-inclusive amount, in both price modes and for every country, and they lower the base and the VAT. Render the "VAT 21 %: X" rows from taxBreakdown (it sums to vatTotal, the VAT the customer actually pays, identical to the invoice). taxTotal is the goods VAT before the order discount and only belongs in a goods subtotal.

CartItem

interface CartItem {
  id: string;
  product: CartItemProduct;
  quantity: number;
  unitPrice: number;
  totalPrice: number;
  priceChanged: boolean;
  volumePriceApplied: boolean;
}

CartItemProduct

interface CartItemProduct {
  slug: string;
  name: string;
  sku: string;
  imageUrl?: string;
  inStock: boolean;
  currentPrice: number;
  /** "Only X left" on the cart line, same contract as ProductListItem. SDK 2.13.0. */
  lowStockRemaining?: number | null;
}

CartDiscount

interface CartDiscount {
  code: string;
  type: string;
  value: number;
}

AddToCartInput

interface AddToCartInput {
  /** Product ID (eshop product ID or warehouse item ID) */
  productId: string;
  quantity: number;
}

Checkout

CheckoutInput

interface CheckoutInput {
  shippingAddress: CheckoutAddress;
  billingAddress: CheckoutAddress;
  email: string;
  phone?: string;
  customerNote?: string;
}

CheckoutAddress

interface CheckoutAddress {
  firstName: string;
  lastName: string;
  company?: string;
  street: string;
  city: string;
  zip: string;
  country: string;
  phone?: string;
}

OrderDetail

Full order detail returned after checkout, extends OrderListItem.

interface OrderDetail extends OrderListItem {
  items: OrderItem[];
  shippingAddress: CheckoutAddress;
  billingAddress: CheckoutAddress;
  email: string;
  phone?: string | null;
  customerNote?: string | null;
  subtotal: number;
  /** Goods VAT before order discounts. */
  taxTotal: number;
  /** VAT contained in grandTotal (after discounts, incl. shipping), as on the invoice. SDK 2.12.0. */
  vatTotal?: number;
  /** VAT of the amount paid by rate, sums to vatTotal. */
  taxBreakdown: TaxBreakdownLine[];
  shippingTotal: number;
  discountTotal: number;
  fulfillmentStatus: FulfillmentStatus;
  statusHistory: OrderStatusHistory[];
  /** Discount codes applied to the order. SDK 2.14.0. */
  discounts?: OrderDiscount[];
  /** @deprecated Not sent on the detail; `checkout.createOrder()` returns it on `CheckoutResult`. */
  trackingToken?: string;
  /** Bank transfer details while unpaid, null otherwise. SDK 2.13.0. */
  paymentInstructions?: OrderPaymentInstructions | null;
}

interface OrderPaymentInstructions {
  accountNumber: string | null;
  iban: string | null;
  bic: string | null;
  bankName: string | null;
  /** Digits only, at most 10. */
  variableSymbol: string | null;
  amount: number;
  currency: string;
  instructions: string | null;
}

Orders

OrderListItem

interface OrderListItem {
  id: string;
  orderNumber: string;
  status: OrderStatus;
  paymentStatus: PaymentStatus;
  grandTotal: number;
  currency: string;
  itemCount: number;
  createdAt: number;
}

OrderItem

interface OrderItem {
  /** Order line id. SDK 2.14.0. */
  id?: string;
  productName: string;
  sku: string;
  imageUrl?: string | null;
  /** Product still in the shop, for a link back (SDK 2.8.0). Null = removed. */
  productId?: string | null;
  productSlug?: string | null;
  quantity: number;
  unitPrice: number;
  /** VAT rate in percent, e.g. 21. SDK 2.14.0. */
  taxRate?: number;
  /** VAT of the whole line. SDK 2.14.0. */
  taxAmount?: number;
  totalPrice: number;
  totalPriceWithTax: number;
}

OrderDiscount

interface OrderDiscount {
  code: string;
  /** PERCENTAGE, FIXED_AMOUNT or FREE_SHIPPING. */
  type: string;
  value: number;
  /** Money the code took off this order. */
  appliedAmount: number;
}

OrderStatusHistory

interface OrderStatusHistory {
  /** Null for the first entry. */
  fromStatus?: OrderStatus | null;
  toStatus: OrderStatus;
  note?: string | null;
  /** @deprecated Never sent: who changed the status stays inside the shop. */
  changedBy?: string;
  createdAt: number;
}

Status enums

const OrderStatuses = {
  PENDING: "PENDING",
  CONFIRMED: "CONFIRMED",
  PROCESSING: "PROCESSING",
  SHIPPED: "SHIPPED",
  DELIVERED: "DELIVERED",
  CANCELLED: "CANCELLED",
  REFUNDED: "REFUNDED",
} as const;
type OrderStatus = (typeof OrderStatuses)[keyof typeof OrderStatuses];

const PaymentStatuses = {
  UNPAID: "UNPAID",
  PAID: "PAID",
  PARTIALLY_REFUNDED: "PARTIALLY_REFUNDED",
  REFUNDED: "REFUNDED",
} as const;
type PaymentStatus = (typeof PaymentStatuses)[keyof typeof PaymentStatuses];

const FulfillmentStatuses = {
  UNFULFILLED: "UNFULFILLED",
  PARTIALLY_FULFILLED: "PARTIALLY_FULFILLED",
  FULFILLED: "FULFILLED",
} as const;
type FulfillmentStatus = (typeof FulfillmentStatuses)[keyof typeof FulfillmentStatuses];

Auth

AuthTokens

interface AuthTokens {
  accessToken: string;
  refreshToken: string;
}

RegisterInput

interface RegisterInput {
  email: string;
  password: string;
  firstName?: string;
  lastName?: string;
}

LoginInput

interface LoginInput {
  email: string;
  password: string;
}

Customer

CustomerProfile

interface CustomerProfile {
  id: string;
  email: string;
  firstName?: string | null;
  lastName?: string | null;
  phone?: string | null;
  emailVerified: boolean;
  isApproved: boolean;
  /** Registration time (Unix ms). SDK 2.14.0. */
  createdAt?: number;
}

updateProfile accepts null for firstName, lastName and phone and clears the value; a missing key leaves it unchanged.

CustomerAddress

interface CustomerAddress {
  id: string;
  type: AddressType;
  isDefault: boolean;
  firstName: string;
  lastName: string;
  company?: string | null;
  street: string;
  city: string;
  zip: string;
  country: string;
  state?: string | null;
  companyId?: string | null;
  vatId?: string | null;
  /** VIES result at save time, read-only. */
  vatIdValid?: boolean | null;
  vatIdCheckedAt?: number | null;
  phone?: string | null;
}

createAddress and updateAddress accept null for company, phone, state, companyId and vatId, so an address read from the API can be sent back as it is. On updateAddress, null clears the value and a missing key leaves it unchanged (SDK 2.14.0; older backends answered 400).

AddressType

const AddressTypes = {
  SHIPPING: "SHIPPING",
  BILLING: "BILLING",
} as const;
type AddressType = (typeof AddressTypes)[keyof typeof AddressTypes];

Bundles

Bundle

interface Bundle {
  id: string;
  slug: string;
  name: string;
  description: string | null;
  bundlePrice: number;
  currency: string;
  coverImage: string | null;
  endsAt: number | null;
  itemsSum: number;
  savings: number;
  savingsPercent: number;
  items: BundleItem[];
}

BundleItem

interface BundleItem {
  productId: string;
  slug: string | null;
  name: string;
  sku: string;
  quantity: number;
  imageUrl: string | null;
  defaultPrice: number | null;
}

Cross-sell

CrossSellItem

interface CrossSellItem {
  productId: string;
  slug: string | null;
  name: string; // localized
  sku: string;
  price: number | null; // in `currency`, incl. customer price-list override
  compareAtPrice: number | null; // original price when on sale
  currency: string;
  imageUrl: string | null;
  stockCached: number;
  inStock: boolean;
}

Promotions

ActivePromotion

interface ActivePromotion {
  id: string;
  name: string;
  slug: string;
  type: string;
  discountType: string;
  discountValue: number;
  startsAt: number;
  endsAt: number | null;
  badgeText: string | null;
  badgeColor: string | null;
  showCountdown: boolean;
  couponRequired: boolean;
}

Gift Cards

GiftCardBalance

interface GiftCardBalance {
  valid: boolean;
  balance: number;
  currency: string;
}

Wishlist

WishlistItem

interface WishlistItem {
  id: string;
  productId: string;
  productName: string;
  productSku: string;
  productSlug: string | null;
  imageUrl: string | null;
  price: number | null;
  stockCached: number;
  createdAt: number;
}

Reviews

ProductReviewsResponse

interface ProductReviewsResponse {
  items: ProductReview[];
  averageRating: number;
  reviewCount: number;
  page: number;
  totalPages: number;
}

ProductReview

interface ProductReview {
  id: string;
  authorName: string;
  rating: number;
  title: string | null;
  content: string | null;
  imageUrls: string[];
  isVerifiedPurchase: boolean;
  helpfulCount: number;
  unhelpfulCount: number;
  replyContent: string | null;
  createdAt: number;
}

SubmitReviewInput

interface SubmitReviewInput {
  productId: string;
  rating: number;
  title?: string;
  content?: string;
  authorName: string;
  authorEmail?: string;
  imageUrls?: string[];
}

Returns

ReturnRequest

interface ReturnRequest {
  id: string;
  orderId: string;
  status: string;
  reason: string;
  customerNote: string | null;
  refundAmount: number | null;
  refundMethod: string | null;
  createdAt: number;
}

SubmitReturnInput

interface SubmitReturnInput {
  orderId: string;
  reason: string;
  customerNote?: string;
  items: {
    orderItemId: string;
    productName: string;
    quantity: number;
    reason?: string;
    imageUrls?: string[];
  }[];
}

CookieConsent

interface CookieConsent {
  necessary: boolean;
  analytics: boolean;
  marketing: boolean;
  preferences: boolean;
  consentedAt: number;
}

CookieConsentInput

interface CookieConsentInput {
  visitorId: string;
  analytics: boolean;
  marketing: boolean;
  preferences: boolean;
}

Quotes (B2B)

QuoteRequest

interface QuoteRequest {
  id: string;
  status: string;
  contactName: string;
  contactEmail: string;
  companyName: string | null;
  quotedTotal: number | null;
  createdAt: number;
}

SubmitQuoteInput

interface SubmitQuoteInput {
  contactName: string;
  contactEmail: string;
  contactPhone?: string;
  companyName?: string;
  companyIco?: string;
  message?: string;
  items: {
    productId: string;
    quantity: number;
    requestedPrice?: number;
  }[];
}

Pages

Page

interface Page {
  slug: string;
  title: string;
  isActive: boolean;
  /** Last change (epoch ms), sitemap lastmod */
  updatedAt?: number;
}

PageDetail

interface PageDetail {
  slug: string;
  title: string;
  /** Ready, sanitised HTML of the body (SDK 2.20.0). Render it with <RichText html={...} /> */
  contentHtml: string;
  /** @deprecated Read contentHtml. The body as a string: sanitised HTML, or markdown on older pages */
  content: string;
  seoTitle?: string;
  seoDescription?: string;
  ogTitle?: string;
  ogDescription?: string;
  ogImage?: string;
  /** Downloadable files (e.g. legal form PDFs), render as a download list */
  attachments: PageAttachment[];
}

interface PageAttachment {
  name: string;
  description: string | null;
  url: string;
  mimeType: string;
  fileSize: number;
}

Blog

interface Blog {
  /** URL segment: /blog/{handle} */
  handle: string;
  name: string;
  description: string | null;
  postCount: number;
  settings: {
    postsPerPage?: number;
    showAuthor?: boolean;
    showReadingTime?: boolean;
    showTags?: boolean;
    layout?: "grid" | "list" | "magazine";
  };
  /** Latest publish time (epoch ms), for sitemaps */
  updatedAt: number | null;
}

interface BlogTag {
  slug: string;
  name: string;
}

interface BlogPostListItem {
  slug: string;
  title: string;
  excerpt: string | null;
  coverUrl: string | null;
  publishedAt: number | null;
  readingTimeMin: number | null;
  authorName: string | null;
  isFeatured: boolean;
  tags: BlogTag[];
}

interface BlogPostsPage {
  items: BlogPostListItem[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
  /** Tags of the blog (for a filter) */
  tags: BlogTag[];
}

interface BlogPostDetail {
  slug: string;
  blogHandle: string;
  blogName: string;
  title: string;
  excerpt: string | null;
  /** {format: "html", html} sanitised by the API, render with RichText */
  content: {format?: string; html?: string};
  coverUrl: string | null;
  publishedAt: number | null;
  updatedAt: number | null;
  readingTimeMin: number | null;
  authorName: string | null;
  tags: BlogTag[];
  seoTitle: string | null;
  seoDescription: string | null;
  ogImage: string | null;
  /** Other posts of the same blog */
  related: BlogPostListItem[];
}

Forms

type SiteFormFieldType =
  | "text" | "email" | "phone" | "textarea" | "number"
  | "select" | "radio" | "checkbox" | "multiselect"
  | "date" | "time" | "datetime" | "url" | "hidden";

interface SiteFormField {
  /** Key in the submitted `data` object */
  key: string;
  type: SiteFormFieldType;
  label: string;
  placeholder?: string;
  help?: string;
  required: boolean;
  /** select / radio / multiselect */
  options?: {value: string; label: string}[];
  /** Number: min/max value. Text: min/max length */
  min?: number;
  max?: number;
  /** Regular expression for text fields (without slashes) */
  pattern?: string;
  width?: "full" | "half";
  /** Hidden field value, or a prefill */
  defaultValue?: string;
}

interface StorefrontForm {
  slug: string;
  name: string;
  description: string | null;
  fields: SiteFormField[];
  settings: {
    submitLabel?: string;
    successMessage?: string;
    redirectUrl?: string;
    consentText?: string;
    requireConsent?: boolean;
  };
}

interface StorefrontFormSubmitInput {
  data: Record<string, unknown>;
  locale?: string;
  page?: string;
  consent?: boolean;
  /** Honeypot, pass the hidden input's value through */
  website?: string;
}

interface StorefrontFormSubmitResult {
  ok: boolean;
  id: string | null;
  message: string | null;
  redirectUrl: string | null;
}

interface StorefrontFormFieldError {
  key: string;
  code: "required" | "invalid" | "tooShort" | "tooLong" | "min" | "max" | "notOption" | "pattern";
}

Shop

ShopInfo

interface ShopInfo {
  id: string;
  name: string;
  domain: string;
  logo?: string;
  favicon?: string;
  isActive: boolean;
  defaultLanguage: string;
  supportedLanguages: string[];
  defaultCurrency: string;
  supportedCurrencies: string[];
  metaTitle?: string;
  metaDescription?: string;
  allowGuestCheckout: boolean;
  /** B2B-only shop: guests must sign in to see prices and purchase. */
  b2bMode: boolean;
  /** Whether quote requests make sense for this shop (currently follows B2B mode). */
  quotesEnabled: boolean;
  /**
   * Countries the shop delivers to (ISO 3166-1 alpha-2), derived from the enabled
   * shipping methods. `null` = no restriction, `[]` = no enabled shipping method.
   * Limit the checkout country select to this list. (SDK 1.15.0)
   */
  shippingCountries?: string[] | null;
}

ShopSeo

interface ShopSeo {
  locale: string;
  title: string | null;
  description: string | null;
  keywords: string | null;
  ogTitle: string | null;
  ogDescription: string | null;
  ogImage: string | null;
}

Errors

BehioApiError

Thrown for HTTP error responses from the API.

class BehioApiError extends Error {
  readonly code: BehioErrorCode;
  readonly status: number;
  readonly body: unknown;
  readonly isRetryable: boolean;

  constructor(status: number, body: unknown, message?: string);

  /** Check if this is a specific error type */
  is(code: BehioErrorCode): boolean;
}

BehioNetworkError

Thrown for network failures and timeouts.

class BehioNetworkError extends Error {
  readonly code: "NETWORK_ERROR" | "TIMEOUT";
  readonly isRetryable: true;

  constructor(message: string, isTimeout?: boolean);
}

BehioErrorCode

type BehioErrorCode =
  | "UNAUTHORIZED"
  | "FORBIDDEN"
  | "NOT_FOUND"
  | "VALIDATION_ERROR"
  | "CONFLICT"
  | "RATE_LIMITED"
  | "CART_EMPTY"
  | "PRODUCT_NOT_FOUND"
  | "INVALID_CREDENTIALS"
  | "INVALID_DISCOUNT"
  | "DISCOUNT_EXPIRED"
  | "TOKEN_EXPIRED"
  | "TOKEN_INVALID"
  | "EMAIL_ALREADY_EXISTS"
  | "ORDER_NOT_CANCELLABLE"
  | "INTERNAL_ERROR"
  | "NETWORK_ERROR"
  | "TIMEOUT"
  | "UNKNOWN";

Events

BehioEventType

type BehioEventType =
  | "auth:login"
  | "auth:logout"
  | "auth:token-refresh"
  | "auth:token-refresh-failed"
  | "cart:updated"
  | "cart:cleared"
  | "order:created"
  | "error"
  | "request"
  | "response"
  | "rate-limit-warning";

BehioEventHandler

type BehioEventHandler = (data?: unknown) => void;

Interceptors

RequestInterceptor

interface RequestInterceptorConfig {
  url: string;
  method: string;
  headers: Record<string, string>;
  body?: string;
}

type RequestInterceptor = (
  config: RequestInterceptorConfig,
) => RequestInterceptorConfig | Promise<RequestInterceptorConfig>;

ResponseInterceptor

interface ResponseInterceptorData {
  status: number;
  data: unknown;
  headers: Headers;
}

type ResponseInterceptor = (
  response: ResponseInterceptorData,
) => void | Promise<void>;

Pagination

PaginatedResponse

Generic wrapper for paginated list endpoints.

interface PaginatedResponse<T> {
  items: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

MessageResponse

interface MessageResponse {
  message: string;
}

SDK 1.20 additions

The SDK exports CheckoutPreview and CheckoutResult; checkout creation returns the latter and includes the permanent trackingToken (SDK 2.7.0) plus an optional short-lived orderAccessToken for a guest's private receipt. Do not put that token in URLs or browser-visible props. CheckoutInput.previewToken binds the displayed review to its current cart and choices. CheckoutInput.shippingAddress is optional only when all cart products are configured without physical delivery.

Cart.requiresShipping includes bundle contents, and Cart.checkoutLimits contains minimum/maximum amounts in cart currency. OrderDetail includes requiresShipping and nullable historical financial fields paymentFee, roundingAdjustment, loyaltyDiscount and giftCardDeducted. Null means an older order did not snapshot that component; do not replace it with current merchant settings.

ShopInfo includes optional appearance, maintenance, commerce, timezone, account and currencyDisplayFormat. ShopInfo.checkout includes checkoutLayout, showCartPriceBreakdown, showEstimatedDelivery and orderConfirmationText. See merchant settings for their behavior.

DigitalDownload.isRevoked optionally reports an inactive asset or an order that no longer grants access. Disable the download action for revoked, expired or exhausted grants. The server rechecks these conditions atomically.

On this page