# SparkLayer Docs: Developers > Everything needed to build on SparkLayer: the facts for calling the APIs, an index of every API operation, and every developer and Ignite guide in full (the API, JavaScript SDK, templates, data and Ignite integrations). Source: https://docs.sparklayer.io/developers and https://docs.sparklayer.io/ignite. Each API operation's full reference (parameters, body, responses) is linked below; https://docs.sparklayer.io/llms-api.txt lists them with their parameters, and https://docs.sparklayer.io/llms.txt lists every page of the docs. Ready-made prompts for common jobs (shortened here to what to read and the steps) are at https://docs.sparklayer.io/developers/build-with-ai.md. ## Facts for building integrations These apply to every call to the SparkLayer APIs. Use only what the docs and OpenAPI specs define. - **Base URLs:** Live `https://app.sparklayer.io`, test `https://test.app.sparklayer.io`. Each environment has its own data and its own API keys. - **Access:** API access needs the Pro or Enterprise plan. Create credentials (Site ID, Client ID, Client Secret) in the SparkLayer Dashboard at Settings > API. - **Access token:** `POST {base}/api/auth/token` with a `Site-Id` header and a JSON body (not form-encoded): `{"grant_type":"client_credentials","client_id":"…","client_secret":"…"}`. It returns `access_token`, valid for 3,600 seconds. There is no refresh token: cache the token and request a new one shortly before it expires. - **Every request:** `Authorization: Bearer ` and `Site-Id: `, plus `Content-Type: application/json` when there is a body. Set a `User-Agent` that names your integration. - **Errors:** RFC 7807 problem details: `title`, `status`, `detail` and an optional `errors[]` of `{ code, message, property }`. Some error responses have no body. Retry `5xx`, `429` (honouring `Retry-After`) and concurrent-update `409`s with exponential backoff; on `401`, get a new token once and retry; don't retry other `4xx` without changing the request. - **Pagination:** Most list endpoints return everything. `GET /api/v2/price-lists` pages with `page` and `page_size` (up to 250) until `pagination.current_page` equals `total_pages`. `GET /api/v1/purchases` uses `limit` (up to 500) and `offset`: stop when a page has fewer than `limit` results. - **Ground rules:** Use only endpoints, fields and SDK methods that the docs or OpenAPI specs define. Prefer `GET /api/v2/price-lists` over the deprecated v1. Products are matched by SKU. Keep credentials in environment variables or a secrets manager, never in code. - **Ignite:** With Ignite, SparkLayer calls endpoints that your service implements (the Ignite spec), and your service calls the SparkLayer APIs above for everything else. - **Read the docs as Markdown:** Add `.md` to any page URL. Index of every page: https://docs.sparklayer.io/llms.txt. Every API operation, compactly: https://docs.sparklayer.io/llms-api.txt. The developer guides in full: https://docs.sparklayer.io/developers/llms.txt. - **OpenAPI specs:** `core`, `ordering`, `pricing`, `purchasing`, `stock`, `files`, `sync-log`: https://docs.sparklayer.io/openapi/.yaml (also `.json`). Ignite: https://docs.sparklayer.io/openapi/ignite.yaml. - **MCP:** Search and read these docs from your assistant with the docs MCP server at https://docs.sparklayer.io/mcp. OpenAPI specs (index: https://docs.sparklayer.io/openapi/index.json; each is also published as `.json`): - Core API: https://docs.sparklayer.io/openapi/core.yaml - Ordering API: https://docs.sparklayer.io/openapi/ordering.yaml - Pricing API: https://docs.sparklayer.io/openapi/pricing.yaml - Purchasing API: https://docs.sparklayer.io/openapi/purchasing.yaml - Stock API: https://docs.sparklayer.io/openapi/stock.yaml - Files API: https://docs.sparklayer.io/openapi/files.yaml - Sync Logging API: https://docs.sparklayer.io/openapi/sync-log.yaml - Ignite endpoints: https://docs.sparklayer.io/openapi/ignite.yaml ## API operations ### Core API Overview: https://docs.sparklayer.io/developers/api/core.md · OpenAPI spec: https://docs.sparklayer.io/openapi/core.yaml - `POST /api/auth/token` [Get an access token](https://docs.sparklayer.io/developers/api/core/get-an-access-token.md) - `DELETE /api/v1/customers/{lookupBy}/{id}` [Delete a customer](https://docs.sparklayer.io/developers/api/core/delete-a-customer.md) - `POST /api/v1/fetch-customer-pricing` [Get customer-specific pricing](https://docs.sparklayer.io/developers/api/core/get-customer-specific-pricing.md) - `GET /api/v1/customer-groups` [List customer groups](https://docs.sparklayer.io/developers/api/core/list-customer-groups.md) - `POST /api/v1/customer-groups` [Create a customer group](https://docs.sparklayer.io/developers/api/core/new-customer-group.md) - `DELETE /api/v1/customer-groups/{slug}` [Delete a customer group](https://docs.sparklayer.io/developers/api/core/delete-customer-group.md) - `PATCH /api/v1/customer-groups/{slug}` [Update a customer group](https://docs.sparklayer.io/developers/api/core/update-customer-group.md) - `GET /api/v1/discounts` [List discounts](https://docs.sparklayer.io/developers/api/core/list-of-discounts.md) - `POST /api/v1/discounts` [Create a discount](https://docs.sparklayer.io/developers/api/core/create-a-discount.md) - `PATCH /api/v1/set-discount-priorities` [Update discount priorities](https://docs.sparklayer.io/developers/api/core/update-discount-priorities.md) - `GET /api/v1/discounts/{id}` [Get a discount](https://docs.sparklayer.io/developers/api/core/get-a-discount.md) - `DELETE /api/v1/discounts/{id}` [Delete a discount](https://docs.sparklayer.io/developers/api/core/delete-a-discount.md) - `PATCH /api/v1/discounts/{id}` [Update a discount](https://docs.sparklayer.io/developers/api/core/update-a-discount.md) - `GET /api/v1/shipping-methods` [List shipping methods](https://docs.sparklayer.io/developers/api/core/list-of-shipping-methods.md) - `POST /api/v1/shipping-methods` [Create a shipping method](https://docs.sparklayer.io/developers/api/core/create-a-shipping-method.md) - `GET /api/v1/shipping-methods/{id}` [Get a shipping method](https://docs.sparklayer.io/developers/api/core/get-a-shipping-method.md) - `DELETE /api/v1/shipping-methods/{id}` [Delete a shipping method](https://docs.sparklayer.io/developers/api/core/delete-a-shipping-method.md) - `PATCH /api/v1/shipping-methods/{id}` [Update a shipping method](https://docs.sparklayer.io/developers/api/core/update-a-shipping-method.md) - `GET /api/v1/settings` [List global settings](https://docs.sparklayer.io/developers/api/core/list-global-settings.md) - `PATCH /api/v1/settings` [Update global settings](https://docs.sparklayer.io/developers/api/core/update-global-setting.md) - `GET /api/v1/settings/{slug}` [List customer group settings](https://docs.sparklayer.io/developers/api/core/list-customer-group-settings.md) - `PATCH /api/v1/settings/{slug}` [Update customer group settings](https://docs.sparklayer.io/developers/api/core/update-customer-group-setting.md) ### Ordering API Overview: https://docs.sparklayer.io/developers/api/ordering.md · OpenAPI spec: https://docs.sparklayer.io/openapi/ordering.yaml - `POST /api/v1/carts` [Create a cart](https://docs.sparklayer.io/developers/api/ordering/create-cart.md) - `GET /api/v1/carts/{id}` [Get a cart](https://docs.sparklayer.io/developers/api/ordering/get-cart.md) - `DELETE /api/v1/carts/{id}` [Delete a cart](https://docs.sparklayer.io/developers/api/ordering/delete-cart.md) - `PATCH /api/v1/carts/{id}` [Update a cart](https://docs.sparklayer.io/developers/api/ordering/update-cart.md) - `POST /api/v1/carts/{id}/complete` [Complete a cart](https://docs.sparklayer.io/developers/api/ordering/complete-cart.md) - `POST /api/v1/carts/{id}/calculate` [Calculate a cart](https://docs.sparklayer.io/developers/api/ordering/calculate-cart.md) ### Pricing API Overview: https://docs.sparklayer.io/developers/api/pricing.md · OpenAPI spec: https://docs.sparklayer.io/openapi/pricing.yaml - `GET /api/v1/price-lists` [List price lists](https://docs.sparklayer.io/developers/api/pricing/get-price-lists.md) - `POST /api/v1/price-lists` [Create a price list](https://docs.sparklayer.io/developers/api/pricing/create-a-price-list.md) - `GET /api/v1/price-lists/{slug}` [Get a price list](https://docs.sparklayer.io/developers/api/pricing/get-price-list.md) - `DELETE /api/v1/price-lists/{slug}` [Delete a price list](https://docs.sparklayer.io/developers/api/pricing/delete-a-price-list.md) - `PATCH /api/v1/price-lists/{slug}` [Update a price list](https://docs.sparklayer.io/developers/api/pricing/update-a-price-list.md) - `GET /api/v2/price-lists` [List price lists (v2)](https://docs.sparklayer.io/developers/api/pricing/get-price-lists-v2.md) - `GET /api/v1/price-lists/{slug}/pricing` [List prices in a price list](https://docs.sparklayer.io/developers/api/pricing/get-pricing-by-price-list.md) - `PATCH /api/v1/price-lists/{slug}/pricing` [Update prices in a price list](https://docs.sparklayer.io/developers/api/pricing/update-pricing-by-price-list.md) - `GET /api/v1/pricing/{sku}` [Get prices for a SKU](https://docs.sparklayer.io/developers/api/pricing/get-pricing-by-sku.md) - `PATCH /api/v1/pricing/{sku}` [Update prices for a SKU](https://docs.sparklayer.io/developers/api/pricing/update-pricing-by-sku.md) - `PATCH /api/v1/batch-update-pricing` [Update prices across price lists](https://docs.sparklayer.io/developers/api/pricing/update-pricing-for-multiple-price-lists.md) - `POST /api/v1/calculate-pricing` [Calculate prices for SKUs](https://docs.sparklayer.io/developers/api/pricing/calculate-pricing.md) - `GET /api/v2/price-lists-num-manual-prices` [List price lists with manual price counts](https://docs.sparklayer.io/developers/api/pricing/get-price-lists-with-manual-price-counts.md) ### Purchasing API Overview: https://docs.sparklayer.io/developers/api/purchasing.md · OpenAPI spec: https://docs.sparklayer.io/openapi/purchasing.yaml - `GET /api/v1/purchases/{lookupBy}/{identifier}` [Get a purchase](https://docs.sparklayer.io/developers/api/purchasing/get-purchase.md) - `PUT /api/v1/purchases/{lookupBy}/{identifier}` [Create or update a purchase](https://docs.sparklayer.io/developers/api/purchasing/create-update-purchase.md) - `DELETE /api/v1/purchases/{lookupBy}/{identifier}` [Delete a purchase](https://docs.sparklayer.io/developers/api/purchasing/delete-purchase.md) - `GET /api/v1/purchases` [List purchases](https://docs.sparklayer.io/developers/api/purchasing/get-purchases.md) - `GET /api/v1/purchases/{lookupBy}/{identifier}/history` [List purchase history](https://docs.sparklayer.io/developers/api/purchasing/get-purchase-history.md) - `POST /api/v1/purchases/{lookupBy}/{identifier}/history` [Create a purchase history entry](https://docs.sparklayer.io/developers/api/purchasing/create-a-purchase-history-entry.md) - `GET /api/v1/purchases/{lookupBy}/{identifier}/transactions` [List purchase transactions](https://docs.sparklayer.io/developers/api/purchasing/get-purchase-transactions.md) - `PUT /api/v1/purchases/{lookupBy}/{identifier}/transactions` [Create or update a purchase transaction](https://docs.sparklayer.io/developers/api/purchasing/create-or-update-a-purchase-transaction.md) - `GET /api/v1/store-currency` [Get the store currency](https://docs.sparklayer.io/developers/api/purchasing/get-store-currency.md) - `PUT /api/v1/store-currency` [Update the store currency](https://docs.sparklayer.io/developers/api/purchasing/update-store-currency.md) - `GET /api/v1/purchases/stats/order/sku` [Get a customer's SKU order stats](https://docs.sparklayer.io/developers/api/purchasing/get-order-stats-for-a-customer.md) ### Stock API Overview: https://docs.sparklayer.io/developers/api/stock.md · OpenAPI spec: https://docs.sparklayer.io/openapi/stock.yaml - `POST /api/v1/batch-fetch-stock` [Get stock levels for multiple SKUs](https://docs.sparklayer.io/developers/api/stock/get-stock-levels.md) - `POST /api/v1/batch-update-stock` [Update stock levels for multiple SKUs](https://docs.sparklayer.io/developers/api/stock/update-stock-levels.md) - `POST /api/v1/batch-delete-stock` [Delete stock levels for multiple SKUs](https://docs.sparklayer.io/developers/api/stock/delete-stock-levels.md) - `GET /api/v1/stock-locations` [List stock locations](https://docs.sparklayer.io/developers/api/stock/list-stock-locations.md) - `POST /api/v1/stock-locations` [Create a stock location](https://docs.sparklayer.io/developers/api/stock/create-a-stock-location.md) - `GET /api/v1/stock-locations/{StockLocationID}` [Get a stock location](https://docs.sparklayer.io/developers/api/stock/get-a-stock-location.md) - `DELETE /api/v1/stock-locations/{StockLocationID}` [Delete a stock location](https://docs.sparklayer.io/developers/api/stock/delete-a-stock-location.md) - `PATCH /api/v1/stock-locations/{StockLocationID}` [Update a stock location](https://docs.sparklayer.io/developers/api/stock/update-a-stock-location.md) ### Files API Overview: https://docs.sparklayer.io/developers/api/files.md · OpenAPI spec: https://docs.sparklayer.io/openapi/files.yaml - `GET /api/v1/files` [Get multiple files](https://docs.sparklayer.io/developers/api/files/get-bulk-files.md) - `POST /api/v1/files` [Create a file](https://docs.sparklayer.io/developers/api/files/create-a-new-file.md) - `DELETE /api/v1/files` [Delete all files](https://docs.sparklayer.io/developers/api/files/delete-all-files.md) - `POST /api/v1/search-files` [Search files by metadata](https://docs.sparklayer.io/developers/api/files/query-files-by-metadata.md) - `GET /api/v1/files/{id}` [Get a file](https://docs.sparklayer.io/developers/api/files/get-a-file.md) - `DELETE /api/v1/files/{id}` [Delete a file](https://docs.sparklayer.io/developers/api/files/delete-a-file.md) - `PATCH /api/v1/files/{id}` [Update a file](https://docs.sparklayer.io/developers/api/files/update-a-file.md) - `POST /api/v1/batch-delete-files` [Delete multiple files](https://docs.sparklayer.io/developers/api/files/delete-multiple-files.md) ### Sync Logging API Overview: https://docs.sparklayer.io/developers/api/sync-log.md · OpenAPI spec: https://docs.sparklayer.io/openapi/sync-log.yaml - `DELETE /api/v1/sync-log` [Delete all sync logs](https://docs.sparklayer.io/developers/api/sync-log/delete-a-stores-sync-logs.md) - `GET /api/v1/sync-log/{integration}/{data_type}` [Get a sync status](https://docs.sparklayer.io/developers/api/sync-log/get-sync-logging-status.md) - `PUT /api/v1/sync-log/{integration}/{data_type}` [Record a full sync](https://docs.sparklayer.io/developers/api/sync-log/update-sync-logs-with-a-full-sync.md) - `PATCH /api/v1/sync-log/{integration}/{data_type}` [Record a partial sync](https://docs.sparklayer.io/developers/api/sync-log/update-sync-logs-with-a-partial-sync.md) - `GET /api/v1/sync-log/{integration}/{data_type}/errors` [List sync errors](https://docs.sparklayer.io/developers/api/sync-log/get-sync-logging-status-errors.md) ### Ignite endpoints (your service implements these; SparkLayer calls them) Overview: https://docs.sparklayer.io/ignite/api.md · OpenAPI spec: https://docs.sparklayer.io/openapi/ignite.yaml - `POST /v1/{siteEnv}/{siteId}` [Connect a platform](https://docs.sparklayer.io/ignite/api/new-platform-connection.md) - `DELETE /v1/{siteEnv}/{siteId}` [Disconnect a platform](https://docs.sparklayer.io/ignite/api/remove-platform-connection.md) - `PUT /v1/{siteEnv}/{siteId}/customers` [Create or update a customer](https://docs.sparklayer.io/ignite/api/create-update-a-customer.md) - `POST /v1/{siteEnv}/{siteId}/sub-accounts` [Create a sub-account](https://docs.sparklayer.io/ignite/api/create-a-new-customer-sub-account.md) - `DELETE /v1/{siteEnv}/{siteId}/sub-accounts/{customer_external_id}` [Delete a sub-account](https://docs.sparklayer.io/ignite/api/remove-a-customer-sub-account.md) - `PATCH /v1/{siteEnv}/{siteId}/sub-accounts/{customer_external_id}` [Update a sub-account](https://docs.sparklayer.io/ignite/api/update-an-existing-customer-sub-account.md) - `POST /v1/{siteEnv}/{siteId}/customers/{customer_external_id}/addresses` [Create a customer address](https://docs.sparklayer.io/ignite/api/create-a-new-customer-address.md) - `PUT /v1/{siteEnv}/{siteId}/customers/{customer_external_id}/addresses/{customer_address_external_id}` [Update a customer address](https://docs.sparklayer.io/ignite/api/update-a-customer-address.md) - `DELETE /v1/{siteEnv}/{siteId}/customers/{customer_external_id}/addresses/{customer_address_external_id}` [Delete a customer address](https://docs.sparklayer.io/ignite/api/remove-a-customer-address.md) - `POST /v1/{siteEnv}/{siteId}/checkout/calculate` [Calculate tax and shipping](https://docs.sparklayer.io/ignite/api/calculate-tax-and-shipping.md) - `POST /v1/{siteEnv}/{siteId}/checkout/complete` [Complete checkout](https://docs.sparklayer.io/ignite/api/complete-checkout.md) - `GET /v1/{siteEnv}/{siteId}/sync/{syncDataType}` [List entities for a sync](https://docs.sparklayer.io/ignite/api/list-entities.md) - `POST /v1/{siteEnv}/{siteId}/sync/{syncDataType}/{entityId}` [Fetch an entity](https://docs.sparklayer.io/ignite/api/fetch-entity.md) - `POST /v1/{siteEnv}/{siteId}/status/{syncDataType}` [Trigger a data sync](https://docs.sparklayer.io/ignite/api/trigger-a-data-sync.md) - `GET /v1/{siteEnv}/{siteId}/metafield-configuration` [Get metafield configuration](https://docs.sparklayer.io/ignite/api/get-metafield-configuration.md) - `POST /v1/{siteEnv}/{siteId}/metafield-configuration/enable` [Enable a metafield](https://docs.sparklayer.io/ignite/api/enable-a-metafield.md) - `POST /v1/{siteEnv}/{siteId}/authentication/verify` [Verify storefront authentication](https://docs.sparklayer.io/ignite/api/verify-authentication.md) ## Guides # Developer documentation URL: https://docs.sparklayer.io/developers Build on SparkLayer with the REST APIs, JavaScript SDK, storefront templates and Ignite, and make your first authenticated API call in a few minutes. SparkLayer adds B2B ordering to the eCommerce platform you already use, such as Shopify. These pages are for developers who connect SparkLayer to other systems, or change how it works on a store. New to SparkLayer? Start with [How SparkLayer connects](https://docs.sparklayer.io/developers/architecture.md). It explains which system holds which data, and that decides what you need to build. ## Choose what to build - [REST APIs](https://docs.sparklayer.io/developers/api.md): Send price lists, stock levels and orders between your business systems, such as an ERP, and SparkLayer. - [Frontend integration](https://docs.sparklayer.io/developers/frontend.md): Add SparkLayer to your store's theme, hide retail-only parts of the page from B2B customers, and match it to your design. - [JavaScript SDK](https://docs.sparklayer.io/developers/javascript-sdk.md): Write your own code to change how SparkLayer behaves on your store, or build a headless storefront. - [Templates](https://docs.sparklayer.io/developers/templates.md): Change the invoice and quote PDFs, and the emails SparkLayer sends, with Liquid templates. - [Ignite](https://docs.sparklayer.io/ignite.md): Use SparkLayer on an eCommerce platform it doesn't have a ready-made integration for. - [Data and limits](https://docs.sparklayer.io/developers/data.md): Look up metafield keys for product, customer and order data, and the limits to design around. Not sure which you need? [Building integrations](https://docs.sparklayer.io/developers/building-integrations.md) compares building on the API yourself with using a middleware partner, or [ask our support team](https://docs.sparklayer.io/help/support.md). ## Make your first API call **Step 1.** **Check your plan** API access is on the [Pro and Enterprise plans](https://www.sparklayer.io/pricing/). **Step 2.** **Follow the quickstart** The [quickstart](https://docs.sparklayer.io/developers/quickstart.md) takes you from no credentials to a working API call: create an API key, get an access token and call an endpoint. **Step 3.** **Learn the rules every call follows** How to [authenticate](https://docs.sparklayer.io/developers/authentication.md), what the APIs return when something goes wrong ([errors](https://docs.sparklayer.io/developers/errors.md)), and how long lists come back in pages ([pagination](https://docs.sparklayer.io/developers/pagination.md)). **Step 4.** **Pick the APIs you need** The [API overview](https://docs.sparklayer.io/developers/api.md) says what each API does and which ones most integrations use. ## Build with AI These docs are written to work with AI assistants. Pick a ready-made prompt below, add a line about your setup, and open it in Claude or ChatGPT. Each prompt tells the assistant which pages to read and what a finished, production-ready result looks like. [Build with AI](https://docs.sparklayer.io/developers/build-with-ai.md) has every prompt, plus the docs MCP server and the OpenAPI specs. ### Sync prices from your ERP Push price lists and SKU prices from your ERP or PIM into SparkLayer on a schedule. Read: - https://docs.sparklayer.io/developers/authentication.md: getting and using an access token - https://docs.sparklayer.io/developers/api/pricing.md: how price lists and prices fit together - https://docs.sparklayer.io/developers/api/pricing/get-price-lists-v2.md: listing existing price lists (paginated) - https://docs.sparklayer.io/developers/api/pricing/create-a-price-list.md: creating missing price lists - https://docs.sparklayer.io/developers/api/pricing/update-pricing-by-price-list.md: updating the prices in one price list - https://docs.sparklayer.io/developers/api/pricing/update-pricing-for-multiple-price-lists.md: updating prices across price lists in one call - https://docs.sparklayer.io/developers/api/sync-log.md: recording each sync so it shows in the SparkLayer Dashboard - https://docs.sparklayer.io/developers/errors.md: error format and retries - https://docs.sparklayer.io/developers/pagination.md: paging through price lists Steps: 1. Read the price lists and prices (including any quantity price breaks) from my ERP. 2. Fetch every SparkLayer price list with `GET /api/v2/price-lists`, following the pagination. 3. Create any price list that exists in my ERP but not in SparkLayer with `POST /api/v1/price-lists`, as the Pricing API docs describe for price lists managed by an external system. 4. Send the prices for each price list with `PATCH /api/v1/price-lists/{slug}/pricing`, or several price lists at once with `PATCH /api/v1/batch-update-pricing`, matching products by SKU and keeping each request within the documented limits. 5. Record each run with the Sync Logging API: a full sync for a complete run, a partial sync for changes only. ### Keep stock levels in sync Send stock levels per SKU and location from your ERP or WMS every few minutes. Read: - https://docs.sparklayer.io/developers/authentication.md: getting and using an access token - https://docs.sparklayer.io/developers/api/stock.md: stock levels and stock locations - https://docs.sparklayer.io/developers/api/stock/list-stock-locations.md: the stock locations SparkLayer knows about - https://docs.sparklayer.io/developers/api/stock/update-stock-levels.md: updating stock for many SKUs in one call - https://docs.sparklayer.io/developers/api/sync-log.md: recording each sync - https://docs.sparklayer.io/developers/errors.md: error format and retries Steps: 1. Read current stock levels per SKU (and per warehouse, if I have several) from my stock system. 2. Map my warehouses to SparkLayer stock locations from `GET /api/v1/stock-locations`, and tell me if any are missing rather than guessing. 3. Send the levels with `POST /api/v1/batch-update-stock`, matching products by SKU, in batches within the documented limits. 4. Record each run with the Sync Logging API. ### Send B2B orders to your ERP Pick up new and updated B2B orders from SparkLayer and create them in your ERP. Read: - https://docs.sparklayer.io/developers/authentication.md: getting and using an access token - https://docs.sparklayer.io/developers/api/purchasing.md: what a purchase is and how orders, quotes and carts differ - https://docs.sparklayer.io/developers/api/purchasing/get-purchases.md: listing orders with filters - https://docs.sparklayer.io/developers/api/purchasing/get-purchase.md: fetching one order in full - https://docs.sparklayer.io/developers/pagination.md: offset paging through purchases - https://docs.sparklayer.io/developers/errors.md: error format and retries Steps: 1. List orders with `GET /api/v1/purchases`, filtered to orders updated since the last successful run (use the documented date filters, not my own), paging with `limit` and `offset`. 2. Fetch each order in full if the list response lacks fields I need, and map it to my ERP's sales order format, including lines, prices, tax, shipping and the customer. 3. Create the order in my ERP, or update it if it was already sent, keyed on the SparkLayer order ID so nothing is created twice. 4. Store the timestamp of the last order processed and resume from it on the next run. --- # How SparkLayer connects URL: https://docs.sparklayer.io/developers/architecture How SparkLayer sits alongside your eCommerce platform and backend systems: B2B data syncs through the SparkLayer API while the Frontend adds wholesale features. SparkLayer works alongside your eCommerce platform; it doesn't replace it. Your platform still runs your catalogue, your customers, checkout and orders. SparkLayer adds what B2B buyers need on top, such as their own prices, pack sizes and ordering rules. How SparkLayer connects: - **Backend:** B2B-specific data (price lists, product pack sizes, customer rules) comes from an ERP / CRM, a manual upload or an automatic sync, and is sent to the **SparkLayer Backend**, which stores it. - **eCommerce platform** (e.g. Shopify): its core functions (order, catalogue and customer management, analytics, marketing and promotions, CMS) sync both ways with the **SparkLayer Connector**. - **Website storefront:** core templates (homepage, collections, products, static pages, blog) work as normal. The **SparkLayer Frontend** overlays B2B widgets: product pricing, buy button, quick order form, checkout and ordering, and the My Account area. ## The three parts of SparkLayer | Part | What it does | | --- | --- | | **Backend** | Stores your B2B data, such as price lists, pack sizes and customer rules. Your backend systems send it through the [SparkLayer API](https://docs.sparklayer.io/developers/api.md), or you add it with the import tools in the [Dashboard](https://docs.sparklayer.io/help/dashboard.md). | | **Connector** | Keeps SparkLayer and your eCommerce platform in step, in both directions. Products and customers come in from your platform: see [Product and customer sync](https://docs.sparklayer.io/help/integrations/data-sync.md). | | **Frontend** | Adds B2B features to your storefront, such as customer-specific prices, quick order and the My Account area. The rest of your site works as normal. See [Frontend integration](https://docs.sparklayer.io/developers/frontend.md). | ## What this means for your integration - **Keep products and customers on your eCommerce platform.** SparkLayer reads them from there, so you don't send them to SparkLayer. - **Send B2B data from the system that owns it.** Prices and stock usually live in your ERP, which is their [source of truth](https://docs.sparklayer.io/help/glossary.md#source-of-truth): send them to SparkLayer with the API, and change them in the ERP, not in SparkLayer. - **No ready-made integration for your platform?** With [Ignite](https://docs.sparklayer.io/ignite.md), you build the Connector yourself. ## Next steps - [SparkLayer API](https://docs.sparklayer.io/developers/api.md): What each API does, and which ones your integration needs. - [Building integrations](https://docs.sparklayer.io/developers/building-integrations.md): Build the integration yourself, or use a middleware partner. - [Data limitations](https://docs.sparklayer.io/developers/data/data-limitations.md): Recommended and hard limits on the data you send. - [SparkLayer Ignite](https://docs.sparklayer.io/ignite.md): Connect SparkLayer to a platform without a ready-made integration. --- # Building integrations URL: https://docs.sparklayer.io/developers/building-integrations Choose how to connect your ERP or CRM to SparkLayer: build a direct integration with the SparkLayer API or use a middleware partner such as an iPaaS. If your B2B data lives in a backend system, such as an ERP or CRM, you can send it to SparkLayer automatically instead of keeping it up to date by hand. There are two ways to connect them. ## Choose how to connect | Option | How it works | Who builds it | Choose it when | | --- | --- | --- | --- | | **Direct connection** | You build an integration between your backend system and SparkLayer with the [SparkLayer API](https://docs.sparklayer.io/developers/api.md). | Your developers, or an agency | You have developers, or you need full control over what syncs and when. | | **Middleware connection** | A middleware tool you already use, such as an [iPaaS](https://docs.sparklayer.io/help/glossary.md#ipaas), passes the data between your backend system and SparkLayer. | Your middleware partner's team | Your systems already connect through middleware, and it supports SparkLayer. | Either way, your backend system keeps talking to your eCommerce platform as it does today. Only the B2B data, such as price lists, pack sizes and payment terms, goes to SparkLayer. How data flows between systems: 1. Your backend system (e.g. Brightpearl, Oracle NetSuite, Microsoft Dynamics, Cegid, SEKO, SAP, OrderWise) holds the B2B data that's typically synchronised: B2B pricing (e.g. price lists), product rules (e.g. pack sizing) and customer rules (e.g. payment terms). 2. It's integrated with both Shopify and SparkLayer, via a direct connection or a middleware connection. 3. Specific B2B data syncs to SparkLayer; product, customer and order data syncs to Shopify. 4. SparkLayer connects to Shopify and enables the B2B portal with your synchronised B2B data. ## Check for a ready-made integration SparkLayer already integrates with many backend systems, so you may not need to build anything. See the [full list of integrations](https://www.sparklayer.io/integrations/index.html). To talk through how SparkLayer works with your systems, contact our [support team](https://docs.sparklayer.io/help/support.md). ## Next steps - [API quickstart](https://docs.sparklayer.io/developers/quickstart.md): make your first SparkLayer API request. - [SparkLayer API](https://docs.sparklayer.io/developers/api.md): which APIs your integration needs. - [SparkLayer Ignite](https://docs.sparklayer.io/ignite.md): connect SparkLayer to a platform without a ready-made integration. --- # Quickstart URL: https://docs.sparklayer.io/developers/quickstart Make your first SparkLayer API call in about five minutes: create an API key in the Dashboard, request an access token and list your price lists. This guide takes you from no credentials to a successful API call. You need a SparkLayer account on the [Pro or Enterprise plan](https://www.sparklayer.io/pricing/) and a terminal with `curl` (and [`jq`](https://jqlang.org), to read the token from the response). > **Use the test environment first** > > If your account has [Test mode](https://docs.sparklayer.io/help/dashboard/test-mode.md) set up, create your first key in the test environment and call `https://test.app.sparklayer.io`. Test and live hold separate data, so you can experiment without affecting your live store. **Step 1.** **Create an API key** In the SparkLayer Dashboard, go to **Settings > API** in the SparkLayer Dashboard ([open](https://app.sparklayer.io/configuration/api)), or **SparkLayer Wholesale > Settings > API** in the Shopify app. For a test key, turn on **Test mode** in the left-hand menu first. Select **Create new API Key**, enter a name such as `Quickstart`, and copy the three values shown: | Value | Used as | | --- | --- | | Site ID | The `Site-Id` header on every request | | Client ID | `client_id` when you request a token | | Client Secret | `client_secret` when you request a token: shown once | **Step 2.** **Save your credentials in your shell** So the commands below run as they are, put your values in environment variables. For the test environment, set `SPARKLAYER_BASE_URL` to `https://test.app.sparklayer.io`. ```bash title="Terminal" export SPARKLAYER_BASE_URL="https://app.sparklayer.io" export SPARKLAYER_SITE_ID="" export SPARKLAYER_CLIENT_ID="" export SPARKLAYER_CLIENT_SECRET="" ``` **Step 3.** **Request an access token** Exchange the client ID and secret for an access token. The request body is JSON, and the `Site-Id` header is required here too: ```bash title="Terminal" curl -s -X POST "$SPARKLAYER_BASE_URL/api/auth/token" \ -H "Site-Id: $SPARKLAYER_SITE_ID" \ -H "Content-Type: application/json" \ -d "{ \"grant_type\": \"client_credentials\", \"client_id\": \"$SPARKLAYER_CLIENT_ID\", \"client_secret\": \"$SPARKLAYER_CLIENT_SECRET\" }" ``` The response contains your token, which is valid for one hour (`expires_in` is in seconds): ```json title="Response" { "token_type": "Bearer", "expires_in": 3600, "access_token": "" } ``` To keep the token in a variable for the next step, run the same request through `jq`: ```bash title="Terminal" export SPARKLAYER_TOKEN=$(curl -s -X POST "$SPARKLAYER_BASE_URL/api/auth/token" \ -H "Site-Id: $SPARKLAYER_SITE_ID" \ -H "Content-Type: application/json" \ -d "{\"grant_type\":\"client_credentials\",\"client_id\":\"$SPARKLAYER_CLIENT_ID\",\"client_secret\":\"$SPARKLAYER_CLIENT_SECRET\"}" \ | jq -r .access_token) ``` **Step 4.** **Call an endpoint** List your price lists with the [Pricing API](https://docs.sparklayer.io/developers/api/pricing/get-price-lists-v2.md), sending the token in the `Authorization` header and your Site ID in the `Site-Id` header: ```bash title="Terminal" curl -s "$SPARKLAYER_BASE_URL/api/v2/price-lists?page=1&page_size=100" \ -H "Site-Id: $SPARKLAYER_SITE_ID" \ -H "Authorization: Bearer $SPARKLAYER_TOKEN" \ -H "User-Agent: quickstart/1.0" ``` You get back a page of price lists, with an empty `data` list if your site has none yet: ```json title="Response" { "data": [ { "slug": "base-list", "name": "Base Price List", "currency_code": "GBP", "source": "custom", "tax_inclusive_display": false } ], "pagination": { "total": 1, "per_page": 100, "current_page": 1, "total_pages": 1 } } ``` ## The same in code The same two calls as a script you can build on. It reads the same environment variables. **Node.js:** Node.js 18 or later, with no dependencies: ```javascript title="quickstart.mjs" const { SPARKLAYER_BASE_URL, SPARKLAYER_SITE_ID, SPARKLAYER_CLIENT_ID, SPARKLAYER_CLIENT_SECRET } = process.env; const tokenResponse = await fetch(`${SPARKLAYER_BASE_URL}/api/auth/token`, { method: 'POST', headers: { 'Site-Id': SPARKLAYER_SITE_ID, 'Content-Type': 'application/json' }, body: JSON.stringify({ grant_type: 'client_credentials', client_id: SPARKLAYER_CLIENT_ID, client_secret: SPARKLAYER_CLIENT_SECRET, }), }); if (!tokenResponse.ok) throw new Error(`Token request failed: ${tokenResponse.status}`); const { access_token } = await tokenResponse.json(); const response = await fetch(`${SPARKLAYER_BASE_URL}/api/v2/price-lists?page=1&page_size=100`, { headers: { 'Site-Id': SPARKLAYER_SITE_ID, Authorization: `Bearer ${access_token}`, 'User-Agent': 'quickstart/1.0', }, }); if (!response.ok) throw new Error(`Request failed: ${response.status} ${await response.text()}`); const { data, pagination } = await response.json(); console.log(`${pagination.total} price lists:`, data.map((list) => list.slug)); ``` ```bash title="Terminal" node quickstart.mjs ``` **Python:** Python 3.9 or later, with [`requests`](https://pypi.org/project/requests/): ```python title="quickstart.py" import os import requests base_url = os.environ["SPARKLAYER_BASE_URL"] site_id = os.environ["SPARKLAYER_SITE_ID"] token_response = requests.post( f"{base_url}/api/auth/token", headers={"Site-Id": site_id}, json={ "grant_type": "client_credentials", "client_id": os.environ["SPARKLAYER_CLIENT_ID"], "client_secret": os.environ["SPARKLAYER_CLIENT_SECRET"], }, timeout=30, ) token_response.raise_for_status() access_token = token_response.json()["access_token"] response = requests.get( f"{base_url}/api/v2/price-lists", params={"page": 1, "page_size": 100}, headers={ "Site-Id": site_id, "Authorization": f"Bearer {access_token}", "User-Agent": "quickstart/1.0", }, timeout=30, ) response.raise_for_status() body = response.json() print(f"{body['pagination']['total']} price lists:", [p["slug"] for p in body["data"]]) ``` ```bash title="Terminal" python quickstart.py ``` For a production integration, reuse the token until it's close to expiry instead of requesting one per call: see [Reuse the access token](https://docs.sparklayer.io/developers/authentication.md#reuse-the-access-token). ## If something goes wrong | Response | What to check | | --- | --- | | `400` from `/api/auth/token` | The `Site-Id` and `Content-Type: application/json` headers are set, and the body includes all three fields | | `401` from `/api/auth/token` | The client ID and secret are correct and belong to the environment you're calling: live keys only work with `https://app.sparklayer.io`, test keys with `https://test.app.sparklayer.io` | | `401` or `403` from an API endpoint | The `Authorization` header is `Bearer ` followed by the token, the token is less than an hour old, and you're sending the same `Site-Id` and base URL you requested the token with | | `jq: command not found` | Install `jq`, or copy `access_token` from the response by hand: `export SPARKLAYER_TOKEN=""` | See [Errors](https://docs.sparklayer.io/developers/errors.md) for the error format and every status code. ## Next steps - [Build with AI](https://docs.sparklayer.io/developers/build-with-ai.md): Ready-made prompts for syncing prices, stock and orders - [Authentication](https://docs.sparklayer.io/developers/authentication.md): Environments, token reuse and authentication errors - [API overview](https://docs.sparklayer.io/developers/api.md): Find the APIs your integration needs Every endpoint page in the API reference also has a playground: enter your token and Site ID to try requests from your browser. --- # Authentication URL: https://docs.sparklayer.io/developers/authentication Authenticate with the SparkLayer APIs using OAuth 2.0 client credentials: request a Bearer token from /api/auth/token and send it with your Site-Id header. Every request to the SparkLayer APIs needs an access token. Getting one takes three steps: 1. Create an API key in the SparkLayer Dashboard. 2. Swap the key's client ID and secret for an access token, which lasts an hour. 3. Send the token with every request. This is the standard OAuth 2.0 client credentials flow. For a step-by-step first call, see the [quickstart](https://docs.sparklayer.io/developers/quickstart.md). ## Environments Each SparkLayer account has a live environment and, on plans with [Test mode](https://docs.sparklayer.io/help/dashboard/test-mode.md), a test environment. The host you call selects the environment: | Environment | Base URL | | --- | --- | | Live | `https://app.sparklayer.io` | | Test | `https://test.app.sparklayer.io` | The environments hold separate data and separate API keys. A key created while the Dashboard is in Test mode only works against the test base URL, and a key created in live mode only works against the live base URL. ## Create an API key **Step 1.** **Open the API settings** In the SparkLayer Dashboard, go to **Settings > API** in the SparkLayer Dashboard ([open](https://app.sparklayer.io/configuration/api)), or **SparkLayer Wholesale > Settings > API** in the Shopify app. Turn on **Test mode** first if you want a key for the test environment. **Step 2.** **Create a key** Select **Create new API Key** and give the key a name that identifies the integration using it. **Step 3.** **Copy the credentials** Copy the **Site ID**, **Client ID** and **Client Secret**. The client secret is only shown once, so store it securely — for example in your integration's secrets manager. Deleting a key in the Dashboard also revokes every access token issued for it. ## Request an access token Send a `POST` request to `/api/auth/token` with your Site ID in the `Site-Id` header and your credentials in a JSON body. The token endpoint only accepts `Content-Type: application/json`: ```bash curl -X POST https://app.sparklayer.io/api/auth/token \ -H "Site-Id: " \ -H "Content-Type: application/json" \ -d '{ "grant_type": "client_credentials", "client_id": "", "client_secret": "" }' ``` | Field | Value | | --- | --- | | `grant_type` | Always `client_credentials` | | `client_id` | The Client ID shown when you created the key | | `client_secret` | The Client Secret shown when you created the key | If the credentials are valid, you receive an access token: ```json { "token_type": "Bearer", "expires_in": 3600, "access_token": "" } ``` | Field | Description | | --- | --- | | `token_type` | Always `Bearer` | | `expires_in` | Seconds until the token expires: tokens are valid for 1 hour | | `access_token` | The token to send in the `Authorization` header | A token only works with the Site ID and environment it was issued for. No refresh token is issued: when a token expires, request a new one with the same credentials. > **Standard OAuth libraries won't work as they are** > > The token request is JSON with a `Site-Id` header, not the form-encoded body most OAuth 2.0 client libraries send. Request the token with a plain HTTP call, as below, or configure your library to send JSON and the extra header. ## Call the API Send the token in the `Authorization` header, together with the `Site-Id` header, on every request: ```bash curl "https://app.sparklayer.io/api/v2/price-lists" \ -H "Site-Id: " \ -H "Authorization: Bearer " \ -H "User-Agent: acme-erp-sync/1.2" ``` | Header | Value | | --- | --- | | `Site-Id` | Your Site ID | | `Authorization` | `Bearer ` | | `Content-Type` | `application/json` for requests with a body | > **Set a User-Agent** > > Set a `User-Agent` header that identifies your integration, such as `acme-erp-sync/1.2`. It helps our support team find your requests if you need help debugging an issue. ## Reuse the access token Request a token once and reuse it for every call until it's close to expiry, rather than requesting one per call. These small helpers do that, and fetch a new token when one is rejected: **Node.js:** ```javascript title="sparklayer.mjs" const { SPARKLAYER_BASE_URL, SPARKLAYER_SITE_ID, SPARKLAYER_CLIENT_ID, SPARKLAYER_CLIENT_SECRET } = process.env; let token = null; // { value, expiresAt } async function getToken() { // reuse the token until a minute before it expires if (token && Date.now() < token.expiresAt - 60_000) return token.value; const response = await fetch(`${SPARKLAYER_BASE_URL}/api/auth/token`, { method: 'POST', headers: { 'Site-Id': SPARKLAYER_SITE_ID, 'Content-Type': 'application/json' }, body: JSON.stringify({ grant_type: 'client_credentials', client_id: SPARKLAYER_CLIENT_ID, client_secret: SPARKLAYER_CLIENT_SECRET, }), }); if (!response.ok) throw new Error(`Token request failed: ${response.status}`); const body = await response.json(); token = { value: body.access_token, expiresAt: Date.now() + body.expires_in * 1000 }; return token.value; } /** Calls a SparkLayer API path, e.g. sparklayer('/api/v2/price-lists'). */ export async function sparklayer(path, init = {}, retried = false) { const response = await fetch(`${SPARKLAYER_BASE_URL}${path}`, { ...init, headers: { 'Site-Id': SPARKLAYER_SITE_ID, Authorization: `Bearer ${await getToken()}`, 'User-Agent': 'acme-erp-sync/1.2', ...(init.body ? { 'Content-Type': 'application/json' } : {}), ...init.headers, }, }); if (response.status === 401 && !retried) { token = null; // the token was rejected: get a new one and try once more return sparklayer(path, init, true); } return response; } ``` **Python:** ```python title="sparklayer.py" import os import time import requests BASE_URL = os.environ["SPARKLAYER_BASE_URL"] SITE_ID = os.environ["SPARKLAYER_SITE_ID"] _token = {"value": None, "expires_at": 0.0} def get_token() -> str: # reuse the token until a minute before it expires if _token["value"] and time.time() < _token["expires_at"] - 60: return _token["value"] response = requests.post( f"{BASE_URL}/api/auth/token", headers={"Site-Id": SITE_ID}, json={ "grant_type": "client_credentials", "client_id": os.environ["SPARKLAYER_CLIENT_ID"], "client_secret": os.environ["SPARKLAYER_CLIENT_SECRET"], }, timeout=30, ) response.raise_for_status() body = response.json() _token.update(value=body["access_token"], expires_at=time.time() + body["expires_in"]) return _token["value"] def sparklayer(method: str, path: str, retried: bool = False, **kwargs) -> requests.Response: """Calls a SparkLayer API path, e.g. sparklayer("GET", "/api/v2/price-lists").""" headers = { "Site-Id": SITE_ID, "Authorization": f"Bearer {get_token()}", "User-Agent": "acme-erp-sync/1.2", **kwargs.pop("headers", {}), } response = requests.request(method, f"{BASE_URL}{path}", headers=headers, timeout=60, **kwargs) if response.status_code == 401 and not retried: _token["value"] = None # the token was rejected: get a new one and try once more return sparklayer(method, path, retried=True, **kwargs) return response ``` For retries on other errors, see [Retrying requests](https://docs.sparklayer.io/developers/errors.md#retrying-requests). ## Authentication errors If authentication fails, the API responds with a JSON error body. See [Errors](https://docs.sparklayer.io/developers/errors.md) for the format and every status code. | Status | When | | --- | --- | | `400` | The token request is missing the `Site-Id` header, a required field or `Content-Type: application/json` | | `401` | The client ID or secret is wrong, or the access token is missing, invalid or expired | | `401` or `403` | The access token was issued for a different Site ID or environment. The Pricing and Purchasing APIs also return `403` when the `Authorization` header is missing | --- # Errors URL: https://docs.sparklayer.io/developers/errors HTTP status codes the SparkLayer APIs return, the JSON error body and its fields, validation error details, and which errors you can safely retry. The SparkLayer APIs use standard HTTP status codes: `2xx` for success, `4xx` when the request needs fixing, and `5xx` when something went wrong on SparkLayer's side. Most errors include a JSON body that explains the problem. ## Error body Error bodies follow the [RFC 7807 problem details](https://www.rfc-editor.org/rfc/rfc7807) format: ```json { "title": "invalid-request-contents", "status": 400, "detail": "Unable to validate request contents", "errors": [ { "code": "keyword-mismatch", "message": "", "property": "customer_id" } ] } ``` | Field | Description | | --- | --- | | `title` | A short identifier for the type of problem, such as `invalid-request-contents` or `resource-not-found` | | `status` | The HTTP status code, repeated for convenience | | `detail` | A human-readable explanation. Use it for logging and debugging, not to drive your integration's logic | | `errors` | Optional. A list of individual problems, each with a machine-readable `code`, a `message` and, where relevant, the `property` it applies to | Some APIs also include `"type": "about:blank"`. Treat the status code as the primary signal, `title` and `errors[].code` as more specific detail, and ignore fields you don't recognise. > **Differences between APIs** > > The APIs are separate services, so some responses differ in detail: > > - Errors are `application/problem+json`, except the cart's **Calculate** and **Complete** validation errors (`422`), which are plain `application/json` with their own `cart-error` and `cart-validation-errors` shapes. > - Invalid price updates sent to the [Pricing API](https://docs.sparklayer.io/developers/api/pricing.md) (for example with **Update prices in a price list**) return a `400` whose body is a list of `{ "line", "field", "message" }` objects, so you can match each problem to a row of your JSON array or CSV file. > - Some responses, such as a `404` for an unknown route, a `405`, a `415` or an unexpected `500`, can have an empty body. > - The endpoint pages in the API reference list the responses each operation returns. ## Status codes | Status | Meaning | What to do | | --- | --- | --- | | `200`, `201`, `204` | Success. `204` responses have no body | — | | `207` | Partial success: some items in the request couldn't be processed, and each affected item carries an error. Returned by **Get customer-specific pricing** | Check each item for an error | | `400` | The request is invalid: the body, parameters or headers (including `Site-Id`) don't match the API's schema, a required value is missing, or the request breaks a business rule | Fix the request using `detail` and `errors`. Don't retry it unchanged | | `401` | The access token is missing, malformed or expired, or was issued for a different site or environment. On `/api/auth/token`, the client ID or secret is wrong | Request a new access token, then retry. See [Authentication](https://docs.sparklayer.io/developers/authentication.md) | | `403` | The request isn't allowed. The Pricing and Purchasing APIs also return `403` when the `Authorization` header is missing, or the token was issued for a different site or environment | Check the `Authorization` and `Site-Id` headers, and that you're using credentials for the right environment | | `404` | The resource or route doesn't exist | Check the ID, slug or `lookupBy` value and the URL | | `405` | The endpoint exists but doesn't support the HTTP method | Check the method in the API reference | | `409` | The request conflicts with existing data — for example a duplicate external ID, a newer version of the record already exists, or another update to the same data was running at the same time | Fetch the current state, resolve the conflict and retry | | `410` | The resource is gone — for example a file that failed its checks after upload | Don't retry. Upload the file again | | `415` | The `Content-Type` header isn't supported by the endpoint | Send `Content-Type: application/json` (the Pricing API also accepts `text/csv` on some endpoints) | | `422` | The Ordering API can't process the cart — for example it's empty, or it has validation errors when you try to complete it | See [Cart errors](#cart-errors) | | `500` | An unexpected error on SparkLayer's side | Retry with exponential backoff. If it persists, [contact support](https://docs.sparklayer.io/help/support.md) with the request details | | `502`, `503`, `504` | The service is temporarily unavailable or took too long to respond | Retry with exponential backoff | ## Authentication errors Errors from `/api/auth/token`, and errors caused by a missing or invalid token on the Core and Ordering APIs, use [OAuth 2.0 error codes](https://www.rfc-editor.org/rfc/rfc6749#section-5.2) as the `title`: ```json { "title": "invalid_client", "status": 401, "detail": "Client authentication failed" } ``` | `title` | Status | Cause | | --- | --- | --- | | `invalid_client` | `401` | The client ID or secret is wrong, the key has been deleted, or it belongs to the other environment | | `invalid_request` | `400` | A required field, such as `client_secret`, is missing | | `unsupported_grant_type` | `400` | `grant_type` isn't `client_credentials` | | `access_denied` | `401` | The `Authorization` header is missing, or the token is invalid, expired or for another site or environment | The Pricing, Purchasing, Stock, Sync Logging and Files APIs return a `401` or `403` problem body with a `detail` explaining why the token was rejected. ## Validation errors When a request doesn't match the API's schema, the response is a `400` with details of each problem in `errors`. On the Core and Ordering APIs, and for most requests to the Pricing API, the `title` is `invalid-request-contents`. The Core and Ordering APIs use these codes: | `code` | Cause | | --- | --- | | `keyword-mismatch` | A value breaks a schema rule, such as a type, format or length. `property` points to it | | `body-mismatch` | The body isn't valid JSON or doesn't match the expected shape | | `headers-mismatch` | A required header, such as `Site-Id` or `Content-Type`, is missing or invalid | | `parameters-mismatch` | A path or query parameter is invalid, such as an unsupported `lookupBy` value | | `path-mismatch` | The path doesn't match the endpoint | | `missing-param` | A required value is missing | | `resource-already-exists` | A value that must be unique, such as a slug, is already in use | | `resource-id-not-found` | An ID or slug in the request doesn't match an existing resource | | `resource-id-invalid` | An ID isn't in the expected format | | `resource-in-use` | The resource can't be deleted because something else uses it | The Purchasing, Stock, Sync Logging and Files APIs describe schema problems in `detail` instead. ## Cart errors The [Ordering API](https://docs.sparklayer.io/developers/api/ordering.md) returns `422` when it can't process a cart. The `title` tells you which kind of problem it is: - `cart-error`: `errors` lists codes such as `cart-empty`, `cart-customer-not-found`, `cart-shipping-rate-handle-unavailable` or `cart-payment-method-unavailable`. - `cart-validation-errors`: returned by **Complete a cart** when the cart has validation errors you haven't chosen to ignore. `errors.validation_error_types` lists them. Fix the cart, or list the types you accept in `ignore_validation_errors`, and try again. ## Retrying requests - Retry `5xx` responses and `409` conflicts caused by concurrent updates, using exponential backoff. - On a `401`, request a new access token once and retry. If it fails again, check your credentials rather than retrying. - Don't retry other `4xx` responses without changing the request. - `PUT` requests that create or replace a record, such as **Create or update a purchase**, are safe to repeat. Before retrying a `POST` that creates something, check whether the first attempt succeeded. ## Rate limits SparkLayer doesn't publish fixed rate limits. Design your integration to be gentle and to recover: - Send changes in batches using the bulk endpoints (for example **Update prices across price lists** and **Update stock levels for multiple SKUs**) rather than one request per SKU. - Run requests one after another, or with a small number in parallel. - No SparkLayer API returns `429 Too Many Requests` today. If you ever receive one, wait before retrying: use the `Retry-After` header if the response has one, otherwise back off exponentially. --- # Pagination URL: https://docs.sparklayer.io/developers/pagination Page through large result sets in the SparkLayer APIs: page-based pagination for price lists, offset paging for purchases, and which lists return everything. Most SparkLayer list endpoints return every matching record in a single response. Two endpoint families paginate, each with its own pattern. ## Page-based: price lists `GET /api/v2/price-lists` and `GET /api/v2/price-lists-num-manual-prices` in the [Pricing API](https://docs.sparklayer.io/developers/api/pricing.md) return results one page at a time. | Query parameter | Default | Description | | --- | --- | --- | | `page` | `1` | The page to return | | `page_size` | `100` | Records per page, up to `250` | | `order_by` | `name:asc` | Sort field and direction: `name`, `created_at` or `updated_at`, followed by `:asc` or `:desc` | The response wraps the records in `data`, with a `pagination` object: ```json { "data": [ … ], "pagination": { "total": 312, "per_page": 100, "current_page": 1, "total_pages": 4 } } ``` Request pages until `current_page` equals `total_pages`: ```bash curl "https://app.sparklayer.io/api/v2/price-lists?page=2&page_size=100" \ -H "Site-Id: " \ -H "Authorization: Bearer " ``` > **Use v2 for price lists** > > `GET /api/v1/price-lists` is the older, unpaginated version of this endpoint: it returns up to 25,000 price lists in one array. Use `GET /api/v2/price-lists` for new integrations. ## Offset-based: purchases `GET /api/v1/purchases` in the [Purchasing API](https://docs.sparklayer.io/developers/api/purchasing.md) uses a limit and an offset. | Query parameter | Default | Description | | --- | --- | --- | | `limit` | Not set | Maximum number of purchases to return, from `1` to `500`. Always set it. | | `offset` | `0` | Number of purchases to skip | | `order_by` | `created_at` | Field to sort by, always newest first: `created_at`, `updated_at`, `placed_at`, `calculated_submitted_at` or `shipping_requested_date` | The response is a plain array with no total count. To fetch every purchase, increase `offset` by `limit` after each request, and stop when a response contains fewer than `limit` purchases: ```bash curl "https://app.sparklayer.io/api/v1/purchases?type=order&limit=500&offset=500" \ -H "Site-Id: " \ -H "Authorization: Bearer " ``` Keep `order_by` and any filters the same for every page. Records created or updated while you're paging can shift between pages, so use a date filter such as `dates[last_updated][gte]` for incremental syncs. ## Endpoints that aren't paginated Other list endpoints return all matching records in one response, including the Core API's customer groups, discounts, shipping methods and settings, and the Stock API's stock locations. A few have a fixed upper limit: - **List sync errors** in the [Sync Logging API](https://docs.sparklayer.io/developers/api/sync-log.md) returns at most 5,000 errors. - **List price lists** (v1) returns at most 25,000 price lists. --- # SparkLayer API URL: https://docs.sparklayer.io/developers/api Overview of the SparkLayer REST APIs: what each API covers, base URLs for live and test, authentication, request format and which plans include access. The SparkLayer API is a set of REST APIs that connect your backend systems, such as an ERP, CRM or [iPaaS](https://docs.sparklayer.io/help/glossary.md#ipaas), to SparkLayer. Use them to send B2B data like price lists and stock levels, keep order history in step, and manage how customers buy. > **Plan requirement** > > API access is available on the [Pro and Enterprise plans](https://www.sparklayer.io/pricing/). ## Which APIs you need Most integrations need only one or two of these APIs. - **On a platform SparkLayer integrates with, such as [Shopify](https://docs.sparklayer.io/help/platforms/shopify.md)?** SparkLayer already gets your products and customers from the platform. Start with the Pricing API, and add the Stock and Purchasing APIs if your ERP holds stock levels and order history. - **On a platform SparkLayer doesn't integrate with?** Use [Ignite](#ignite) to connect it. If you're not sure which approach suits your integration, [contact our support team](https://docs.sparklayer.io/help/support.md) to talk it through. ## The APIs | API | Use it to | | --- | --- | | [Pricing API](https://docs.sparklayer.io/developers/api/pricing.md) | Create price lists, set prices by SKU and calculate the price a customer pays | | [Stock API](https://docs.sparklayer.io/developers/api/stock.md) | Read, update and delete stock levels by SKU, and manage stock locations | | [Purchasing API](https://docs.sparklayer.io/developers/api/purchasing.md) | Keep the orders, quotes and carts customers see in My Account, with their history and payments | | [Ordering API](https://docs.sparklayer.io/developers/api/ordering.md) | Build a cart for a customer, price it, and place it as an order on your eCommerce platform | | [Core API](https://docs.sparklayer.io/developers/api/core.md) | Manage customer groups, discounts, shipping methods and settings, delete customers, and look up a customer's prices | | [Files API](https://docs.sparklayer.io/developers/api/files.md) | Upload and download files, such as the attachments customers add to orders | | [Sync Logging API](https://docs.sparklayer.io/developers/api/sync-log.md) | Record when each data sync ran, and which records failed and why | ## Base URLs | Environment | Base URL | | --- | --- | | Live | `https://app.sparklayer.io` | | Test | `https://test.app.sparklayer.io` | All the APIs share the same base URL. Live and test hold separate data and use separate API keys: see [Test mode](https://docs.sparklayer.io/help/dashboard/test-mode.md). ## Authentication The APIs use the OAuth 2.0 client credentials flow: 1. Create an API key in the Dashboard, at **Settings > API** in the SparkLayer Dashboard ([open](https://app.sparklayer.io/configuration/api)), or **SparkLayer Wholesale > Settings > API** in the Shopify app. 2. Exchange its client ID and secret for an access token at `POST /api/auth/token`. Tokens are valid for one hour. 3. Send the token as `Authorization: Bearer `, with your Site ID in the `Site-Id` header, on every request. See [Authentication](https://docs.sparklayer.io/developers/authentication.md) for the full flow, or the [quickstart](https://docs.sparklayer.io/developers/quickstart.md) to make your first call. ## Requests and responses - Request and response bodies are JSON. Send `Content-Type: application/json` with any request that has a body. - Send a `User-Agent` header that identifies your integration, so our support team can find your requests. - Errors return an HTTP status code and a JSON body describing the problem: see [Errors](https://docs.sparklayer.io/developers/errors.md). - Some list endpoints return results in pages: see [Pagination](https://docs.sparklayer.io/developers/pagination.md). ## Ignite [Ignite](https://docs.sparklayer.io/ignite.md) connects SparkLayer to an eCommerce platform it doesn't have a ready-made integration for. It works the other way round from the APIs above: instead of your code calling SparkLayer, SparkLayer calls a service you build. That service answers [SparkLayer's requests](https://docs.sparklayer.io/ignite/api.md), for example to create customers, work out tax and shipping, complete checkout and sync data from your platform. ## Next steps - [Quickstart](https://docs.sparklayer.io/developers/quickstart.md): make your first API request. - [Authentication](https://docs.sparklayer.io/developers/authentication.md): handle tokens and token expiry. - [Pricing API](https://docs.sparklayer.io/developers/api/pricing.md): the API most integrations start with. --- # Core API URL: https://docs.sparklayer.io/developers/api/core Manage customer groups, discounts, shipping methods and settings with the Core API, delete customers and look up customer-specific pricing. The Core API manages the configuration that controls how your B2B customers buy: customer groups, discounts, shipping methods and settings. It also lets you remove customers and check the prices a specific customer would pay. ## What the Core API covers | Area | What you can do | | --- | --- | | Customer groups | List, create, update and delete customer groups, which can nest under a parent group | | Discounts | List, create, update and delete discounts, and set the order in which they apply | | Shipping | List, create, update and delete the shipping methods offered at checkout | | Settings | Read and update global settings, such as price list selection, payment methods and order validation, and override them per customer group | | Customers | Delete a customer, and fetch the prices a customer would pay for a list of SKUs | ## Where other data comes from Products and customers aren't created through the Core API. SparkLayer syncs them from your eCommerce platform — through a built-in integration such as [Shopify](https://docs.sparklayer.io/help/platforms/shopify.md) or through [Ignite](https://docs.sparklayer.io/ignite.md) for other platforms. For other B2B data, use the dedicated API: | Data | API | | --- | --- | | Price lists and prices | [Pricing API](https://docs.sparklayer.io/developers/api/pricing.md) | | Stock levels and stock locations | [Stock API](https://docs.sparklayer.io/developers/api/stock.md) | | Orders, quotes and purchase transactions shown in My Account | [Purchasing API](https://docs.sparklayer.io/developers/api/purchasing.md) | | Carts and orders placed on behalf of a customer | [Ordering API](https://docs.sparklayer.io/developers/api/ordering.md) | | Files such as order attachments | [Files API](https://docs.sparklayer.io/developers/api/files.md) | | Data sync status and errors | [Sync Logging API](https://docs.sparklayer.io/developers/api/sync-log.md) | See the [API overview](https://docs.sparklayer.io/developers/api.md) for base URLs and authentication. ## Endpoints _Every operation is listed under "API operations" at the top of this file._ ## Next steps - [Authentication](https://docs.sparklayer.io/developers/authentication.md): get an access token for your requests. - [Pricing API](https://docs.sparklayer.io/developers/api/pricing.md): sync price lists and prices. --- # Ordering API URL: https://docs.sparklayer.io/developers/api/ordering Create, update, calculate and complete carts with the Ordering API to place orders for customers, check their pricing first and handle validation errors. The Ordering API lets you create SparkLayer carts on behalf of your customers and complete them as orders on your connected eCommerce platform — for example to place orders from an external system or for drop shipping. Carts use the same pricing, validation, ordering rules and checkout logic as the SparkLayer cart your customers use, except that upfront payment methods aren't available for carts created through the API. > **Beta** > > The Ordering API is in beta. If you'd like to use it, email [support@sparklayer.io](mailto:support@sparklayer.io) and our team will help you get started. The API reference below gives full endpoint and schema details. This guide covers checking customer-specific pricing, the cart and order lifecycle, and how validation works. ## Customer-specific pricing lookup The [Get customer-specific pricing](https://docs.sparklayer.io/developers/api/core/get-customer-specific-pricing.md) endpoint is part of the Core API. Use it before creating a cart to check pricing and price breaks for validation or user feedback. It calculates real-time pricing for one or more SKUs in the context of a specific customer. It returns per-line-item unit prices, line totals, and quantity price breaks, accounting for the customer's assigned price lists and any customer-level discount percentage. ### Price calculation SparkLayer looks up the customer's assigned price lists and uses those to calculate accurate pricing for each requested product. This includes: - **Quantity price breaks** — the price adjusts based on how many units are being ordered - **Variant aggregation** — if your store is configured to apply price breaks across variants, quantities across sizes or colours of the same product are combined when determining the price tier. For example, ordering 5× size M and 5× size L could unlock the 10-unit price break for both. - **Customer discounts** — any discount assigned to the customer is applied on top of the list price Each product in the response includes the unit price, line total, and a full breakdown of available quantity price breaks. All prices are returned as net values. ### Potential issues - **Partial pricing failure** — if any product can't be priced, the response status is `207`: affected line items include an error with null pricing data, and successfully priced items are returned alongside them. - **Invalid request** — a `400` is returned if the request is malformed, the customer doesn't exist, or the customer has no price lists configured. ## Order lifecycle Every order starts as a cart for a customer, and goes through four steps: 1. [Create a cart](#1-create-an-empty-cart) for the customer. 2. [Update the cart](#2-update-the-cart) with line items and order details, and review any validation errors. 3. [Calculate the cart](#3-calculate-the-cart) to get totals and the available shipping and payment methods. Calculate again with your chosen shipping rate to confirm the totals. 4. [Complete the cart](#4-complete-the-cart) with the shipping rate and payment method to place the order. ## 1. Create an empty cart [Create a cart](https://docs.sparklayer.io/developers/api/ordering/create-cart.md) for the customer you're placing the order for. Identify the customer with `customer_id` and `customer_lookup_by`: `sparklayer` (the customer's SparkLayer ID), `platform` (the ID from your eCommerce platform) or `email`. A new cart is empty. It holds all the order information, including line items, quantities and the shipping address. The response includes a `cart_id`: use it for every later request about the cart. ## 2. Update the cart [Update the cart](https://docs.sparklayer.io/developers/api/ordering/update-cart.md) with the information the order needs: - Line items and their quantities - The shipping address (`shipping_address_id`), or a `temporary_shipping_address` - The customer reference, PO number, requested shipping date and custom fields Each update replaces the cart's line items, so always send the full list with the final quantity of each item. Omitting `line_items`, or sending an empty array, empties the cart. For drop shipping, use a temporary shipping address when the delivery address shouldn't be stored against the customer's account. ### Validation Each cart update returns the latest cart state, including any validation errors generated by your business rules in SparkLayer, such as: - Minimum or maximum order quantity restrictions - Product availability constraints - Customer-specific purchasing restrictions The response also has a result for each line item, so you can see how each requested item was processed and whether it was adjusted or has a validation error. The [Update a cart](https://docs.sparklayer.io/developers/api/ordering/update-cart.md) and [Calculate a cart](https://docs.sparklayer.io/developers/api/ordering/calculate-cart.md) operations don't fail because of validation errors: the errors are included in the response for your application to handle. Calculating does return a `422` if the cart is empty or the shipping rate you chose isn't available — see [Cart errors](https://docs.sparklayer.io/developers/errors.md#cart-errors). The [Complete a cart](https://docs.sparklayer.io/developers/api/ordering/complete-cart.md) operation behaves differently. If validation errors exist, it returns a `422` and the cart isn't completed. To allow specific validation errors, list them in the `ignore_validation_errors` property. ## 3. Calculate the cart [Calculate the cart](https://docs.sparklayer.io/developers/api/ordering/calculate-cart.md) before completing it. SparkLayer: - Calculates the order totals and tax with the connected platform - Applies pricing - Returns the available shipping methods and their costs - Returns the allowed payment methods - Validates the current cart state To choose a shipping method, pass its `shipping_rate_handle`. If you leave it out, SparkLayer uses the first available shipping rate. Calculate again after changing the shipping rate or the cart, so the totals you show are up to date. ## 4. Complete the cart [Complete the cart](https://docs.sparklayer.io/developers/api/ordering/complete-cart.md) once it's valid and you've chosen how to ship and pay. Send: - `shipping_rate_handle`: a shipping rate returned when you calculated the cart - `payment_method`: `paymentByInvoice` or `paymentOnAccount` - `ignore_validation_errors`: the validation errors to allow (an empty list allows none, `"*"` allows all) Completing a cart turns it into a SparkLayer order and submits it to the connected eCommerce platform. The response includes the new `purchase_id`. The cart is deleted once it's completed, so later requests with its ID return `404`. ## Endpoints _Every operation is listed under "API operations" at the top of this file._ ## Next steps - [Authentication](https://docs.sparklayer.io/developers/authentication.md): get an access token for your requests. - [Errors](https://docs.sparklayer.io/developers/errors.md#cart-errors): the cart error codes and what they mean. - [Purchasing API](https://docs.sparklayer.io/developers/api/purchasing.md): the orders and quotes customers see in My Account. --- # Pricing API URL: https://docs.sparklayer.io/developers/api/pricing The Pricing API syncs price lists and SKU pricing from your ERP or PIM into SparkLayer, including customer-specific pricing. Covers the flow and endpoints. With the Pricing API, your price list data comes **directly** from your backend system (such as an ERP, PIM, CRM or iPaaS), and each product SKU is then assigned to a price list in SparkLayer. The diagram shows how this works. How price list data flows: 1. B2B pricing is managed in price lists that come from your backend system. Each B2B customer is assigned a price list, which gives them their own pricing when they sign in. 2. Price lists are imported into SparkLayer through the SparkLayer API, each with a name, ID and currency code. 3. Price lists are assigned to individual product SKUs. It's common for a SKU to have several price lists. 4. Customers are also assigned a price list in SparkLayer, matching your backend system (e.g. Customer A sees Price list 1, Customer B sees Price list 2). 5. When the customer signs in and visits a product page, the SparkLayer Frontend recognises them (and their price list) and shows their price. ## Typical integration flow The source system is the external system that sends the pricing data, such as your ERP. A typical sync: 1. Fetch the price lists from the source system. 2. Fetch the SparkLayer price lists with [Get price lists](https://docs.sparklayer.io/developers/api/pricing/get-price-lists-v2.md). To fetch only the lists your integration manages, filter by `source=integration`. 3. Create any price lists missing in SparkLayer with [Create a price list](https://docs.sparklayer.io/developers/api/pricing/create-a-price-list.md). Set `source` to `integration`: the API uses `integration` for price lists from external systems, `custom` (the default) for lists set up in the Dashboard, and `platform` for lists from the eCommerce platform. 4. Collect the prices for each price list from the source system and send them with [Update pricing by price list](https://docs.sparklayer.io/developers/api/pricing/update-pricing-by-price-list.md). The older `GET /api/v1/price-lists` endpoint is deprecated; use the v2 endpoint for new integrations. The same process applies to customer-specific pricing: create a price list for the customer, with a reference that links it to them, and send prices to it. To assign price lists to customers, use SparkLayer customer groups or assign them to customers directly. For direct assignment, see your platform's guide, such as [Shopify metafields](https://docs.sparklayer.io/help/platforms/shopify/metafields.md). ## Data mapping - Price lists are linked to price list rules. - Prices are connected to product data by SKU, and each SKU's price belongs to a price list. Price list data mapping: - A **price list** links to **price list rules**. - A **price list** links to **prices**. - Each **price** is linked to a product **SKU**. ## Checking price list data To check which price lists a product's prices are in, use the price editor in the SparkLayer Dashboard, at **Pricing > Price editor** in the SparkLayer Dashboard ([open](https://app.sparklayer.io/pricing/editor)), or **SparkLayer Wholesale > Pricing > Price editor** in the Shopify app. ## Endpoints _Every operation is listed under "API operations" at the top of this file._ ## Next steps - [API quickstart](https://docs.sparklayer.io/developers/quickstart.md): get a token and list your price lists. - [Managing pricing](https://docs.sparklayer.io/help/pricing/managing-pricing.md): how merchants work with price lists in the Dashboard. - [Stock API](https://docs.sparklayer.io/developers/api/stock.md): sync stock levels alongside your prices. --- # Purchasing API URL: https://docs.sparklayer.io/developers/api/purchasing Store orders, quotes and carts shown in My Account with the Purchasing API, using purchase identifiers, packages and purchase transactions. Lists all endpoints. The Purchasing API stores your customers' purchases: carts, quotes, purchases awaiting approval or confirmation, and placed orders. Use it to keep the orders shown in My Account up to date. > **Purchases aren't sent to your eCommerce platform** > > Purchases you create with this API show in the My Account area for your B2B customers, but SparkLayer doesn't push them to your eCommerce platform (such as Shopify). To have an order on your platform too, create it with the platform's own API, such as the [Shopify order API](https://shopify.dev/docs/api/admin-rest/latest/resources/order). For orders created on your platform to sync to SparkLayer, they need a few properties, which depend on your platform. On Shopify, each order needs: - The `b2b` tag - The `sparklayer.order_import` metafield, set to `true` See [Import order history into SparkLayer](https://docs.sparklayer.io/help/platforms/shopify/metafields.md#import-order-history-into-sparklayer) for the Shopify steps. ## Creating and updating purchases Create and update purchases with [Create or update a purchase](https://docs.sparklayer.io/developers/api/purchasing/create-update-purchase.md) (`PUT /api/v1/purchases/{lookupBy}/{identifier}`). If no purchase matches `lookupBy` and `identifier`, a new one is created. - **Send the full purchase every time.** The request replaces the purchase's previous data, so include every field, not just the ones that changed. This prevents stale data and keeps order integrations simple. - **Leave out calculated fields.** The API calculates them, so don't set them in the request body. - **Expect a `400` for missing or invalid values.** The error identifies the value to fix. ### Purchase identifiers The `purchase_identifiers` object holds the purchase's IDs in each system. Use one of them as `lookupBy`: | Identifier | Description | | --- | --- | | `sparklayer` | SparkLayer's ID for the purchase (a UUID). For an order placed through SparkLayer, this is the ID of the SparkLayer cart, which SparkLayer passes to your eCommerce platform when the customer completes checkout. Send it so the order moves from `awaiting_merchant` to `order` and replaces the cart. For other orders, leave it blank. | | `platform` | The purchase's ID on your eCommerce platform. | | `internal` | Your ID for the purchase, such as the order number in your ERP or order management system (OMS). | | `visible` | The ID shown to the customer in their recent orders. | ### Purchase types Most integrations send purchases of type `order`. | Purchase type | Description | | --- | --- | | `quote` | A quote, with a quote status. | | `awaiting_approval` | A purchase waiting for another person at the customer's company to approve it, typically with [company users](https://docs.sparklayer.io/help/customers/company-users.md). | | `awaiting_merchant` | An order completed in SparkLayer but not yet confirmed, because it's pending on the eCommerce platform or the sync hasn't finished. | | `order` | A finalised order, saved on the eCommerce platform or in your order source of truth, such as an ERP or OMS. | ### Packages A purchase is mostly made up of packages, which hold the line items, shipping methods, addresses and IDs. 1. When you first create an order, put its line items in a `processing` package. 2. As the order progresses, move items to the relevant packages, such as `shipment`, `return` or `cancelled`. You can have more than one of each, for example for partial shipments. The order status customers see is calculated from the package dates, such as when each package was created and shipped. ## Purchase transactions Record payments, refunds and other transactions against a purchase with [Create or update a purchase transaction](https://docs.sparklayer.io/developers/api/purchasing/create-or-update-a-purchase-transaction.md). SparkLayer uses the transactions to calculate the purchase's payment status. If you record transactions with this API, don't also send them to your eCommerce platform: the same transaction could be recorded twice. - **Identify the purchase** in the request URL, with `lookupBy` and `identifier`. A purchase can have many transactions, but each needs a unique `platform_id`: the ID the third-party system (such as your payment provider) uses for the transaction. `purchase_spark_id` in the response is read-only, so don't send it. - **Set the amount** in `total`: positive for a payment, negative for a refund. A `currency_code` is also required. - **Choose whether it counts** with `apply_to_balance`. | `apply_to_balance` | What happens | | --- | --- | | `false` | The transaction is stored for information only, and isn't used to calculate the payment status. The [list endpoint](https://docs.sparklayer.io/developers/api/purchasing/get-purchase-transactions.md) returns it, but nothing else happens. Use this for pending or failed transactions, which can help with debugging. | | `true` | The transaction counts towards the purchase's payment status. SparkLayer also adds an entry to the purchase history, which the customer sees, and adjusts the outstanding balance of the purchase's customer by the `total`. | Changing a transaction from `true` to `false` deletes the history entry it created, and reverses the change to the customer's balance and payment status. Use `full_transaction` to store the full transaction, as a JSON string, as the third-party system holds it. SparkLayer stores it as given, without redacting anything, so remove or obscure sensitive data, such as full card numbers, before you send it. ## Endpoints _Every operation is listed under "API operations" at the top of this file._ ## Next steps - [Authentication](https://docs.sparklayer.io/developers/authentication.md): get an access token for your requests. - [Ordering API](https://docs.sparklayer.io/developers/api/ordering.md): place orders on behalf of customers. - [Files API](https://docs.sparklayer.io/developers/api/files.md): download the files customers attach to orders. --- # Stock API URL: https://docs.sparklayer.io/developers/api/stock The Stock API reads, batch updates and deletes stock levels by SKU and manages stock locations for your SparkLayer site. Lists every endpoint and its responses. The Stock API holds the stock level of each SKU at each of your stock locations, such as warehouses. SparkLayer uses these levels to show stock on the storefront. ## How it works - **Stock locations** identify where stock is held. Each has a SparkLayer `id` and your own `external_id`, and you can use either to refer to it. - **Stock levels** are set in bulk: **Update stock levels for multiple SKUs** creates or overwrites the level for each SKU and location pair you send, and leaves other pairs unchanged. - **Get stock levels for multiple SKUs** only returns the location with the external ID `default` unless you ask for all locations or specific ones. ## Endpoints _Every operation is listed under "API operations" at the top of this file._ ## Next steps - [Stock display](https://docs.sparklayer.io/help/storefront/stock-display.md): how stock levels show to B2B customers. - [Pricing API](https://docs.sparklayer.io/developers/api/pricing.md): sync prices alongside stock. --- # Files API URL: https://docs.sparklayer.io/developers/api/files Upload, update and download files with the SparkLayer Files API using expiring URLs, and retrieve order file attachments from their GIDs. Lists all endpoints. The Files API stores and retrieves files in SparkLayer. SparkLayer uses it for order file attachments, and you can use it to upload files or download the files attached to your orders. ## How it works > **URLs expire** > > The upload and download URLs returned by the API expire after 15 minutes. ### Uploading a file Uploading takes two requests: 1. [Create a file](https://docs.sparklayer.io/developers/api/files/create-a-new-file.md) (`POST /api/v1/files`) with the file's path, including its name, in `file_path`. Like every API request, it needs your access token and Site ID: ```bash title="Create a file" curl -X POST https://app.sparklayer.io/api/v1/files \ -H "Authorization: Bearer " \ -H "Site-Id: " \ -H "Content-Type: application/json" \ -d '{ "file_path": "folder/file.pdf" }' ``` The response includes the file's `id`, a signed upload `url` and a list of `required_headers`. 2. Upload the file's contents with an HTTP `PUT` to that `url`, sending **every** header in `required_headers` (for example `Content-Type`). If any are missing, the upload fails. The signed URL grants access by itself, so this request doesn't need your `Authorization` or `Site-Id` headers: ```bash title="Upload the contents" curl --upload-file 'file.pdf' \ -H "Content-Type: application/pdf" \ '' ``` Files can be up to 20 MB. Use the file's `id` with the other endpoints. To replace a file's contents, [update the file](https://docs.sparklayer.io/developers/api/files/update-a-file.md) (`PATCH /api/v1/files/{id}`) to get a new upload URL, then upload again. ### Downloading a file To download a file, [get the file](https://docs.sparklayer.io/developers/api/files/get-a-file.md) (`GET /api/v1/files/{id}`). Once the upload has completed, this returns a download URL, some file metadata and a list of `required_headers`. Download the file from the URL, sending those headers. ### Failed uploads If the file's contents don't match its file extension, the file is deleted, and `GET /api/v1/files/{id}` returns `410 Gone`. ### Downloading order file attachments To download the files attached to a B2B order, use `GET /api/v1/files/{id}`. SparkLayer stores the IDs of attached files in the order's note attributes, under the name set in `checkoutCustomElements` in your theme (`file-upload` by default). They're stored as a JSON array: ```json title="Order note attribute" [ "gid://sparklayer/File/fdc70d92-fcdf-4719-af2b-9d319a5035d2", "gid://sparklayer/File/a7a3ee08-c6d0-49ee-89b4-bcdaf780d553", "gid://sparklayer/File/15be5381-0b50-459f-9643-af236b9f29f7" ] ``` Remove the `gid://sparklayer/File/` prefix from each one to get the file ID, for example `fdc70d92-fcdf-4719-af2b-9d319a5035d2`. ## Endpoints _Every operation is listed under "API operations" at the top of this file._ ## Next steps - [Authentication](https://docs.sparklayer.io/developers/authentication.md): get the access token the API requests need. - [Custom attributes and files](https://docs.sparklayer.io/developers/javascript-sdk/cart.md#custom-attributes-and-files): let customers attach files to cart items. - [Purchasing API](https://docs.sparklayer.io/developers/api/purchasing.md): look up the orders the files belong to. --- # Sync Logging API URL: https://docs.sparklayer.io/developers/api/sync-log The Sync Logging API records full and partial data syncs, sync errors and status between your systems and SparkLayer. Lists every endpoint and its parameters. The Sync Logging API records the progress of data syncs between an integration and SparkLayer — when full and partial syncs ran, and which records failed to sync and why. Logs are kept per integration and data type. ## How it works 1. **Record each full sync** with **Record a full sync** (`PUT`). This sets the `started_full_sync` and `last_full_sync` timestamps and replaces the logged errors with the ones you send. A timestamp you leave out is cleared. - For a quick sync, send one `PUT` when it finishes, with both timestamps and all the errors. - For a long or asynchronous sync, send a `PUT` with `started_full_sync` when it starts, record errors with `PATCH` requests as items sync, and send a final `PATCH` with `last_full_sync` once every item has synced. 2. **After each partial (incremental) sync**, use **Record a partial sync** (`PATCH`) to update `last_partial_sync`, clear errors for records that now sync successfully (listed in `successfully_synced`), and add new errors. 3. **Read the status and errors** with **Get a sync status** and **List sync errors**. Record a full sync before any partial syncs: until one exists, the status endpoints return `404` and partial sync timestamps aren't saved. Each error's `external_id` must be unique within a request. For how an Ignite integration logs its syncs, see [Ignite data sync](https://docs.sparklayer.io/ignite/data-sync.md#logging-sync-issues). ## Endpoints _Every operation is listed under "API operations" at the top of this file._ ## Next steps - [Authentication](https://docs.sparklayer.io/developers/authentication.md): get an access token for your requests. - [Ignite data sync](https://docs.sparklayer.io/ignite/data-sync.md): what to sync from a custom platform, and when. --- # Frontend integration guide URL: https://docs.sparklayer.io/developers/frontend Install the SparkLayer Frontend: add the Core Script or Shopify app embed, add product page widgets, hide retail elements with data-spark and style with CSS. The SparkLayer Frontend is what your B2B customers see on your website: their prices, the cart, quick order and their account. Adding it takes a few small code changes to your theme. Once they're in place, B2B customers can log in and start placing orders. It takes three steps: **Step 1.** **Add the Core Script** One script in your theme that loads SparkLayer on every page. **Step 2.** **Add the product page widgets** They show each customer their own prices on product pages, and let them add products to an order. **Step 3.** **Match your design** CSS variables set the colours, typefaces and spacing, so SparkLayer looks like the rest of your site. After that, you can tailor the B2B experience further: [Storefront](https://docs.sparklayer.io/help/storefront.md) has the settings, and [Integrations](https://docs.sparklayer.io/help/integrations.md) has advice for each platform. > **Personalised instructions** > > In the SparkLayer Dashboard, **Storefront > Widgets** in the SparkLayer Dashboard ([open](https://app.sparklayer.io/configuration/frontend/core)), or **SparkLayer Wholesale > Storefront > Widgets** in the Shopify app gives you personalised instructions for adding the code snippets to your website. ## The SparkLayer Core Script The Core Script is a JavaScript snippet in your website's theme. It loads the [frontend interfaces](https://docs.sparklayer.io/help/storefront/interfaces.md) your B2B customers use to place orders, manage their account and more. ### Installing the script **Shopify:** On Shopify, the **SparkLayer app embed** loads the Core Script for you, so there's no need to edit `theme.liquid` or other theme files. To enable it: 1. Go to the [Theme setup](https://admin.shopify.com/apps/sparklayer/theme-setup) page in the SparkLayer Shopify app 2. Select your theme from the **Theme** dropdown 3. Click **Enable app embed** - this opens your Shopify theme editor in a new tab 4. In the theme editor, click **Save** in the top right to activate the app embed The Core Script now loads on your storefront. You can verify it's working by checking the **App Blocks** table on the same page, where the **App Embed** row should show as Enabled. ![Installing the script (Shopify) – Frontend Integration Guide](https://docs.sparklayer.io/images/developers/frontend/frontend-0cfafd.webp) If you'd prefer not to install SparkLayer yourself, our team can do this for you free of charge. On the Theme setup page, click **Install SparkLayer for me** to request a free install. **Other platforms:** Add the SparkLayer Core Script to the `...` of your website's theme so it loads on every page. On BigCommerce, this is typically in `/templates/layout/base.html`. ![Installing the script (Other platforms) – Frontend Integration Guide](https://docs.sparklayer.io/images/shared/frontend-61c96b.webp) Your customised Core Script is in the SparkLayer Dashboard at **Storefront > Widgets** in the SparkLayer Dashboard ([open](https://app.sparklayer.io/configuration/frontend/core)), or **SparkLayer Wholesale > Storefront > Widgets** in the Shopify app. ```html title="Core Script" ``` A new installation uses the latest version of the Core Script. You can change the version later (see [Upgrading to the latest version](#upgrading-to-the-latest-version)), and [What's new](https://docs.sparklayer.io/help/whats-new.md) lists the changes in each release. ### Technical details The Core Script gives SparkLayer the information that controls how it works on your website. ```html title="Core Script with options" ``` This information includes: | Item | Details | | --- | --- | | `siteId` | The website's unique identifier (see [your account and team](https://docs.sparklayer.io/help/dashboard/account.md)) | | `platform` | The eCommerce platform of the website | | `language` | The language to be used | | `accountRedirect` | Where to send logged-in customers who visit a matching URL (`urlRegex`), and the page to send them to (`goTo`) | | `display` | Which display configurations to enable (see [Storefront](https://docs.sparklayer.io/help/storefront.md)). Since Core Script 2.0, stock display is set per customer group in the Dashboard instead (see [stock display](https://docs.sparklayer.io/help/storefront/stock-display.md)) | | `auth` | The authentication details of the logged in customer | ### Modifying the core script The Core Script's options turn specific features on or off and customise how they work. **Shopify:** On Shopify, the app embed loads the Core Script for you. To add customisations or configurations, add a small ` ``` The `...window.sparkOptions` line keeps any options your theme or another snippet has already set; without it, the new object replaces them. For example, to set the language and send logged-in customers who visit an account page to your collections page: ```html title="theme.liquid" ``` The `window.sparkOptions` block must come **before** the SparkLayer app embed loads, so place it in the `` of your theme, not the `` or footer. **Other platforms:** On other platforms, `window.sparkOptions` is part of the Core Script you added to your theme's ``. Change the options in that block: see [Technical details](#technical-details) for an example and what each option does, and the [Core Script options](https://docs.sparklayer.io/developers/javascript-sdk/reference/options.md) for every option. For every option, see the [Core Script options](https://docs.sparklayer.io/developers/javascript-sdk/reference/options.md). For the settings merchants manage in the Dashboard, see [Storefront](https://docs.sparklayer.io/help/storefront.md). ### Upgrading to the latest version To upgrade to the latest version of the Core Script, go to **Storefront > Widgets** in the SparkLayer Dashboard ([open](https://app.sparklayer.io/configuration/frontend/core)), or **SparkLayer Wholesale > Storefront > Widgets** in the Shopify app in the SparkLayer Dashboard. **Step 1.** In the **Upgrade Core Script** section, select the latest numeric version, then click **Save**. **Step 2.** Update your website so it loads the matching Core Script. If you're not already on version 2, your Core Script looks something like this: ```html title="Version 1 Core Script" ``` Replace it with the version 2 format: ```html title="Version 2 Core Script" ``` | Item | Details | | --- | --- | | `` | Your unique Site ID, shown in the SparkLayer Dashboard at **Account** ([open in the SparkLayer Dashboard](https://app.sparklayer.io/settings/account)) | | `` | The SparkLayer environment: `live`, or `test` if you're using [test mode](https://docs.sparklayer.io/help/dashboard/test-mode.md) | For example, for a store with Site ID `b2bstore` on the `live` environment: ```html title="Version 2 Core Script" ``` ## Product page widgets The product page widgets are the parts of SparkLayer B2B customers see on your product and collection pages when they log in. There are three: **Step 1.** **[Product detail](https://docs.sparklayer.io/help/storefront/interfaces/product-detail.md)** Shown on your product detail pages, it lets customers quickly add products to an order in a table view. ![Product page widgets – Frontend Integration Guide](https://docs.sparklayer.io/images/developers/frontend/frontend-f499d1.webp) **Step 2.** **[Product card](https://docs.sparklayer.io/help/storefront/interfaces/product-card.md)** Shown wherever a product card appears, such as a collection page or a product upsell area. ![Product page widgets – Frontend Integration Guide](https://docs.sparklayer.io/images/developers/frontend/frontend-535a99.webp) **Step 3.** **[Product price](https://docs.sparklayer.io/help/storefront/interfaces/product-price.md)** Shows a product's B2B price anywhere on your website, such as a product detail page or collection page. ![Product page widgets – Frontend Integration Guide](https://docs.sparklayer.io/images/developers/frontend/frontend-b67dc7.webp) Adding them takes two steps: **Step 1.** **Hide the retail elements** If you're installing SparkLayer on your existing retail website, **hide** the elements B2B customers shouldn't see, such as your retail pricing, add to cart button, variant options and quantity selector. SparkLayer has an attribute you can add to any HTML element to do this (see [Hiding elements](#hiding-elements)). **Step 2.** **Add the widgets** Add the SparkLayer code snippets to your product pages, such as the product detail page and collection page. When a B2B customer logs in, the interfaces show automatically, so they can see prices, add items to an order, view their account and check out. See [Storefront widgets](https://docs.sparklayer.io/help/storefront/interfaces.md) for each interface and how to customise it. ### Hiding elements SparkLayer can hide any element on your website from B2B customers with a `data-spark` attribute. Elements you would typically hide include: - Price information - Product variant information - Quantity selection - Buy button ![Hiding elements – Frontend Integration Guide](https://docs.sparklayer.io/images/developers/frontend/frontend-25ee14.webp) You need access to your website's source code to find the files that contain the elements you want to hide, such as: - A product detail page - A product collection page - Specific "components" that are used in multiple locations on your website In those files, add `data-spark="b2c-only"` to each element you want to hide. SparkLayer then applies `display: none` to the element when a logged-in B2B customer views the page. This step changes your website's code, so you may want a developer to help. For platform-specific guidance, see [Integrations](https://docs.sparklayer.io/help/integrations.md). ```html title="Hiding elements from B2B customers"

{{ product.price }}

``` ### Displaying the SparkLayer interfaces Next, add the SparkLayer [frontend interfaces](https://docs.sparklayer.io/help/storefront/interfaces.md) to your pages. Each interface's page explains how to add its code snippet. | Interface | Details | | --- | --- | | [Product detail](https://docs.sparklayer.io/help/storefront/interfaces/product-detail.md) | Shown on your product detail pages; lets customers quickly add products to an order. | | [Product card](https://docs.sparklayer.io/help/storefront/interfaces/product-card.md) | Shown wherever a product card appears, such as a collection page. | | [Product price](https://docs.sparklayer.io/help/storefront/interfaces/product-price.md) | Shows a product's B2B price anywhere on your website, instead of or alongside the product card. | ## Design customisations (CSS) SparkLayer's interface is neutral by default, and you can change nearly every aspect of its look and feel with CSS variables, including: - Colours - Typography (e.g. typefaces and font sizing) - Spacing (e.g. padding and margin) - Button styling - Form styling Add the CSS to your existing stylesheets, or to your website's `...` as you did with the Core Script. ![Design customisations (CSS) – Frontend Integration Guide](https://docs.sparklayer.io/images/shared/frontend-e2b840.webp) ### What can be styled To find the styles you want to change, use your browser's **Inspect element** tool. SparkLayer's styles use CSS variables prefixed with `--spark-`, and you can override each one. In the inspector, you'll see something like the example below. To change a style, such as a button colour, set the matching variable in your website's CSS. See [Customising the design](https://docs.sparklayer.io/help/storefront/customising-design.md) for details. ```css title="SparkLayer button styles" .btn, .btn-large, .btn-small { color: var(--spark-button-raised-color,var(--spark-lightest-color,#fff)); background-color: var(--spark-button-raised-background,var(--spark-secondary-color,#125ef8)); border: var(--spark-button-border,none); border-radius: var(--spark-button-radius,var(--spark-border-radius-button,4px)); padding: var(--spark-button-padding,.875em 1.75em); text-transform: var(--spark-button-text-transform,none); letter-spacing: var(--spark-button-text-letter-spacing,0); font-weight: var(--spark-button-font-weight,400); font-family: var(--spark-button-font-family,Poppins,sans-serif); } ``` ## Frontend integration checklist Before you launch, check you've covered everything: - **Core Script added.** You've added the SparkLayer Core Script to your website's ``. On Shopify, you've enabled the SparkLayer app embed. - **Product detail widget added.** You've added the SparkLayer product detail widget to your product detail pages, and hidden content B2B customers shouldn't see, such as your regular direct-to-consumer (DTC) pricing, add to cart buttons, "sticky" content that references pricing, and non-B2B content. - **Product card widget added.** You've added the SparkLayer product card widget to your product cards (typically on collection and category pages), and hidden DTC pricing, add to cart buttons, "quick buy" buttons and non-B2B content. - **Every product card checked.** You've checked all areas of your website that show product cards, such as the homepage and product recommendations. - **CSS and design.** You've added the SparkLayer CSS to your website (in the header or a CSS file) and styled it to match your branding. - **Cart and cart drawer.** Clicking the cart icon in your website header opens the My Cart interface. If it doesn't, make sure the cart link points to `/cart` and doesn't trigger your theme's JavaScript for B2B customers. - **General site audit.** You've checked features such as site search and wish lists, so DTC prices are hidden (in CSS or in the code). ## Next steps - [Storefront widgets](https://docs.sparklayer.io/help/storefront/interfaces.md): add and customise each interface. - [Core Script options](https://docs.sparklayer.io/developers/javascript-sdk/reference/options.md): every `sparkOptions` setting. - [Technical information](https://docs.sparklayer.io/developers/frontend/technical-information.md): browser support and analytics event tracking. - [JavaScript SDK](https://docs.sparklayer.io/developers/javascript-sdk.md): build custom or headless interfaces. --- # Technical information URL: https://docs.sparklayer.io/developers/frontend/technical-information How the SparkLayer Frontend loads asynchronously, supported browsers and devices, and how to track B2B events in GA4 or Google Tag Manager via sparkOptions. SparkLayer is designed to be as lightweight as possible and uses modern JavaScript technology to power the frontend. ## How SparkLayer is loaded SparkLayer is fully hosted, so there's no code for you to host or maintain. For performance, the SparkLayer Frontend **only** loads when a customer logs in to your website. The interfaces load asynchronously, so even though SparkLayer is a hosted third-party solution, the impact on page load times is negligible. ## Browser support SparkLayer supports the latest two versions of these browsers: - Google Chrome - Mozilla Firefox - Apple Safari - Microsoft Edge - Apple Safari for iOS - Google Chrome for Android and iOS SparkLayer may not work fully with beta or pre-release versions of these browsers. ## Mobile support The [frontend interfaces](https://docs.sparklayer.io/help/storefront/interfaces.md) work on desktop, tablet and mobile, so customers can browse your website on any device. ## Building custom interfaces To build more custom layouts on top of the SparkLayer Frontend, see the [JavaScript SDK](https://docs.sparklayer.io/developers/javascript-sdk.md). ## Event tracking for analytics (Google Analytics, GA4) SparkLayer can send B2B events, such as add to cart, begin checkout and purchase, to Google Analytics (GA4), Google Tag Manager (GTM) or your own analytics code. You set it up with the `analytics` option in `sparkOptions`, so you'll need a developer to add it to your [Core Script](https://docs.sparklayer.io/developers/frontend.md#modifying-the-core-script). ### Enabling Google Analytics or GTM Add an `analytics` object with a list of `providers`. Each provider has a `handler` (which analytics tool to send to) and the `events` to send. This example sends every event to GA4, GTM and a custom function: ```javascript title="sparkOptions" window.sparkOptions = { ...window.sparkOptions, analytics: { providers: [ { handler: 'ga', // Google Analytics (GA4) events: { addToCart: true, cartUpdate: true, shoppingListSave: true, shoppingListLoad: true, shoppingListDelete: true, csvUpload: true, quickAdd: true, finalStageCheckout: true, shippingUpdate: true, beginCheckout: true, purchase: true, viewCart: true, }, }, { handler: 'gtm', // Google Tag Manager events: { addToCart: true, cartUpdate: true, shoppingListSave: true, shoppingListLoad: true, shoppingListDelete: true, csvUpload: true, quickAdd: true, finalStageCheckout: true, shippingUpdate: true, beginCheckout: true, purchase: true, viewCart: true, }, }, { handler: function (eventName, eventParam) { // Your own analytics code }, events: { addToCart: true, purchase: true, }, }, ], }, }; ``` The `...window.sparkOptions` line keeps the options you've already set; without it, this object replaces them. ### Provider options | Provider type | Description | | --- | --- | | `'ga'` | Integrates with Google Analytics (GA4). This works best with [Shopify's GA4 integration](https://help.shopify.com/en/manual/reports-and-analytics/google-analytics/google-analytics-setup). | | `'gtm'` | Integrates with Google Tag Manager. | | function | A custom analytics handler, for other analytics platforms or your own tracking logic. See [Custom analytics provider](#custom-analytics-provider). | ### Available events | Event name | Description | | --- | --- | | `addToCart` | Triggered when a product is added to the cart. | | `cartUpdate` | Fired when the cart is updated (for example, quantity changes or items removed). | | `shoppingListSave` | Occurs when a shopping list is saved. | | `shoppingListLoad` | Occurs when a shopping list is loaded. | | `shoppingListDelete` | Occurs when a shopping list is deleted. | | `csvUpload` | Triggered when a CSV file is uploaded. | | `quickAdd` | Fired during quick add actions. | | `finalStageCheckout` | Occurs at the final stage of the checkout process. | | `shippingUpdate` | Fired when shipping details are updated during checkout. | | `beginCheckout` | Triggered when the checkout process begins. | | `purchase` | Occurs when a purchase is completed. | | `viewCart` | Fired when the cart is viewed. | ### Custom analytics provider To send events to an analytics service SparkLayer doesn't support natively, set `handler` to a function. It receives the event name and its parameters, so you can add any tracking logic: ```javascript title="Custom analytics provider" { handler: function (eventName, eventParam) { // Example: send the event to your own analytics endpoint fetch('https://your-analytics-endpoint.com/track', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ event: eventName, parameters: eventParam, }), }); }, events: { addToCart: true, cartUpdate: true, // ... other events ... }, } ``` ## Next steps - [Frontend integration guide](https://docs.sparklayer.io/developers/frontend.md): install the Core Script and product page widgets. - [Core Script options](https://docs.sparklayer.io/developers/javascript-sdk/reference/options.md): every `sparkOptions` setting. - [Set up the JavaScript SDK](https://docs.sparklayer.io/developers/javascript-sdk/setup.md): customise the frontend with code. --- # JavaScript SDK URL: https://docs.sparklayer.io/developers/javascript-sdk What the SparkLayer JavaScript SDK is, what you can build with it, how it loads with the Core Script, and a first example that fetches a customer's price. The JavaScript SDK lets your own code work with SparkLayer on your storefront. Your theme can read the same data SparkLayer's own screens use, such as a customer's prices and cart, take the same actions, such as adding to the cart, and run code at key moments, such as just before checkout. In code, the SDK is the `window.spark` object, which SparkLayer's [Core Script](https://docs.sparklayer.io/help/glossary.md#core-script) adds to every page. ## What you can build - **Custom pricing displays**: show a customer's price, RRP or price breaks anywhere in your theme. See [Products and pricing](https://docs.sparklayer.io/developers/javascript-sdk/products-and-pricing.md). - **Your own add to cart**: add products, quantities and custom attributes (including files) to the cart. See [Cart](https://docs.sparklayer.io/developers/javascript-sdk/cart.md). - **Checkout rules**: block checkout with your own validation, such as a product quota checked against your API. See [Checkout](https://docs.sparklayer.io/developers/javascript-sdk/checkout.md). - **Customer and sales agent tools**: check who's logged in, switch the customer a sales agent orders for, or query their data. See [Customers and accounts](https://docs.sparklayer.io/developers/javascript-sdk/customers-and-accounts.md). - **Theme integration**: swap variant images, open the cart drawer or send events to your analytics. See [Events and analytics](https://docs.sparklayer.io/developers/javascript-sdk/events-and-analytics.md). - **Headless storefronts**: load and initialise SparkLayer yourself in a custom frontend. See [Headless and React](https://docs.sparklayer.io/developers/javascript-sdk/headless.md). ### Choose the most stable way to customise You can build around SparkLayer's own components, and change them too. Prefer the ways that keep working when SparkLayer updates: 1. An **option** or **hook** in the SDK. 2. A [CSS variable](https://docs.sparklayer.io/help/storefront/customising-design.md) for the look, or a [custom slot](https://docs.sparklayer.io/help/storefront/interfaces/custom-slots.md) to add your own content. 3. Only if neither does the job, change a component's HTML. Keep the change small, and check it after each SparkLayer update. Planning something bigger? [Talk to our team](https://docs.sparklayer.io/help/support.md) first. ## Build it with AI Most SDK customisations are a few dozen lines of code, which suits an AI assistant well. Describe what you want, and the prompt below gives your assistant the SDK reference and the rules to follow. Review the code it writes, and test it on an unpublished theme or a test store before it goes live. For work with the SparkLayer APIs, see [Build with AI](https://docs.sparklayer.io/developers/build-with-ai.md). ## How the SDK loads The SDK comes with the Core Script, so there's nothing extra to install. On Shopify, the SparkLayer app embed loads it. On other platforms, you add the Core Script to your theme's `` from the SparkLayer Dashboard. Two rules apply to all SDK code: - **Set your options first.** You configure the SDK with `window.sparkOptions`, which must be set **before** the Core Script loads. - **Wait until SparkLayer is ready.** `window.spark` only works once SparkLayer has started, so run your code from the `onReady` hook. [Set up the SDK](https://docs.sparklayer.io/developers/javascript-sdk/setup.md) covers both in detail. ## Try it: show a customer's price Add this to your theme's ``, before the Core Script. When SparkLayer is ready, it checks whether a customer is logged in and, if they are, writes their price for one product to the browser console: ```html title="theme.liquid or your theme's " ``` Open a product page as a logged-in B2B customer and check the browser console. The `...window.sparkOptions` line keeps the options already set by the Core Script or other snippets: see [Add your own options and hooks](https://docs.sparklayer.io/developers/javascript-sdk/setup.md#add-your-own-options-and-hooks). If one of your hooks throws an error, SparkLayer shows a system error with a code such as `OPTIONS:HOOK:OR`. See [Hook error codes](https://docs.sparklayer.io/developers/javascript-sdk/reference/hooks.md#hook-error-codes). ## Next steps - [Set up the SDK](https://docs.sparklayer.io/developers/javascript-sdk/setup.md): Load the Core Script, add your own options and hooks, and wait for SparkLayer to be ready. - [Products and pricing](https://docs.sparklayer.io/developers/javascript-sdk/products-and-pricing.md): Show trade prices, live quantity pricing and the right image for each variant. - [Cart](https://docs.sparklayer.io/developers/javascript-sdk/cart.md): Add and remove items, set cart fields, attach attributes and files, and open the drawer. - [Checkout](https://docs.sparklayer.io/developers/javascript-sdk/checkout.md): Block checkout until your own rules are met. - [Customers and accounts](https://docs.sparklayer.io/developers/javascript-sdk/customers-and-accounts.md): Check the login, work as a sales agent, switch company sections and run GraphQL queries. - [Events and analytics](https://docs.sparklayer.io/developers/javascript-sdk/events-and-analytics.md): Hooks, DOM events and sending B2B events to Google Analytics or your own tools. - [Headless and React](https://docs.sparklayer.io/developers/javascript-sdk/headless.md): Load and initialise SparkLayer yourself, with Next.js and React examples. - [SDK reference](https://docs.sparklayer.io/developers/javascript-sdk/reference.md): Every sparkOptions setting, hook, window\.spark method, type, DOM event and web component. --- # Set up the SDK URL: https://docs.sparklayer.io/developers/javascript-sdk/setup Load the SparkLayer Core Script, add your own sparkOptions and hooks without overwriting others, and wait for SparkLayer to be ready before calling it. Every SDK customisation starts here: how the SDK loads with the Core Script, how to add your own options and hooks without overwriting anyone else's, and how to wait until SparkLayer is ready. It uses `window.sparkOptions`, the `onReady` hook and, on headless storefronts, `window.initSpark()`. ## How the SDK loads The SDK comes with the SparkLayer Core Script. On Shopify, the SparkLayer app embed loads it. On other platforms, the Core Script you copy from the SparkLayer Dashboard goes in your theme's ``: ```html title="Core Script" ``` Replace `` with your Site ID, and use `test` instead of `live` in [test mode](https://docs.sparklayer.io/help/dashboard/test-mode.md). The version this URL serves is set in the Dashboard: see [Upgrading to the latest version](https://docs.sparklayer.io/developers/frontend.md#upgrading-to-the-latest-version). For the rest of the storefront setup, see the [frontend integration guide](https://docs.sparklayer.io/developers/frontend.md#the-sparklayer-core-script). Older themes may load a version 1 file such as `https://cdn.sparklayer.io/spark.1.0.21.js`. These URLs are legacy: replace them with the loader above. Don't use `spark.latest.js` either: it's a legacy bundle that lacks most of the SDK. When the Core Script runs, it reads `window.sparkOptions`, starts SparkLayer and sets `window.spark`. Your options and hooks must therefore be in place **before** the Core Script loads. ## Add your own options and hooks Set your options in a ` ``` `window.sparkOptions = { … }` on its own replaces the whole object, so it wipes anything set earlier: the `siteId` and `auth` in the Core Script on other platforms, or another snippet's hooks. The `...window.sparkOptions` line copies those across first. Every example in these docs extends the existing options, either with this spread or by adding to the object directly. The spread keeps other settings, but a hook with the same name still replaces the earlier one. If two snippets need the same hook, combine them into one function, or keep a reference to the earlier hook and call it from yours, as the [chained checkout validation](https://docs.sparklayer.io/developers/javascript-sdk/checkout.md#combine-your-rule-with-existing-validation) example does. For every option and hook, see [Options](https://docs.sparklayer.io/developers/javascript-sdk/reference/options.md) and [Hooks](https://docs.sparklayer.io/developers/javascript-sdk/reference/hooks.md) in the SDK reference. ## Wait for SparkLayer to be ready `window.spark` exists as soon as the Core Script runs, but its data isn't loaded until SparkLayer has started. Run your code from the `onReady` hook, which receives the `Spark` object once SparkLayer has initialised and the page's DOM has loaded. It runs whether or not the customer is logged in, so check with `isLoggedIn()` first: ```javascript title="sparkOptions" window.sparkOptions = { ...window.sparkOptions, onReady: async (spark) => { if (!(await spark.isLoggedIn())) { return; } // Safe to call spark methods here }, }; ``` If your code can run after the Core Script has loaded (for example, from a script at the end of the page or in a separate app), use the `window.sparkReady` promise below instead. It resolves whether SparkLayer started before or after your code. ### Wait for SparkLayer from any script Add this once, in your theme's `` **before** the SparkLayer Core Script (on Shopify, before the SparkLayer app embed). It resolves `window.sparkReady` with `window.spark` when SparkLayer calls its `onReady` hook, and keeps any `onReady` you already have. ```html title="Theme , before the SparkLayer script" ``` Then, anywhere after it: ```javascript title="Any script" const spark = await window.sparkReady; ``` Like the spread above, the snippet adds to `window.sparkOptions` instead of replacing it. Many examples on the other SDK pages start with `await window.sparkReady`, so add this snippet first. ## Manual initialisation (headless only) On a headless storefront you may want to decide when SparkLayer starts, for example after your app has loaded the customer. If `window.sparkOptions` isn't set when the Core Script runs, SparkLayer doesn't start. Instead it defines `window.initSpark(options)`, which you call yourself: ```html title="Headless storefront" ``` Pass every option, including hooks, to `initSpark`. Don't set `window.sparkOptions` anywhere on the page (including with the `...window.sparkOptions` pattern), or SparkLayer starts automatically and `initSpark` isn't defined. That rules out the [`window.sparkReady` snippet](#wait-for-sparklayer) too, because it sets `window.sparkOptions`. Pass an `onReady` hook to `initSpark` instead, and resolve your own promise from it. See [Headless and React](https://docs.sparklayer.io/developers/javascript-sdk/headless.md) for a full example. ## Next steps - [Products and pricing](https://docs.sparklayer.io/developers/javascript-sdk/products-and-pricing.md): Show a customer's price, RRP and price breaks in your own theme code. - [Cart](https://docs.sparklayer.io/developers/javascript-sdk/cart.md): Add, update and remove items, set cart fields and handle the results of updateCart. - [SDK reference](https://docs.sparklayer.io/developers/javascript-sdk/reference.md): Every sparkOptions setting, hook, method and type. --- # Products and pricing URL: https://docs.sparklayer.io/developers/javascript-sdk/products-and-pricing Show each B2B customer's own price, RRP and price breaks in your theme, price a quantity as it changes, and swap the product image when a variant is selected. Show B2B prices in your own theme code, work out the price for a quantity, and keep your product images in step with SparkLayer's variant picker. This page uses the pricing methods below and the `spark-variant-change` event. | Method | Use it to | | --- | --- | | [`getPricingForVariant()`](https://docs.sparklayer.io/developers/javascript-sdk/reference/methods.md#getpricingforvariant) | Get a variant's price, RRP and price breaks for the logged-in customer. | | [`getPriceForProduct()`](https://docs.sparklayer.io/developers/javascript-sdk/reference/methods.md#getpriceforproduct) | Get a product's "from" price across its variants, and how many prices there are. | | [`calculatePricingForVariant()`](https://docs.sparklayer.io/developers/javascript-sdk/reference/methods.md#calculatepricingforvariant) | Price a quantity of a variant, including price breaks. | | [`calculatePricingForVariantData()`](https://docs.sparklayer.io/developers/javascript-sdk/reference/methods.md#calculatepricingforvariantdata) | Price a quantity from variant data you already have, without a request. | | [`getPackSizeForVariant()`](https://docs.sparklayer.io/developers/javascript-sdk/reference/methods.md#getpacksizeforvariant) | Get a variant's pack size (`1` if none is set). | | [`getRrpPriceForVariant()`](https://docs.sparklayer.io/developers/javascript-sdk/reference/methods.md#getrrppriceforvariant) | Get a variant's RRP on its own. | Each method takes your platform's parent product ID and the variant SKU. Prices are for the logged-in customer, so check `isLoggedIn()` first. Some examples use `window.sparkReady`: add the [Wait for SparkLayer](https://docs.sparklayer.io/developers/javascript-sdk/setup.md#wait-for-sparklayer) snippet before them. ## Calculate the price for a quantity This example calculates the price of two units of a variant once SparkLayer is ready: ```javascript title="sparkOptions" window.sparkOptions = { ...window.sparkOptions, onReady: async (spark) => { if (!(await spark.isLoggedIn())) { return; } // Parent product ID, variant SKU and quantity const pricing = await spark.calculatePricingForVariant('6660191387839', 'MUG-MOM', 2); console.log(pricing.totalPrice, pricing.unitPrice, pricing.currencyCode); }, }; ``` `totalPrice` is the quantity × the unit price at that quantity, after any price breaks. See [`ProductVariantPricing`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#productvariantpricing) for every field. ## Show trade prices on your own product tiles Your theme's collection tiles show retail prices. This swaps in each signed-in B2B customer's own price, with the RRP, and leaves retail visitors' prices alone. Give each tile its product ID and SKU: ```html title="Product tile (Shopify Liquid)"
{{ product.price | money }}
``` ```javascript title="Trade prices on tiles" const money = (amount, currency) => new Intl.NumberFormat(document.documentElement.lang || 'en-GB', { style: 'currency', currency, }).format(amount); async function showTradePrices(root = document) { const spark = await window.sparkReady; if (!(await spark.isLoggedIn())) return; // retail visitors keep your normal prices const tiles = root.querySelectorAll('[data-product-id][data-sku]'); await Promise.all( [...tiles].map(async (tile) => { try { const { price, rrp, currencyCode } = await spark.getPricingForVariant( tile.dataset.productId, tile.dataset.sku, ); if (price === null) return; // no B2B price for this customer tile.querySelector('.tile-price').textContent = money(price, currencyCode); if (rrp) tile.querySelector('.tile-rrp').textContent = `RRP ${money(rrp, currencyCode)}`; } catch { // the product isn't available to this customer: leave the tile as it is } }), ); } showTradePrices(); ``` Call `showTradePrices(container)` again after your theme loads more products, for example on infinite scroll. ## Read the RRP metafield in Liquid On Shopify, you can also render the RRP on the server, straight from the variant's `sparklayer.rrp` metafield. The metafield is JSON on the variant, with one entry per currency and decimal values (not subunits), for example `[{"value":9.00,"currency_code":"gbp"}]`. This snippet picks the entry for the shopper's currency, converts it to subunits for Shopify's `money` filter, and only shows it when it differs from the variant's Shopify price: ```liquid title="Product template (Shopify Liquid)" {%- if customer.metafields.sparklayer.authentication -%} {%- assign variant = product.selected_or_first_available_variant -%} {%- for entry in variant.metafields.sparklayer.rrp.value -%} {%- assign entry_currency = entry.currency_code | upcase -%} {%- if entry_currency == cart.currency.iso_code -%} {%- assign rrp_subunits = entry.value | times: 100.0 | round -%} {%- if rrp_subunits != variant.price -%} RRP {{ rrp_subunits | money }} {%- endif -%} {%- break -%} {%- endif -%} {%- endfor -%} {%- endif -%} ``` - Liquid is rendered once, on the server. The RRP doesn't change when the customer picks another variant until the page reloads. For an RRP that follows the selected variant, use [`getRrpPriceForVariant()`](https://docs.sparklayer.io/developers/javascript-sdk/reference/methods.md#getrrppriceforvariant) or the `` [component](https://docs.sparklayer.io/developers/javascript-sdk/reference/events-and-components.md#web-components). - Shopify caches pages, so a change to the metafield can take a little while to show. ## Live price as the quantity changes On a product page with your own quantity box, show the unit price, line total and price-break saving as the customer types, snapped to the product's pack size. It fetches the variant once, then works out each price instantly with `calculatePricingForVariantData`, so there's no request per keystroke. ```html title="Product page"

``` ```javascript title="Live quantity pricing" const qtyInput = document.querySelector('#b2b-qty'); const output = document.querySelector('#b2b-line-price'); const { productId, sku } = qtyInput.dataset; const money = (amount, currency) => new Intl.NumberFormat(document.documentElement.lang || 'en-GB', { style: 'currency', currency, }).format(amount); const spark = await window.sparkReady; const [variant, packSize] = await Promise.all([ spark.getVariant(productId, sku), spark.getPackSizeForVariant(productId, sku), ]); qtyInput.step = String(packSize); qtyInput.min = String(packSize); function render() { // round up to a whole number of packs const qty = Math.max(packSize, Math.ceil((Number(qtyInput.value) || 1) / packSize) * packSize); const pricing = spark.calculatePricingForVariantData(variant, qty); if (pricing.unitPrice === null) { output.textContent = 'Price on request'; return; } const saving = pricing.priceBreakSavingsPercentage ? ` (you save ${pricing.priceBreakSavingsPercentage}%)` : ''; output.textContent = `${qty} × ${money(pricing.unitPrice, pricing.currencyCode)} = ${money( pricing.totalPrice, pricing.currencyCode, )}${saving}`; } qtyInput.addEventListener('input', render); qtyInput.addEventListener('change', () => { qtyInput.value = String(Math.ceil((Number(qtyInput.value) || 1) / packSize) * packSize); render(); }); render(); ``` Load this as a module (` ``` ```javascript title="sparkOptions" window.sparkOptions = { ...window.sparkOptions, preCartUpdateListener: async (input) => { if (!input.products?.length) { return input; } const filesJson = document.querySelector('#shirt-design')?.value; // <-- read back the hidden input from above if (!filesJson) { return input; } const gids = JSON.parse(filesJson).map((file) => file.gid); const customShirt = input.products.find((p) => p.sku === 'SHIRT-CUSTOM'); // <-- match your own custom-line-item SKU if (!customShirt) { return input; } customShirt.customAttributes = [ ...(customShirt.customAttributes ?? []), { key: 'Front Design', value: JSON.stringify(gids) }, // <-- key is shown to the customer/staff as the attachment's label ]; return input; }, }; ``` ### Tag order lines with a campaign Record which campaign brought a buyer in, as a custom attribute on every line they add, so it shows on the order. It reads `utm_campaign` from the landing page URL and keeps it for the visit. It also calls any `preCartUpdateListener` you already have, so it works alongside the examples above. ```html title="Theme , before the SparkLayer script" ``` ## Open the cart drawer `openDrawer()` opens the SparkLayer drawer, on the Cart tab unless you pass another [tab](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#draweruistate). To open it each time the cart changes, call it from the `onCartUpdate` hook. This example also switches a drawer that's already open to the Cart tab: ```javascript title="sparkOptions" window.sparkOptions = { ...window.sparkOptions, onCartUpdate: async (cart, results) => { // Opens the drawer if it isn't already open window.spark.openDrawer('cart'); // Switches an open drawer to the Cart tab window.dispatchEvent( new CustomEvent('view-update', { bubbles: true, composed: true, detail: { view: 'cart' }, }), ); }, }; ``` ## Handle the result and show feedback ### What updateCart returns `updateCart` resolves to an array of [`CartResults`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#cartresults), one for each product line you sent, or `null` if the request failed. It doesn't throw: on failure it shows an error toast and logs the error to the console. Each result has: - `requestedQuantity` and `resultantQuantity`: what you asked for and what was applied. They differ when one of the `messages` below applies, such as rounding to a pack size or limiting to the stock available. - `itemKey`, `sku` and `name`: which line the result is for. - `messages`: a list of `{ type }` objects for anything that happened to the line. `type` is one of: | Type | Meaning | | --- | --- | | `success` | The line was added or updated with no issues. | | `pack-size` | The quantity was rounded down to the nearest pack size. | | `qty-excessive` | The quantity was lowered to the allowed maximum. | | `qty-insufficient` | The quantity was raised to the allowed minimum. | | `qty-unavailable` | The quantity was lowered to what's in stock. | | `qty-unavailable-no-clamp` | The line was added, but the requested quantity isn't fully available. Check the cart. | | `excessive-lines` | The cart's line limit was reached, so the item wasn't added. | | `not-found` | The SKU doesn't exist. | | `unavailable` | The product can't be bought at the moment. | | `product-settings-overridden` | The product's default settings were overridden. | These are status codes, not display text. `updateCart` uses them to show its own toasts (see below). If you show your own messages, switch on `type`. ### Show your own feedback By default, `updateCart` shows toast messages. Pass `true` as the second argument (`cartSuccessHandledLocally`) to turn off the success toast, or as the third (`cartErrorsHandledLocally`) to turn off toasts for the `messages` in the results: ```javascript // Suppress the built-in success toast, but keep automatic error toasts await window.spark.updateCart( { products: [{ sku: 'RED-SHIRT-M', adjustQuantity: 1 }] }, true, // cartSuccessHandledLocally ); ``` ## Add to cart from your own button Add a product to the B2B cart from any button, rounded up to its pack size, then open the cart drawer. SparkLayer shows its own messages if the quantity is changed, for example to match the stock available. This uses `window.sparkReady` from [Wait for SparkLayer](https://docs.sparklayer.io/developers/javascript-sdk/setup.md#wait-for-sparklayer). ```html title="Any page" ``` ```javascript title="Add to cart" document.addEventListener('click', async (event) => { const button = event.target.closest('[data-add-to-trade-order]'); if (!button) return; const spark = await window.sparkReady; const { productId, sku } = button.dataset; const packSize = await spark.getPackSizeForVariant(productId, sku); const quantity = Math.ceil(Number(button.dataset.qty || 1) / packSize) * packSize; button.disabled = true; try { const results = await spark.updateCart({ products: [{ sku, adjustQuantity: quantity }] }); if (results) spark.openDrawer('cart'); // null means SparkLayer already showed an error } finally { button.disabled = false; } }); ``` The click listener sits on `document`, so it also works for buttons your theme adds later. ## Quick order from a pasted list Let buyers paste a list of SKUs and quantities, one per line, such as `MUG-BLUE 24` or `MUG-BLUE,24`, and add everything in one update. Duplicate SKUs are added together. ```html title="Quick order form"

``` ```javascript title="Quick order" document.querySelector('#quick-order').addEventListener('submit', async (event) => { event.preventDefault(); const text = document.querySelector('#quick-order-lines').value; const result = document.querySelector('#quick-order-result'); const totals = new Map(); const skipped = []; for (const line of text.split('\n').map((l) => l.trim()).filter(Boolean)) { const [sku, qty] = line.split(/[\s,;\t]+/); const quantity = Number.parseInt(qty ?? '1', 10); if (!sku || !Number.isFinite(quantity) || quantity < 1) { skipped.push(line); continue; } totals.set(sku, (totals.get(sku) ?? 0) + quantity); } if (!totals.size) { result.textContent = 'Add at least one SKU and quantity.'; return; } const spark = await window.sparkReady; const products = [...totals].map(([sku, adjustQuantity]) => ({ sku, adjustQuantity })); const results = await spark.updateCart({ products }); if (!results) return; // SparkLayer showed the error const added = results.filter((r) => r.resultantQuantity > 0).length; result.textContent = `Added ${added} of ${products.length} products.${ skipped.length ? ` Couldn't read: ${skipped.join(', ')}.` : '' }`; spark.openDrawer('cart'); }); ``` ## Clear the cart after an order If customers complete orders outside SparkLayer's checkout and your order confirmation page loads the Core Script, clear their B2B cart there: ```javascript title="Order confirmation page" const spark = await window.sparkReady; await spark.externalClearBasket(); ``` ## Next steps - [Checkout](https://docs.sparklayer.io/developers/javascript-sdk/checkout.md): Check the cart against your own rules before the customer can check out. - [Products and pricing](https://docs.sparklayer.io/developers/javascript-sdk/products-and-pricing.md): Show trade prices and live quantity pricing in your theme. - [Cart types](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#cart): The UpdateCart, CartItemInput, CartResults and Cart types in full. --- # Checkout URL: https://docs.sparklayer.io/developers/javascript-sdk/checkout Block SparkLayer checkout until your own rules are met with the onCheckoutValidation hook, from a simple product quota check to rules chained together. Add your own rules to SparkLayer's checkout, for rules SparkLayer's own settings don't cover. This page uses the `onCheckoutValidation` hook, with examples that call your own API and that combine several rules. ## Add custom checkout validation `onCheckoutValidation` lets you check the cart against your own rules, or your own API, before the customer can check out. For example, you could check whether a customer has already used up a one-off discount or a product quota, or limit how many free samples go in one order. The hook must be an `async` function. It receives the [`Cart`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#cart) and the current checkout step (a [`CartUIState`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#cartuistate), so your rules can vary by step), and returns `null` if the cart is valid or an array of `{ message }` objects if it isn't. SparkLayer shows each `message` to the customer and won't let them check out until the hook returns `null`. The hook runs every time the cart loads or changes, so keep it quick: avoid network requests unless your rule needs them, and keep any you make fast. ### Check the cart against your API This example sends the cart to your own endpoint and blocks checkout if it says the cart isn't valid: ```javascript title="sparkOptions" window.sparkOptions = { ...window.sparkOptions, onCheckoutValidation: async (cart, cartUiState) => { const response = await fetch('https://example.com/cart-validation', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(cart), }); const { valid } = await response.json(); if (valid) { return null; } return [{ message: 'This order is over your quota for one of these products.' }]; }, }; ``` Decide what should happen if your API is unreachable. Here, a failed request rejects the promise: SparkLayer logs the error to the console and doesn't apply a result. ### Combine your rule with existing validation Setting `onCheckoutValidation` replaces any hook another snippet has set. To add a rule without losing one that's already there, keep a reference to the earlier hook, call it, and add your messages to its list. This example limits free samples to three per order: ```html title="Theme , before the SparkLayer script" ``` ## Collect extra details at checkout To ask customers for a PO number, a requested delivery date or your own fields at checkout, turn on [checkout fields](https://docs.sparklayer.io/help/ordering/checkout-fields.md) in the Help Center, or set them in code with the [`checkoutCustomElements`](https://docs.sparklayer.io/developers/javascript-sdk/reference/options.md#checkout-and-account) option. To fill these values in from your own code, see [Cart-level fields](https://docs.sparklayer.io/developers/javascript-sdk/cart.md#cart-level-fields). ## Next steps - [Cart](https://docs.sparklayer.io/developers/javascript-sdk/cart.md): Add and remove items, set cart-level fields and handle the results of updateCart. - [Checkout fields](https://docs.sparklayer.io/help/ordering/checkout-fields.md): Turn on standard checkout fields and add your own. - [onCheckoutValidation](https://docs.sparklayer.io/developers/javascript-sdk/reference/hooks.md#oncheckoutvalidation): The hook's signature and return value, with links to the Cart and CartUIState types. --- # Customers and accounts URL: https://docs.sparklayer.io/developers/javascript-sdk/customers-and-accounts Check whether a B2B customer is logged in, switch customers as a sales agent, change company section, refresh customer data and run GraphQL queries. Work with the logged-in customer from your own code: check the login, act for a customer as a sales agent, switch company section, refresh their data and query the GraphQL API. This page uses `isLoggedIn`, `switchImpersonatingCustomer`, `setActiveCompanySection`, `refreshGlobalData`, `getActiveCustomerVerificationToken` and `fetch`, plus the `spark-redirect.js` login redirect. The examples use `window.sparkReady`: add the [Wait for SparkLayer](https://docs.sparklayer.io/developers/javascript-sdk/setup.md#wait-for-sparklayer) snippet before them. For every method's parameters and return values, see [Customers and session](https://docs.sparklayer.io/developers/javascript-sdk/reference/methods.md#customers-and-session) in the SDK methods. ## Check whether the customer is logged in `isLoggedIn()` resolves to `true` when the customer is logged in to SparkLayer. The `onReady` hook runs for every visitor, so check it before you show B2B content or call pricing and cart methods: ```javascript title="Any script" const spark = await window.sparkReady; if (await spark.isLoggedIn()) { // Show B2B content } ``` ## Switch the customer a sales agent orders for A [sales agent](https://docs.sparklayer.io/help/sales-reps.md) can order on behalf of their customers. `switchImpersonatingCustomer()` changes the customer they're acting for, and `null` ends impersonation. Both resolve to the [`UserData`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#userdata) for the active customer, or `null`. ```javascript title="Any script" const spark = await window.sparkReady; // Act for a customer const customer = await spark.switchImpersonatingCustomer(''); // End impersonation await spark.switchImpersonatingCustomer(null); ``` While a sales agent is impersonating, pass `true` as the `useImpersonatingCustomer` argument of [`fetch()`](#run-a-graphql-query) to run a query as the impersonated customer rather than the sales agent. ## Select a company section If a customer's company is split into sections, `setActiveCompanySection()` selects the one they're buying for. If the cart isn't empty, the customer is asked to confirm and the cart is cleared, then the page reloads. It resolves to `false` if the section isn't available to the customer, they cancel, or the cart couldn't be cleared. ```javascript title="Any script" const spark = await window.sparkReady; const user = await spark.refreshGlobalData(); const section = user?.companySections[0]; if (section && !(await spark.setActiveCompanySection(section.id))) { // The section wasn't changed } ``` ## Refresh customer and site data SparkLayer caches the active customer and the site configuration. If they change while the page is open, for example after your own code updates the customer through the API, call `refreshGlobalData()` to fetch them again. It resolves to the active customer's [`UserData`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#userdata), or `null`. ```javascript title="Any script" const spark = await window.sparkReady; const user = await spark.refreshGlobalData(); ``` ## Get a customer verification token `getActiveCustomerVerificationToken()` refetches the active customer and resolves to a fresh form customer verification token, or `null`. When a sales agent is impersonating, the token is for the impersonated customer; otherwise it's for the logged-in customer. ```javascript title="Any script" const spark = await window.sparkReady; const token = await spark.getActiveCustomerVerificationToken(); ``` ## Run a GraphQL query `spark.fetch` runs a GraphQL query as the signed-in customer. This one reads their email address: ```javascript title="GraphQL query" const spark = await window.sparkReady; const response = await spark.fetch(`query { loggedInCustomer { id email } }`); if (response.status === 200) { const { data, errors } = await response.json(); if (!errors?.length) console.log(data.loggedInCustomer.email); } ``` `fetch` returns a standard `Response`: check `status`, then call `json()` for the `data` and `errors`. See [fetch()](https://docs.sparklayer.io/developers/javascript-sdk/reference/methods.md#fetch) for its other arguments. ## Redirect to the login page `spark-redirect.js` sends customers who aren't logged in to your login page when they follow a link that needs SparkLayer, such as the **View order** button in some emails. This lets SparkLayer open what the link was for once they've logged in. Add it to your theme in addition to the Core Script. On Shopify, put it outside the `{%- if customer.metafields.sparklayer.authentication -%}` block, so it loads for customers who aren't logged in: ```html title="theme.liquid" ``` By default, customers are sent to `/account/login`. To use another path, set `window.sparkRedirectLoginPath` before the script: ```html title="theme.liquid" ``` ## Next steps - [Events and analytics](https://docs.sparklayer.io/developers/javascript-sdk/events-and-analytics.md): Run your code when the customer logs out, the cart changes or SparkLayer is ready. - [Sales rep setup](https://docs.sparklayer.io/help/sales-reps.md): Add sales agents and choose which customers they can order for. - [Customer methods](https://docs.sparklayer.io/developers/javascript-sdk/reference/methods.md#customers-and-session): Every customer and session method, and the UserData type. --- # Events and analytics URL: https://docs.sparklayer.io/developers/javascript-sdk/events-and-analytics Run your own code from SparkLayer's hooks and DOM events, send B2B events to Google Analytics or Tag Manager, and push cart changes to any analytics tool. Run your own code at key moments in SparkLayer, react to what customers do in its interfaces, and send B2B events to your analytics. This page covers the `onLoad`, `onReady`, `onCartLoad`, `onCartUpdate` and `onLogout` hooks, SparkLayer's DOM events and web components, and the `analytics` option. ## Hooks Hooks are `async` functions you set on `window.sparkOptions` before the Core Script loads (see [Add your own options and hooks](https://docs.sparklayer.io/developers/javascript-sdk/setup.md#add-your-own-options-and-hooks)). SparkLayer calls them at these points: | Hook | When it runs | Receives | | --- | --- | --- | | [`onLoad`](https://docs.sparklayer.io/developers/javascript-sdk/reference/hooks.md#onload) | Once while SparkLayer initialises, after translations have loaded and before `onReady`, whether or not the customer is logged in. | The `Spark` object | | [`onReady`](https://docs.sparklayer.io/developers/javascript-sdk/reference/hooks.md#onready) | When SparkLayer has initialised and the page's DOM has loaded. The place to start most of your code. | The `Spark` object | | [`onCartLoad`](https://docs.sparklayer.io/developers/javascript-sdk/reference/hooks.md#oncartload) | When the cart is loaded. | The `Cart` | | [`onCartUpdate`](https://docs.sparklayer.io/developers/javascript-sdk/reference/hooks.md#oncartupdate) | Each time the cart is updated. | The updated `Cart` and the `CartResults` for each change | | [`onLogout`](https://docs.sparklayer.io/developers/javascript-sdk/reference/hooks.md#onlogout) | When the customer logs out. | Nothing | Two more hooks change what SparkLayer does rather than react to it: `preCartUpdateListener` edits an update before SparkLayer's add to cart sends it (see [Cart](https://docs.sparklayer.io/developers/javascript-sdk/cart.md#add-attributes-to-sparklayers-add-to-cart)), and `onCheckoutValidation` blocks checkout (see [Checkout](https://docs.sparklayer.io/developers/javascript-sdk/checkout.md)). If `onReady`, `onCartLoad` or `onCartUpdate` throws an error, SparkLayer shows a system error with a code that names the hook. See [Hook error codes](https://docs.sparklayer.io/developers/javascript-sdk/reference/hooks.md#hook-error-codes). ## DOM events and web components SparkLayer's interfaces are web components, such as `` and ``, that you add to your theme. Some of them dispatch DOM events you can listen for: - `spark-variant-change`: the customer selects a variant in `spark-pdp` or `spark-product-card`. It doesn't bubble, so listen on each element. See [Update the product image when a variant is selected](https://docs.sparklayer.io/developers/javascript-sdk/products-and-pricing.md#update-the-product-image-when-a-variant-is-selected). - `spark-file-attachment-changed` and `spark-file-attachment-failed`: a file is added to, removed from or fails to upload to a `spark-file-upload-field`. See [Let customers upload a file](https://docs.sparklayer.io/developers/javascript-sdk/cart.md#let-customers-upload-a-file). - `view-update`: you dispatch this one on `window` to switch an open drawer to a tab. See [Open the cart drawer](https://docs.sparklayer.io/developers/javascript-sdk/cart.md#open-the-cart-drawer). For every web component and event, with the shape of each `event.detail`, see [Events and components](https://docs.sparklayer.io/developers/javascript-sdk/reference/events-and-components.md) in the SDK reference. ## Google Analytics SparkLayer can send B2B events, such as add to cart, begin checkout and purchase, to Google Analytics (GA4), Google Tag Manager or your own function. You turn this on with the `analytics` option in `sparkOptions`, listing a `handler` and the `events` to send for each provider. For the configuration, the list of events and a custom handler example, see [Event tracking for analytics](https://docs.sparklayer.io/developers/frontend/technical-information.md#event-tracking-for-analytics-google-analytics-ga4). ## Send cart changes to your analytics For tools other than [Google Analytics](#google-analytics), push cart changes to the data layer from the `onCartUpdate` hook. This snippet keeps any `onCartUpdate` you already have: ```html title="Theme , before the SparkLayer script" ``` ## Next steps - [Event tracking for analytics](https://docs.sparklayer.io/developers/frontend/technical-information.md#event-tracking-for-analytics-google-analytics-ga4): Turn on GA4 or Google Tag Manager, and see every event SparkLayer sends. - [Cart](https://docs.sparklayer.io/developers/javascript-sdk/cart.md): Change the cart from your own code and open the cart drawer. - [Hooks](https://docs.sparklayer.io/developers/javascript-sdk/reference/hooks.md): When each hook runs, what it receives and returns, with an example of each. --- # Headless and React URL: https://docs.sparklayer.io/developers/javascript-sdk/headless Run SparkLayer on a headless storefront: load the Core Script, start it with initSpark, authenticate the customer, and use it from Next.js and React. On a headless storefront, your own frontend renders the pages, so it also loads SparkLayer: it fetches the logged-in customer, loads the Core Script and starts SparkLayer with `window.initSpark()`. This page walks through a headless Shopify storefront built with Next.js, using `window.initSpark()` and the `onReady` and `onLogout` options, then shows a React hook for calling `window.spark` from your components. SparkLayer still needs your platform's data (products, customers and orders) to stay in sync. On Shopify, the SparkLayer app does this. For another platform or a custom backend, [SparkLayer Ignite](https://docs.sparklayer.io/ignite.md) connects it to SparkLayer. ## Requirements - **A Shopify store** with the [SparkLayer app](https://apps.shopify.com/sparklayer) installed. - **A metafield definition** for the SparkLayer authentication customer metafield (`sparklayer.authentication`). - **Storefront access** turned on for that metafield definition, so your frontend can read it. SparkLayer stores each customer's authentication data in this metafield. Your frontend passes it to SparkLayer, which uses it to authenticate the customer with the SparkLayer API. ## How it works 1. Get the logged-in customer's data. 2. Check that they're a B2B customer (in this example, tagged `b2b`) and have the `sparklayer.authentication` metafield set. If not, don't load SparkLayer. 3. Load the Core Script by adding a script tag: ``. 4. Once the script has loaded, call `window.initSpark({ … })` with your options, such as `siteId`, `platform` and `auth`. See [Options](https://docs.sparklayer.io/developers/javascript-sdk/reference/options.md). 5. Add a `