# Event names Source: https://docs.bily.ai/api-reference/events Choose consistent names for Bily events and your own application events. `BilyEvent` autocompletes known events and still accepts your own event names. ```typescript theme={null} type BilyEvent = KnownBilyEvent | (string & Record); ``` Event names are case-sensitive. Use the capitalization shown for Bily events. Use stable snake\_case names for application events. ## Use known Bily events ```typescript theme={null} type KnownBilyEvent = | "AddContactInfo" | "AddShippingInfo" | "Checkout Address Info Submitted" | "Contact" | "New Customer Purchase" | "PageView" | "Pageview" | "Product Removed From Cart" | "Product Removed From Wishlist" | "ViewCategory" | "Product List Viewed" | "Products Searched" | "Product Clicked" | "Product Added" | "Product Added to Wishlist" | "Product Removed" | "Product Viewed" | "Checkout Step Viewed" | "Checkout Step Completed" | "Shipping Info Entered" | "Order Refunded" | "Clicked Promotion" | "Viewed Promotion" | "Category Viewed" | "Checkout Started" | "Payment Info Entered" | "Payment Add Contact Info" | "Payment Add Shipping Info" | "Payment Add Address Info" | "Subscription" | "New Customer Order" | "New Customer Subscription" | "Order Completed" | "Cart Viewed" | "Remove from Cart" | "Cart Updated" | "Order Updated" | "Order Cancelled" | "Lead" | "Login" | "Logout" | "Search" | "Add to Wishlist" | "Remove from Wishlist" | "View Wishlist" | "Newsletter" | "Contact Form Submitted" | "Subscription Cancelled" | "Subscription Updated" | "Subscription Renewed"; ``` ## Use current ecommerce names For new ecommerce tracking, use the current names in the [ecommerce guide](/guides/ecommerce). The SDK still accepts these compatibility names and converts them before delivery: | Compatibility name | Current name | | --------------------------- | --------------------------------- | | `Pageview` | `PageView` | | `Add to Wishlist` | `Product Added to Wishlist` | | `Category Viewed` | `ViewCategory` | | `Contact Form Submitted` | `Contact` | | `New Customer Order` | `New Customer Purchase` | | `Payment Add Address Info` | `Checkout Address Info Submitted` | | `Payment Add Contact Info` | `AddContactInfo` | | `Payment Add Shipping Info` | `AddShippingInfo` | | `Product Added To Wishlist` | `Product Added to Wishlist` | | `Remove from Cart` | `Product Removed From Cart` | | `Remove from Wishlist` | `Product Removed From Wishlist` | | `Search` | `Products Searched` | ## Add application events ```typescript theme={null} track("workspace_created", { workspace_id: "workspace_456", }); ``` Every custom name must contain at least one non-whitespace character. Keep a short internal event catalog so every component uses the same spelling. Create stable names and safe properties for your application. # getBilyTrackingId() Source: https://docs.bily.ai/api-reference/get-bily-tracking-id Get or create the stable Bily identifier for the current browser. Use `getBilyTrackingId()` to read the identifier Bily uses for the current browser. The SDK creates one when needed. ## Get the identifier ```typescript theme={null} function getBilyTrackingId(): string | null; ``` ## Correlate it safely ```typescript theme={null} import { getBilyTrackingId } from "@bilyai/js"; const trackingId = getBilyTrackingId(); if (trackingId) { sendSafeCorrelationId(trackingId); } ``` ## Keep it stable in the browser When browser storage is available, the SDK stores a stable identifier there. If storage is blocked, the SDK keeps an in-memory identifier for the page session. It can also use an existing Bily identifier when one is available. Call this method before or after `init()`. ## Handle `null` on the server During server rendering, the method returns `null`. Always handle the nullable return type. ```typescript theme={null} const trackingId: string | null = getBilyTrackingId(); ``` ## Keep authorization separate The browser tracking ID is not a password, session token, or authenticated account ID. Never use it to authorize a request. Authorize requests with your own verified session. Use `client.userId` only for approved account context. Separate browser identity, authenticated users, and consent responsibilities. # init() Source: https://docs.bily.ai/api-reference/init Start Bily with an exact tracking URL and control how it loads. Call `init()` once to load Bily in a browser document. Use the object form to preserve the exact tracking URL Bily provides. ## Choose a signature ```typescript theme={null} function init(options: BilyInitOptions): void; function init( trackingDomainOrScriptUrl: string, options?: BilyLegacyInitOptions, ): void; ``` ## Start with the exact URL ```typescript theme={null} import { init } from "@bilyai/js"; init({ scriptUrl: "https://tracking.example.com/b.js?shop=store.example.com", }); ``` The SDK places `scriptUrl` on the browser script without removing or rebuilding its query string. Copy the complete URL from **Bily > Settings > Apps > More settings > Install tracking**. Keep every query parameter and encoded character. ## Control loading | Option | Type | Default | Behavior | | ---------------- | --------- | ------------------------------ | ---------------------------------------------------------------- | | `scriptUrl` | `string` | Required in the preferred form | Uses the exact Bily tracking URL | | `debug` | `boolean` | `false` | Logs lifecycle diagnostics, never event payloads | | `maxQueueSize` | `number` | `100` | Keeps 1 to 1,000 early calls and drops the oldest when full | | `loadTimeoutMs` | `number` | `15000` | Waits 1,000 to 60,000 milliseconds before rejecting readiness | | `nonce` | `string` | — | Adds your content-security-policy nonce to the injected script | | `trackingDomain` | `string` | — | Compatibility option that loads `/b.js` from the supplied origin | | `verbose` | `boolean` | `false` | Deprecated alias for `debug` | `scriptUrl` and `trackingDomain` are mutually exclusive in `BilyInitOptions`. ## Configure every option ```typescript theme={null} init({ scriptUrl: "https://tracking.example.com/b.js?shop=store.example.com", debug: false, maxQueueSize: 250, loadTimeoutMs: 20_000, nonce: document.querySelector( 'meta[name="csp-nonce"]', )?.content, }); ``` ## Keep the URL valid * Use HTTPS for production tracking URLs. * Use HTTP only for local development on `localhost`, `127.0.0.1`, or `[::1]`. * Use the object form to preserve the exact tracking URL. * Prefer `scriptUrl` over the compatibility domain form, which builds a `/b.js` URL from the value you provide. ## Call it again safely Calling `init()` again with the same URL does not add another script. If the page already has a script with that URL, the SDK reuses it. After Bily starts loading or becomes ready, the SDK ignores a later call with a different URL. Keep one configuration source on each page. After a terminal load failure, call `init()` again to retry. ## Stay safe during server rendering During server rendering, `init()` returns without accessing browser globals. Call it from client-mounted code to start browser tracking. ## Keep an older integration working Existing applications can keep using the string overload while you migrate: ```typescript theme={null} init("https://tracking.example.com", { verbose: false }); ``` For new integrations, use `{ scriptUrl }` to keep the exact URL intact. # SDK overview Source: https://docs.bily.ai/api-reference/overview Find every public method and TypeScript type exported by @bilyai/js. `@bilyai/js` gives you one small browser SDK for loading Bily and sending events. It supports ESM, CommonJS, and TypeScript with no runtime dependencies. ## Install Bily ```bash theme={null} npm install @bilyai/js ``` Use Node.js 16 or newer for build tooling. You can call every public method during server rendering; tracking begins only when the code runs in a browser document. ## Choose a method | Export | Return type | Purpose | | ------------------------------------------------------------ | ---------------- | ------------------------------------------ | | [`init(options)`](/api-reference/init) | `void` | Load Bily from an exact tracking URL | | [`track(event, payload?)`](/api-reference/track) | `void` | Queue or send an event | | [`ready()`](/api-reference/ready) | `Promise` | Verify that Bily finished loading | | [`getBilyTrackingId()`](/api-reference/get-bily-tracking-id) | `string \| null` | Read or create the Bily browser identifier | ## Use exported types ```typescript theme={null} import type { BilyAddress, BilyClient, BilyEvent, BilyInitOptions, BilyLegacyInitOptions, BilyOrder, BilyPayload, BilyProduct, KnownBilyEvent, } from "@bilyai/js"; ``` See [event names](/api-reference/events) and [payload types](/api-reference/payload) for the complete definitions. ## Start now, verify when needed ```typescript analytics.ts theme={null} import { init, ready, track } from "@bilyai/js"; init({ scriptUrl: "https://tracking.example.com/b.js?shop=store.example.com", }); track("workspace_created", { workspace_id: "workspace_456" }); try { await ready(); } catch (error) { console.error("Bily failed to load", error); } ``` You can call `track()` before `ready()` resolves. The SDK keeps early calls in a bounded queue, then delivers them in order after Bily loads. Do not add a raw script tag alongside the SDK. `init()` loads the browser script for you. # Payload types Source: https://docs.bily.ai/api-reference/payload Build consistent event payloads with Bily client, product, and order types. Pass an optional `BilyPayload` to `track()`. Typed fields keep common data consistent, while additional top-level keys let you describe your application. ## Build a payload ```typescript theme={null} type BilyPayload = { client?: BilyClient; order?: BilyOrder; products?: BilyProduct[]; pageTitle?: string; userAgent?: string; ip?: string; referrer?: string; sourceUrl?: string; source?: "app" | "web"; category?: string; searchQuery?: string; eventAlias?: string; [key: string]: unknown; }; ``` The browser SDK sets `source` to `web` for you. For explicit SPA page views, send `sourceUrl` and `pageTitle`. For product searches, send `searchQuery`. Do not supply an IP address from browser code. ## Describe a client ```typescript theme={null} type BilyAddress = { city?: string; state?: string; zip?: string; country?: string; }; type BilyClient = { firstname?: string; lastname?: string; dateOfBirth?: string; email?: string; phone?: string; gender?: string; address?: BilyAddress; userId?: string; userGroup?: string; }; ``` Every client field is optional. Send only the customer data allowed by the current consent state. ## Describe a product ```typescript theme={null} type BilyProduct = { id: string | number; name: string; price: number; currency: string; quantity?: number; sku?: string; category?: string; brand?: string; image?: string; }; ``` When you omit `quantity`, it defaults to `1`. When you provide `image`, the SDK preserves it. ## Describe an order ```typescript theme={null} type BilyOrder = { id: string | number; products?: BilyProduct[]; currency?: string; total?: number; tax?: number; discount?: number; discountCode?: string; shippingFees?: number; }; ``` If `total` is absent, the SDK calculates it from each product's price and quantity. Zero remains valid for `total`, `tax`, `discount`, and `shippingFees`. When a payload contains `order`, place its products in `order.products`. Do not rely on the top-level `products` field for the same event. ## Add application properties Add your own top-level keys when an event needs application-specific properties. ```typescript theme={null} track("workspace_created", { workspace_id: "workspace_456", organization_id: "organization_789", plan: "growth", }); ``` The SDK creates `event_id` and `ts` when they are absent. Provide them yourself when you already have a stable event ID or timestamp. ```typescript theme={null} track("campaign_launched", { event_id: "campaign_launch_01J2M91S2P", ts: "2026-07-13T08:30:00.000Z", campaign_id: "campaign_123", }); ``` # ready() Source: https://docs.bily.ai/api-reference/ready Confirm that Bily finished loading when your application needs an explicit check. Use `ready()` when you need to confirm that Bily loaded. Normal event tracking does not wait for it because `track()` queues early calls. ## Check readiness ```typescript theme={null} function ready(): Promise; ``` ## Verify the installation In the browser, call `init()` before awaiting `ready()`. ```typescript theme={null} import { init, ready } from "@bilyai/js"; init({ scriptUrl: "https://tracking.example.com/b.js?shop=store.example.com", loadTimeoutMs: 15_000, }); try { await ready(); console.info("Bily is ready"); } catch (error) { console.error("Bily failed to load", error); } ``` ## Know when it resolves The promise resolves when Bily can accept queued events. During server rendering, it resolves immediately because there is no browser script to load. ## Handle a rejection The promise rejects when: * The browser reports a script load error. * Bily does not become ready before `loadTimeoutMs`. * The script loads but does not expose a usable Bily browser API. The default timeout is 15 seconds. Set a value from 1 to 60 seconds through `init()`. ## Retry one failed load After a terminal load failure, call `init()` again. Then call `ready()` for the new attempt. ```typescript theme={null} async function loadBily() { const options = { scriptUrl: "https://tracking.example.com/b.js?shop=store.example.com", } as const; init(options); try { await ready(); } catch { init(options); await ready(); } } ``` If production keeps failing, check the URL, network policy, and duplicate installation. Do not retry indefinitely. Resolve timeouts, script errors, and conflicting configuration. # track() Source: https://docs.bily.ai/api-reference/track Send a Bily event or your own application event with an optional payload. Use `track()` to record an outcome with a known Bily name or your own non-empty event name. ## Send an event ```typescript theme={null} function track(event: BilyEvent, payload?: BilyPayload): void; ``` ## Track a known outcome ```typescript theme={null} import { track } from "@bilyai/js"; track("Product Viewed", { products: [ { id: "product_123", name: "Everyday Tee", price: 39, currency: "USD", }, ], }); ``` ## Track an application outcome ```typescript theme={null} track("integration_connected", { integration_id: "integration_456", integration_type: "crm", }); ``` Use stable snake\_case names for application events. You still get autocomplete for known Bily names. ## Know what Bily adds When the SDK prepares a browser event, it adds: * A Bily browser tracking ID when one is available. * An ISO timestamp when `ts` is not already a string. * A generated event ID when `event_id` is empty or absent. * The browser source for the event. Provide `event_id` when the same outcome needs one identifier across systems. ## Track before loading finishes Call `track()` before `init()` or while Bily loads. The SDK keeps those calls in memory until loading succeeds. The queue holds 100 calls by default. Set a limit from 1 to 1,000 with `init({ maxQueueSize })`. When the queue is full, the SDK removes the oldest call before it adds the newest. ## Handle errors without interrupting the app * An empty or whitespace-only event name is ignored. * A browser delivery failure does not throw from `track()`. * With `debug: true`, Bily logs a warning for delivery failures without logging the payload. * During server rendering, `track()` is a no-op. ## Track only later page views The first `PageView` is automatic. Call `track("PageView", ...)` only after a later client-side route change. ```typescript theme={null} track("PageView", { sourceUrl: window.location.href, pageTitle: document.title, }); ``` # Bily Apps Source: https://docs.bily.ai/concepts/bily-apps Add reusable capabilities through the Bily website connection you already control. **Install capabilities, not scripts.** Bily Apps is the app store for the internet—and the installable capability layer of Bily. Bily connects and governs your website data. Bily Apps lets you activate reusable capabilities through that connection, without adding a separate script integration each time. ## Add an App with confidence Find the capability that matches your website's goal. See the data and actions the capability needs before you activate it. Add the settings the capability needs for your website. Activate the capability through your site's existing Bily connection. Review its status, results, and available diagnostics. Adopt a newer version or remove access without replacing your website's Bily integration. ## Know what each product does | Product | Role | | --------- | ------------------------------------------------------------------------------------------------------ | | Bily | Connects website, server, and AI workflows through one programmable customer data and activation layer | | Bily Apps | Adds installable capabilities through that governed Bily connection | The Bily SDK, API, and MCP are developer surfaces for Bily. Bily Apps are capabilities you install through the layer. The developer surfaces are not Apps. ## Keep one website connection Bily Apps can support analytics, advertising, customer data, and automation while your website integration stays the same. Add or evolve a capability without giving every site owner another loader or release process to maintain. Install the Bily SDK and verify your event contract. Discover supported operations through MCP. # Bily glossary Source: https://docs.bily.ai/concepts/glossary Clear definitions for Bily products, events, identity, analytics, API scope, and MCP operations. ## Products and surfaces The programmable customer data and activation layer for websites. It keeps browser, server, and AI workflows aligned to the same website, organization, and store context. Bily's installable capability layer. You can review, configure, install, observe, update, or revoke an App through the website connection you already control. The `@bilyai/js` browser package. It installs the exact Bily tracking URL, queues early calls, sends known and custom events, and exports browser-safe TypeScript methods and types. A versioned HTTP interface for trusted server workflows. Authenticated endpoints expose Bily identity, stores, analytics, connected data, and supported actions. A Streamable HTTP interface for compatible AI clients. Its two stable tools, `search` and `execute`, expose current Bily operations. ## Events and payloads A named browser outcome with an optional payload. Examples include `PageView`, `Product Viewed`, `Order Completed`, and `workspace_created`. An event name in `KnownBilyEvent`. TypeScript autocompletes known names, while the SDK also accepts custom non-empty strings. An application-specific event name from your code. Use stable snake\_case names. Put changing values in the payload. The optional object passed to `track()`. It can include typed client, product, order, page, and search fields, plus reviewed custom properties. The identifier for one logical outcome. The SDK generates it when missing. It supports correlation, but it is not an ingestion receipt or an exactly-once guarantee. The event timestamp. The SDK generates an ISO timestamp unless you provide `ts` as a string. Bily's current page-view event name. The browser script records the initial document automatically. Single-page applications send it for each later committed route. A browser application that changes routes without loading a new document. Its router integration sends an explicit `PageView` for each later route change. A `BilyProduct` with required `id`, `name`, `price`, and `currency`, plus optional quantity, SKU, category, brand, and image fields. A `BilyOrder` for one order. It can include products, currency, total, tax, discount, discount code, and shipping fees. ## Identity and acquisition The browser-context identifier from `getBilyTrackingId()`. It can persist in browser storage, but it is not an authenticated account ID or authorization value. Your stable authenticated customer or account ID on an event. It provides context. It does not log anyone in or prove authorization. The current page URL you include with an explicit event, especially an SPA page view. An optional referring URL you include when your privacy policy permits it. The link between observed customer activity and acquisition or advertising context. Use Bily's returned analytics fields as the reporting contract. The SDK does not define a public first-touch or last-touch rule. ## Analytics and connected data Orders in Bily advertising analytics. Aggregate order volume with `sum(conversions)`. Unique browser visitors in Bily advertising analytics. Use `sum(people)` as the visitor denominator only when your selected rows contain the complete traffic population for that grouping. Total 30-minute visits. Use `sum(sessions)` as the session denominator only when your selected rows contain the complete traffic population for that grouping. Tracked page-view events. This is event volume, not unique visitors or sessions. Orders divided by a complete traffic denominator. Visitor CVR is `sum(conversions) / sum(people)`; session CVR is `sum(conversions) / sum(sessions)`. Never use orders, revenue, customers, purchase-only sessions, or conversion-filtered rows as the denominator. Revenue divided by spend when spend is greater than zero. Bily's normalized channel and asset reports use the report's revenue and store-currency-normalized spend. Raw platform revenue and spend remain separate where available. Spend divided by orders when orders are greater than zero. Bily reports use order volume—not customer count—for CPA. Spend divided by customers when the customer count is greater than zero. CAC differs from CPA whenever one customer places more than one order. A customer's total lifetime revenue in the `customer_ltv` dataset. Average LTV is the average customer `total_revenue`. Net LTV subtracts customer refunds where available. This is lifetime customer data, not period new-versus-returning revenue. Raw fields such as `p_spend` stay in the ad account's native `p_currency`. Group raw money aggregates by `p_currency`. For store-currency spend and efficiency across accounts, use normalized channel or asset reports. Product-order occurrences in the `product_metrics` dataset. Aggregate them with `sum(orders)`. Row count does not equal product-order count. Product quantity in the `product_metrics` dataset. Aggregate it with `sum(units)`. Units exceed product orders when an order contains more than one unit of a product. A store-scoped link to a supported data or activation surface. Depending on the operation, Bily can expose tracking connections, catalog sync connections, connected advertising accounts, and enabled account selections. The public API resource for a store-scoped tracking connection. Its record includes an ID, name, channel, enabled state, settings, timestamps, and optional publish state. An advertising account mapped to a Bily store. A store connection exposes connected accounts. Enabled accounts are selected for Bily reads or supported actions. A normalized campaign, ad set, or ad from an enabled ad account. Results identify the platform, account, asset type and ID, status, available budget fields, and fields Bily currently supports writing. A store's per-platform connector and account-selection status. It tells you whether to connect a source, select accounts, or wait before using the data. Analytics guidance with a code, severity, message, evidence, and recommended next step. Investigate `review_required` results and blocking warnings before they influence channel, budget, creative, or attribution decisions. ## Access and scope The Bily team boundary that owns access. API and MCP requests verify that the authenticated identity can access the organization. A connected commerce or web property within an organization. Store-scoped API paths use the exact URL returned by Bily discovery. The organizations, stores, permissions, and actions available to an authenticated identity or key. The authenticated Bily user, accessible organizations, and their stores. `GET /context` and `bily.context()` return this shape in one request, filtered to the connection's allowed scope. A server-side credential for the Bily API. A key created for a selected store carries that store and organization scope. Keep it in secret storage. Never expose it in browser code, prompts, logs, or repositories. The recommended MCP sign-in path for compatible clients. It uses Bily's OAuth flow and the signed-in user's current organization and store memberships. A one-time-revealed access key for MCP clients that cannot complete Bily Connect. A key created for a selected store gives MCP that default store and organization scope. A safe correlation value in the Bily API `x-request-id` response header. Save it when you diagnose an API failure. ## MCP operations The current set of Bily reads and actions discoverable through MCP `search`, including helper signatures, schemas, and planning guidance. The MCP tool that matches your intent to current Bily operations. It accepts `query`, optional `storeUrl`, and an optional limit from 1 to 20. The MCP tool that runs a focused async arrow function with `bily.*` helpers. It returns a JSON-serializable `result` and captured `logs`. Advisory MCP planning metadata, such as `read`, `draft_write`, `spend_affecting`, or `destructive`. Advisory MCP planning metadata for the recommended review before a write or spend-affecting action. The client approval prompt remains the visible approval boundary. # What is Bily? Source: https://docs.bily.ai/concepts/what-is-bily Connect customer data and activation across your browser, server, and AI workflows. Bily is the programmable customer data and activation layer for your website. Connect a site once to collect consistent events, route trusted data, and run governed browser, server, and AI workflows. Every surface stays aligned to the same website, organization, and customer data context. [Bily Apps](/concepts/bily-apps) adds reusable capabilities through that connection. You can expand what Bily does without managing a new script for every capability. ## What one connection gives you Collect consistent website events once, then share dependable context across browser, server, and AI workflows. Keep data within explicit website, organization, and store boundaries. Run supported reads and actions through authenticated surfaces with clear scope. Install and evolve reusable capabilities through the Bily connection you already maintain. ## Use the right surface for each job ```mermaid theme={null} flowchart LR A["Your website or app"] --> B["Bily SDK"] C["Your backend or workflow"] --> D["Bily API"] G["Your AI client"] --> H["Bily MCP"] B --> E["Bily data and activation layer"] D --> E H --> E E --> I["Bily Apps"] E --> F["Trusted data and actions"] I --> F ``` Use the [JavaScript SDK](/api-reference/overview) in the browser. It installs Bily once and sends page, product, checkout, and custom events. Use the [Bily API](/rest-api/overview) on the server. Your tools can read scoped analytics and store state, then perform supported actions with explicit authentication. Use [Bily MCP](/mcp/overview) in compatible AI clients. They can discover the current operation catalog and run scoped work with Bily authentication. All three connect to the same Bily layer. The API and MCP enforce identity, organization, and store access for authenticated server and AI work. ## Grow without rebuilding Bily gives your team one stable foundation for customer data, activation, safety, and observability. Add Bily Apps as your needs change, without rebuilding the website integration. You control: * which sites and stores are connected; * which Bily Apps are installed and enabled; * which organization can access each store; * which API keys can read data or perform actions; * when a Bily App or configuration reaches customers. Connect your website and send your first event. See what you can install through your Bily connection. Authenticate your server and discover its stores. Connect through Bily MCP and discover supported operations. # Choose the API or MCP Source: https://docs.bily.ai/guides/api-mcp-selection Use the Bily API for repeatable software. Use Bily MCP for guided work in compatible AI clients. The API and MCP can access overlapping Bily data and actions. The right choice depends on who controls the workflow and how repeatable it needs to be. ## Choose by workflow | Need | Use | | ---------------------------------------------------------------- | ----------------------- | | Send website events | Bily JavaScript SDK | | Build a backend integration with versioned endpoints | Bily API | | Run a scheduled or repeatable server job | Bily API | | Generate a typed client from OpenAPI | Bily API | | Let a compatible AI client discover a current operation | Bily MCP | | Perform an operator-reviewed analysis or action in an AI client | Bily MCP | | Combine browser collection, automation, and interactive analysis | SDK plus API and/or MCP | ## Use the API for repeatable software Choose the API when your application owns the sequence. Use it for: * scheduled analytics exports; * store and account synchronization jobs; * internal dashboards and reporting services; * explicit create, update, toggle, clone, or delete workflows; * integrations that need stable paths, request bodies, response schemas, and status codes. API requests use the versioned base URL and server-side authentication. Your application controls retries, idempotency decisions, verification, and errors. Review authentication, scope, errors, and every generated endpoint. ## Use MCP for guided AI workflows Choose MCP when a compatible AI client should interpret your intent and discover the current operation catalog. Bily MCP gives the client two stable tools: * `search` finds supported Bily operations and their planning guidance; * `execute` runs a focused async function with the selected `bily.*` helper. Use it for: * interactive questions about stores and analytics; * exploratory work where the exact helper should be discovered at runtime; * operator-reviewed actions where the client shows the complete execution before it runs; * AI workflows that benefit from operation schemas, risk guidance, and verification hints. MCP planning metadata is advisory. Keep the client approval visible. Verify every write with a read. Connect your AI client and review its two-tool contract. ## Combine surfaces when the workflow needs them A complete implementation can give each surface one clear job: ```mermaid theme={null} flowchart LR A["Website"] --> B["Bily SDK"] C["Backend job"] --> D["Bily API"] E["AI client"] --> F["Bily MCP"] B --> G["Bily data and activation layer"] D --> G F --> G ``` For example, the SDK sends product and order events. A backend job reads a daily analytics dataset through the API. An operator investigates the result through MCP. ## Keep each surface in its role * Do not place an API key in browser code. * Do not use MCP as the website event transport. * Do not use the browser SDK for scheduled server jobs. * Do not call undocumented Bily endpoints when an OpenAPI operation exists. * Do not hard-code MCP helper names without first using `search` for the current contract. ## Compare the contracts | Consideration | API | MCP | | ---------------------- | ------------------------------------- | --------------------------------------------- | | Contract discovery | OpenAPI and endpoint reference | `search` results | | Caller | Trusted server code | Compatible AI client | | Execution shape | One HTTP request per endpoint | Focused async function using Bily helpers | | Typical authorization | Server-side API key | Bily Connect or store-scoped fallback key | | Approval | Your application workflow | Client prompt plus advisory planning metadata | | Runtime responsibility | Your service | MCP execution limit and client timeout | | Best for | Repeatable deterministic integrations | Intent-driven operator workflows | If an MCP investigation becomes scheduled production logic, move its repeatable work to the versioned API. # Client data Source: https://docs.bily.ai/guides/client-data Add only the approved customer context each Bily event needs. Add `client` only when an event needs customer context. Its data applies to that event alone. ## Add verified customer context Send only the fields your privacy policy and the customer's consent allow. ```typescript theme={null} import { track } from "@bilyai/js"; track("user_signed_in", { client: { userId: "user_123", email: "buyer@example.com", firstname: "Ada", lastname: "Lovelace", userGroup: "growth", }, organization_id: "organization_456", }); ``` ## Choose the minimum fields | Field | Type | Purpose | | ------------- | ------------- | -------------------------------------------------------- | | `userId` | `string` | Your stable customer or account ID | | `email` | `string` | Customer email when permitted | | `phone` | `string` | Customer phone when permitted | | `firstname` | `string` | First name | | `lastname` | `string` | Last name | | `dateOfBirth` | `string` | Date of birth only when strictly necessary and permitted | | `gender` | `string` | Gender only when strictly necessary and permitted | | `userGroup` | `string` | Plan, segment, or account group | | `address` | `BilyAddress` | Approved city, state, postal code, and country fields | `BilyAddress` accepts `city`, `state`, `zip`, and `country`. Do not send customer data by default. Collect only what you need, and honor the visitor's consent and deletion choices. ## Read the browser identifier Use `getBilyTrackingId()` when you need Bily's stable identifier for the current browser context. ```typescript theme={null} import { getBilyTrackingId } from "@bilyai/js"; const trackingId = getBilyTrackingId(); if (trackingId) { console.info("Bily browser ID is available"); } ``` The return type is `string | null`. During server rendering, it returns `null`. It is not an authenticated user ID, credential, or authorization value. ## Keep each identity in its role * Use `client.userId` for your authenticated account identifier. * Use `getBilyTrackingId()` for the Bily browser identifier. * Never treat either value as proof that a request is authorized. * Do not place passwords, session tokens, or private keys in any event field. # Custom events Source: https://docs.bily.ai/guides/custom-events Track meaningful application outcomes with stable names and safe properties. Use custom events for meaningful application outcomes outside the ecommerce catalog. `track()` accepts known Bily event names and your own non-empty string names. ## Give every outcome one stable name Use snake\_case for application events: * `user_signed_in` * `workspace_created` * `integration_connected` * `contacts_imported` * `campaign_launched` * `billing_checkout_started` * `billing_checkout_completed` * `product_error_encountered` Keep IDs, timestamps, and translated labels out of event names. Put changing values in the payload. ## Track the result, not the attempt ```typescript workspace.ts theme={null} import { track } from "@bilyai/js"; const workspace = await createWorkspace({ name: "North America" }); track("workspace_created", { workspace_id: workspace.id, workspace_name: workspace.name, plan: "growth", }); ``` Send a completion event only after the operation succeeds. If a failure is useful and safe to analyze, give it a separate event. ```typescript campaign.ts theme={null} import { track } from "@bilyai/js"; try { const campaign = await launchCampaign(); track("campaign_launched", { campaign_id: campaign.id }); } catch (error) { track("product_error_encountered", { operation: "campaign_launch", error_code: getSafeErrorCode(error), }); throw error; } ``` Never include raw error objects. They can expose request data, stack traces, or customer content. ## Correlate one logical outcome The SDK creates `event_id` and `ts` when you omit them. Provide a stable event ID when more than one system can report the same business outcome. ```typescript theme={null} track("billing_checkout_completed", { event_id: "checkout_session_01J2M8YQ7B", checkout_id: "checkout_123", plan: "growth", }); ``` Use one `event_id` for one logical outcome. Never reuse it for a later outcome. ## Track while Bily loads Call `track()` immediately after `init()` if needed. The SDK keeps calls while Bily loads and delivers them in order when ready. # Ecommerce events Source: https://docs.bily.ai/guides/ecommerce Track the journey from product discovery to completed order with typed ecommerce payloads. Use Bily's title-case event names for product and order activity. The SDK recognizes them and maps typed products and orders to the Bily event contract. ## Record a product view ```typescript theme={null} import { track } from "@bilyai/js"; track("Product Viewed", { products: [ { id: "product_123", name: "Everyday Tee", price: 39, currency: "USD", quantity: 1, sku: "TEE-NAVY-M", category: "Tops", brand: "Everyday", image: "https://cdn.example.com/products/everyday-tee.jpg", }, ], }); ``` `quantity` defaults to `1` when omitted. ## Record an add to cart ```typescript theme={null} track("Product Added", { products: [ { id: "product_123", name: "Everyday Tee", price: 39, currency: "USD", quantity: 2, sku: "TEE-NAVY-M", }, ], }); ``` ## Record the start of checkout ```typescript theme={null} track("Checkout Started", { products: [ { id: "product_123", name: "Everyday Tee", price: 39, currency: "USD", quantity: 2, }, ], checkout_id: "checkout_456", }); ``` ## Record a completed order Use `order` when an event represents one order. If you provide `order.total`, the SDK uses it. Otherwise, it calculates the total from product price and quantity. ```typescript theme={null} track("Order Completed", { order: { id: "order_789", currency: "USD", total: 73, tax: 4, discount: 10, discountCode: "WELCOME10", shippingFees: 1, products: [ { id: "product_123", name: "Everyday Tee", price: 39, currency: "USD", quantity: 2, }, ], }, }); ``` Totals, tax, discounts, and shipping fees can all be zero. ## Use an exact event name * `Product List Viewed` * `Products Searched` * `Product Clicked` * `Product Added` * `Product Added to Wishlist` * `Product Removed` * `Product Removed From Cart` * `Product Removed From Wishlist` * `Product Viewed` * `Cart Viewed` * `Checkout Started` * `Checkout Step Viewed` * `Checkout Step Completed` * `Payment Info Entered` * `Shipping Info Entered` * `AddShippingInfo` * `AddContactInfo` * `Checkout Address Info Submitted` * `Order Completed` * `Order Updated` * `Order Refunded` * `Order Cancelled` * `Clicked Promotion` * `Viewed Promotion` * `ViewCategory` Match the spelling and capitalization exactly. See [event names](/api-reference/events) for accepted legacy aliases. ## Include every required product field Every product needs `id`, `name`, `price`, and `currency`. Include `quantity`, `sku`, `category`, `brand`, and `image` when you have them. See every field in `BilyProduct` and `BilyOrder`. # Event and data model Source: https://docs.bily.ai/guides/event-data-model Create a stable, reviewable contract for every Bily event and property. You control two parts of every Bily browser event: a stable name and an optional payload. The JavaScript SDK prepares both for delivery and adds a small amount of browser context. ## Give every event a clear shape ```typescript theme={null} track("checkout_completed", { event_id: "checkout_01J2M8YQ7B", ts: "2026-07-13T08:30:00.000Z", checkout_id: "checkout_123", plan: "growth", }); ``` | Part | Purpose | | ----------------- | ------------------------------------------------------- | | Event name | Stable description of the outcome | | Typed fields | `client`, `products`, `order`, page, and search context | | Custom properties | Application-specific IDs, categories, and safe state | | `event_id` | Identifier for one logical outcome | | `ts` | ISO timestamp for the event | When `event_id` or `ts` is missing, the SDK creates it. A generated event ID helps with correlation. `track()` does not acknowledge ingestion or promise exactly-once delivery. ## Name outcomes consistently Use Bily's current title-case names for ecommerce events. Use stable snake\_case names for application events. Good application event names: * `workspace_created` * `integration_connected` * `billing_checkout_completed` * `product_error_encountered` Keep customer IDs, timestamps, translated labels, and UI positions out of event names. Put changing context in properties. Event names are case-sensitive. The SDK accepts documented compatibility aliases. New code should use the current names in the [event reference](/api-reference/events). ## Track an outcome only when it is true Send an event only after the state it names is true. ```typescript theme={null} const workspace = await createWorkspace(input); track("workspace_created", { workspace_id: workspace.id, plan: workspace.plan, }); ``` A button click does not prove completion when the operation can still fail. If you need to analyze failures, send a separate event with a small, safe error vocabulary. ## Keep commerce data typed Use `products` for product, cart, and checkout activity. Each product requires `id`, `name`, `price`, and `currency`. ```typescript theme={null} track("Product Added", { products: [ { id: "product_123", name: "Everyday Tee", price: 39, currency: "USD", quantity: 2, }, ], }); ``` Use `order` when an event represents one order. Put its products in `order.products`. ```typescript theme={null} track("Order Completed", { order: { id: "order_789", currency: "USD", total: 78, products: [ { id: "product_123", name: "Everyday Tee", price: 39, currency: "USD", quantity: 2, }, ], }, }); ``` When `order.total` is missing, the SDK calculates it from product price and quantity. Total, tax, discount, and shipping values can all be zero. ## Add only the customer context you need The optional `client` object applies only to the event you send. ```typescript theme={null} track("user_signed_in", { client: { userId: "user_123", userGroup: "growth", }, }); ``` Prefer a stable internal `userId` to extra contact fields. Add email, phone, address, date of birth, or gender only when you have a documented need and permission. ## Preserve page and acquisition context Use typed page fields when an explicit event needs that context: ```typescript theme={null} track("PageView", { sourceUrl: window.location.href, pageTitle: document.title, referrer: document.referrer, }); ``` `sourceUrl` and `referrer` are inputs. They do not define a public first-touch or last-touch attribution rule. Use Bily's returned analytics fields as the reporting contract. Do not infer undocumented identity merging or attribution behavior. ## Give each property one meaning and type Keep the meaning and type of every property stable. | Property | Recommended value | | -------- | ------------------------------------------------- | | IDs | Stable string or documented numeric ID | | Currency | Consistent currency code string | | Money | Finite number in the unit your event plan defines | | Flags | Boolean, not `"true"` or `"false"` | | Time | ISO timestamp string in `ts` | | Category | Small controlled vocabulary | Do not change a released property from a number to a formatted string. If its meaning changes, add a new, clearly named property. ## Prepare the contract for review For each event, record: * the exact name and owner; * the successful trigger; * required and optional fields; * field types and allowed values; * customer-data classification; * an example payload; * a test and expected result. See every typed SDK field. Prove the names, payloads, and firing behavior before you release. # Identity and attribution Source: https://docs.bily.ai/guides/identity-attribution Keep browser identity, customer context, and attribution in their proper roles. Bily accepts browser, customer, and acquisition context. Each value serves a different purpose. Define that purpose in your event plan. ## Identify the browser context `getBilyTrackingId()` returns the Bily identifier for the current browser context. ```typescript theme={null} import { getBilyTrackingId } from "@bilyai/js"; const trackingId = getBilyTrackingId(); ``` The SDK keeps this value stable in browser storage when available. If storage is blocked, it can keep an in-memory value stable for the page session. During server rendering, the method returns `null`. The browser tracking ID: * is not a login identity; * is not a secret; * does not authorize a request; * can change when browser storage is cleared or unavailable. Authorize protected work with your verified server session. ## Add authenticated customer context Attach your stable account identifier as `client.userId` only to events that need it. ```typescript theme={null} track("subscription_updated", { client: { userId: session.user.id, userGroup: session.user.plan, }, subscription_id: "subscription_123", }); ``` The public JavaScript SDK does not expose `identify()`, `alias()`, or `reset()`. `client.userId` is approved customer context on an event—not an identity-merge command. If the same person uses more than one browser or device, use the same stable internal user ID when your consent and data policy allow it. Never use email as authorization or assume that matching contact fields merge records. ## Handle sign-in transitions explicitly Use this sequence: 1. Track signed-out behavior without authenticated customer fields. 2. After your application verifies the login, include the stable internal user ID on later events that need it. 3. Stop attaching the previous account's context immediately after logout. 4. Test shared-device login and logout flows. The browser tracking ID may stay the same through this transition. It still represents the browser context—not the authenticated account. ## Preserve page and acquisition inputs These fields preserve where an event occurred and where the browser came from: | Field | Use | | ----------------- | ------------------------------------------------------------- | | `sourceUrl` | Current page URL for an explicit event | | `pageTitle` | Current page title | | `referrer` | Referring URL when your policy permits it | | `category` | Controlled event or content category | | Custom properties | Approved campaign IDs or channel labels from your application | ```typescript theme={null} track("PageView", { sourceUrl: window.location.href, pageTitle: document.title, referrer: document.referrer, campaign_id: "spring_launch", }); ``` Never place credentials, session values, or unreviewed query parameters in these fields. ## Rely on documented attribution Bily's public API and MCP expose normalized analytics and customer-journey reads where available. Treat the returned fields as the reporting contract. The JavaScript SDK contract does not define: * an automatic first-touch or last-touch rule; * cross-domain browser-ID sharing; * anonymous-to-known identity merging; * exactly-once delivery from an `event_id`. If a business decision needs one of these behaviors, document the requirement and verify the relevant Bily analytics output before you rely on it in production. ## Correlate one logical outcome The SDK generates `event_id` when it is missing. Provide a stable value when more than one system can observe the same logical outcome. ```typescript theme={null} track("billing_checkout_completed", { event_id: "checkout_session_01J2M8YQ7B", checkout_id: "checkout_123", }); ``` Use one event ID for one logical outcome. It supports correlation, but it is not an ingestion receipt. ## Confirm the identity model * Browser IDs and authenticated user IDs have separate names and purposes. * Protected actions always use your verified session or server credential. * Customer fields appear only on events that require them. * Login, logout, shared-device, storage-cleared, and consent-denied flows are tested. * Attribution rules come from verified analytics output, not assumptions about the SDK. # Plan a Bily implementation Source: https://docs.bily.ai/guides/implementation-planning Turn the outcomes you need into a focused event plan, clear ownership, and safe release gates. Start with a small, reviewed contract. Decide what you need to learn or operate, where each event becomes true, and who owns the result before you choose framework code. ## Start with the decision you need to make Write down the business decisions your implementation should support. For example: * which product journeys lead to checkout; * where customers leave a conversion flow; * which campaign and store results need regular review; * which operational actions may be performed through the API or MCP. Map each decision to a small set of observable outcomes. Availability alone is not a reason to track every render, click, or state change. ## Give every event an owner and proof Use one row for each event. Review the plan with the product code. | Field | Question to answer | | ------------------- | -------------------------------------------------------------------- | | Event name | What stable outcome occurred? | | Trigger | What exact successful state causes the event? | | Owner | Which team owns the event contract? | | Required properties | Which fields are necessary to interpret it? | | Customer data | Which approved identity fields, if any, are needed? | | Event ID | Can the same outcome be reported by more than one system? | | Verification | How will a tester prove the event fired once with the right payload? | For ecommerce activity, start with Bily's [recognized event names](/guides/ecommerce). For application outcomes, use stable snake\_case [custom event names](/guides/custom-events). ## Install Bily one way | Website architecture | Installation path | | ----------------------------------------- | ------------------------------------------------------------------ | | Plain HTML or a site-managed script field | The exact raw script copied from Bily | | Browser application with an npm build | `@bilyai/js` | | React single-page application | One root-level React integration | | Next.js App Router, Pages Router, or both | One shared client component mounted once in each active route tree | Choose either the raw script or the npm SDK for each website surface. Using both installs Bily twice and can duplicate the initial page view. ## Make one integration own page views The browser script records the initial document page view. If your application changes routes without loading a new document, send one explicit `PageView` after each later committed route. Choose one router integration to own those later page views. It should: 1. skip the initial route callback; 2. wait for the final committed URL; 3. include the current URL and page title; 4. ignore repeated notifications for the same URL. Read the [page-view guide](/guides/pageviews) before you add component-level tracking. ## Set identity and privacy boundaries Keep these three concepts separate: * the Bily browser tracking ID identifies a browser context; * `client.userId` carries your approved authenticated account ID on an event; * your server session authorizes protected product actions. The browser tracking ID never authorizes a request. Include customer fields only when they have a documented purpose and the visitor's consent allows them. Review every field: | Classification | Examples | Default action | | -------------- | ---------------------------------------------- | ---------------------------------------- | | Operational | page name, product ID, plan, safe status code | Include when needed | | Customer | user ID, email, phone, address | Include only with purpose and permission | | Secret | password, session token, API key, payment data | Never include | Continue with [identity and attribution](/guides/identity-attribution) and [privacy governance](/guides/privacy-governance). ## Give each developer surface one job The browser SDK sends website events. It does not replace a server API client. Use the versioned Bily API for repeatable backend integrations and scheduled work. Use Bily MCP when a compatible AI client should discover current operations through `search` and run focused work through `execute`. The [API or MCP selection guide](/guides/api-mcp-selection) helps you choose one surface—or combine them when the workflow needs both. ## Prepare each environment before you build For each environment, define: * which Bily store and tracking URL each environment uses; * where test events are allowed; * who can create or revoke server credentials; * which checks must pass before production; * how to disable the new tracking code without changing unrelated application behavior. Never initialize a placeholder URL and replace it later. Select the exact environment-specific tracking URL before the first `init()` call. ## Know when planning is complete Start implementation when: * every event has an owner, trigger, required fields, and verification step; * the team selected exactly one browser installation path; * initial and SPA page-view ownership is explicit; * optional customer fields have privacy approval; * API and MCP credentials remain outside browser code; * staging and production stores, URLs, and release gates are documented. Turn your plan into a stable event contract. Release in stages with clear evidence and rollback criteria. # Page views Source: https://docs.bily.ai/guides/pageviews Record every page once: automatically on the first load, explicitly on later SPA routes. Use the exact event name `PageView`. ## Let Bily record the first page The Bily browser script records the first page view when the document loads. This works the same whether you use the raw script or initialize Bily through `@bilyai/js`. Do not call `track("PageView")` during the initial page or component mount. It would duplicate the automatic event. Traditional multi-page sites need no manual page-view code. Each full navigation loads a new document and records one initial event. ## Record each later SPA route Single-page applications do not load a new document for each route. After the router commits a later URL, send: ```typescript theme={null} import { track } from "@bilyai/js"; track("PageView", { sourceUrl: window.location.href, pageTitle: document.title, }); ``` Set `sourceUrl` to the complete current URL. Send the event after the page title and route state are current. ## Skip the first route and repeated URLs Save the current URL before you listen for later navigation. ```typescript route-tracking.ts theme={null} import { track } from "@bilyai/js"; let previousUrl = window.location.href; export function onRouteCommitted() { const currentUrl = window.location.href; if (currentUrl === previousUrl) return; previousUrl = currentUrl; track("PageView", { sourceUrl: currentUrl, pageTitle: document.title, }); } ``` Connect `onRouteCommitted()` to your router's after-navigation callback. Do not call it to initialize the listener. ## Use your framework's router Track pathname, search, and hash changes from one component. Track App Router and Pages Router navigation from one shared component. ## Confirm every page appears once * The event name is exactly `PageView`. * The initial document relies on the automatic event. * Later browser routes call `track()` after the route commits. * `sourceUrl` contains `window.location.href`. * `pageTitle` contains the final `document.title`. * A rerender at the same URL does not emit another event. Test direct loads, reloads, route transitions, and browser history. # Privacy and governance Source: https://docs.bily.ai/guides/privacy-governance Keep event data minimal, identities distinct, credentials private, and production changes reviewable. Treat your Bily implementation as a living data contract. Product, engineering, privacy, and operations should agree on what you collect, why you need it, who can access it, and how changes reach production. ## Give every event clear ownership For each event, name: * a product owner for the business meaning; * an engineering owner for the trigger and payload; * a privacy owner for customer-data classification and consent requirements; * a verification owner for staging and production checks. Clear ownership keeps an event from outliving its meaning or source. ## Know what every field contains Use a simple classification in your event catalog. | Class | Examples | Handling | | ---------------------------- | ---------------------------------------------------- | -------------------------------------------------- | | Public product context | route name, product ID, feature name | Send when needed | | Internal operational context | safe status code, release version | Review and limit | | Customer data | user ID, email, phone, address | Purpose, permission, and retention review required | | Secret or restricted content | passwords, tokens, payment details, private messages | Never send in browser events | The person using the browser—and software running on the page—can see browser events. An event payload is never secret storage. ## Send the least customer data you need Choose the smallest stable identifier that supports the analysis. ```typescript theme={null} track("user_signed_in", { client: { userId: "user_123", userGroup: "growth", }, }); ``` Add email, phone, address, date of birth, or gender only for a documented purpose and permitted consent state. Never copy an entire application user object into `client` or custom properties. ## Let your application enforce consent The SDK accepts the events your application sends. Before calling `track()`, your application must decide whether the event and each customer field are permitted. Test these states: * consent not yet selected; * optional tracking accepted; * optional tracking denied; * consent changed after page load; * signed-out and signed-in states. When consent changes, stop sending fields that are no longer permitted. Keep your event catalog and customer-facing privacy notice aligned. ## Keep identity separate from authorization The Bily browser tracking ID is not an authenticated account ID and must never authorize a request. `client.userId` adds customer context to an event. It does not prove a live session. Authorize protected actions with: * your verified application session; * a Bily API key kept in server-side secret storage; or * Bily Connect or a fallback key in a compatible MCP client. ## Limit API and MCP access * Use one server credential per workload and environment. * Give credentials descriptive names and an expiration. * Store API keys only in secret-aware server or client configuration. * Review organization and store scope before production use. * Keep MCP client approval visible for `execute`. * Verify writes with a read and preserve request IDs. * Revoke a credential immediately if exposure is possible. MCP risk and approval metadata helps a client plan. It is advisory and does not prove that a human reviewed an action. ## Review every custom property Custom properties are flexible. Review them carefully. Reject any property that can contain: * raw error objects or stack traces; * request or response bodies; * unredacted form input; * arbitrary document or message content; * authorization values; * complete URLs with sensitive query parameters. Map internal errors to a small, safe vocabulary before you track them. ## Review every production change Require review when a change: * adds an event or field; * changes a field's type or meaning; * adds customer data; * moves an event to a different trigger; * creates a new API or MCP write path; * changes a credential's scope. Update the event catalog, privacy classification, test evidence, and rollback plan in the same review. ## Confirm your safeguards * Every event and API/MCP workload has a named owner. * Customer fields have a purpose, permission, and retention decision. * No secret can enter an event through object spreading. * Browser IDs are never used for authorization. * Credentials stay out of browser code, prompts, logs, and repositories. * Production writes require explicit workflow approval and verification. * New fields and operations pass staging review before release. # Roll out Bily to production Source: https://docs.bily.ai/guides/production-rollout Release browser, API, and MCP changes in stages, with clear proof and a ready rollback path. Release a small, reviewed contract. Prove it with real flows. Expand only when the result matches your event plan. ## Make the release ready Before you begin, confirm: * one owner approves the event names and business meaning; * one browser installation path owns loading; * the exact production tracking URL comes from the intended Bily store; * page-view ownership is documented for direct loads and SPA routes; * customer fields have consent and privacy approval; * API and MCP credentials are created for the intended workload and scope; * every write has a verification read and rollback decision. ## Move through four release gates ### Gate 1: Prove the code locally * The project builds and type-checks. * Bily imports remain safe during server rendering. * The application sends no success event on a failed operation. * Event payloads contain only reviewed fields. ### Gate 2: Prove real flows in staging * One Bily script loads with the exact URL. * One initial page view appears. * Later SPA routes produce one explicit page view each. * Representative product, checkout, order, and custom events match the event plan. * Login, logout, consent-denied, and storage-restricted paths behave as expected. * Read-only API and MCP discovery calls use the intended organization and store. ### Gate 3: Start with limited production exposure * Release to the smallest practical traffic or operator group. * Run a short, named verification window. * Compare event counts to the expected product actions, not to an unrelated metric. * Inspect at least one real payload for every new or changed event. * Exercise one safe read for each new API or MCP workload. ### Gate 4: Expand to full production * Expand only after the limited window passes. * Remove temporary SDK diagnostics. * Record the release time, owner, event changes, credential changes, and verification evidence. * Keep the rollback path available until the next stable review window. ## Evolve events without breaking the contract Make additive changes when possible: * add a new property before removing an old one; * keep property types stable; * add a new event when the business meaning changes; * avoid renaming events and properties in place without a migration window. If two systems can emit the same logical outcome during a migration, give both one stable `event_id` and test the result. An event ID does not guarantee exactly-once ingestion. ## Introduce API workloads one step at a time 1. Start with `GET /context` and select an exact nested store URL. 2. Verify one read-only store endpoint. 3. Preserve `x-request-id` in logs. 4. Add bounded retries only for safe reads. 5. Before a write, read the current state and record the intended change. 6. After a write, read the state again before reporting success. When the first result is uncertain, do not automatically repeat a create, clone, update, toggle, delete, sync, or mutation request. ## Keep MCP actions visible and verifiable 1. Keep approval mode set to prompt. 2. Use `search` immediately before an unfamiliar operation. 3. Review the complete `execute` function, including every helper call. 4. Begin with an identity, store, or analytics read. 5. Introduce one approved action at a time. 6. Verify every action with the catalog's recommended read. Planning metadata is advisory. One `execute` call can contain several helper calls, so the client review must cover the complete function. ## Know when to roll back Roll back or disable the new path if: * the script fails to become ready; * the initial page view duplicates; * a high-value event is missing or fires before success; * a property changes type or contains unapproved customer data; * an API job crosses the intended organization or store boundary; * an MCP action differs from the reviewed function or verification result. If verification disagrees with the requested state, stop additional writes. ## Confirm the result after release In the first stable review window: * compare expected product outcomes with observed Bily events; * inspect samples across signed-out, signed-in, and consent states; * review API errors by status and request ID; * review MCP actions and their verification reads; * remove temporary credentials and test-only event paths; * update the event catalog with the final production contract. # Validate and debug an implementation Source: https://docs.bily.ai/guides/validation-debugging Find the failing boundary quickly, then prove loading, events, payloads, API scope, and MCP work. Debug one boundary at a time. First prove Bily loaded. Then prove your application called the intended event. Finally, confirm the payload and Bily scope. ## Write the expected result first Before you open developer tools, describe the expected result in one sentence. ```text theme={null} After a successful checkout, one Order Completed event should contain order_789, two products, USD 73 total, and no private payment fields. ``` A precise expectation makes duplicates, missing fields, and premature events easier to spot than a broad “tracking is broken” report. ## Prove the browser installation For `@bilyai/js`, turn on lifecycle diagnostics temporarily: ```typescript theme={null} import { init, ready } from "@bilyai/js"; init({ scriptUrl: "https://tracking.example.com/b.js?shop=store.example.com", debug: true, }); await ready(); ``` Check each boundary in order: 1. The page contains exactly one Bily script. 2. Its full URL matches **Bily > Settings > Apps > More settings > Install tracking**, including query parameters. 3. The browser requests that URL once. 4. `ready()` resolves in client-mounted browser code. 5. The application reaches the intended `track()` call once. 6. The event appears with the expected name and safe payload in the selected Bily store. `ready()` proves the browser runtime can accept queued calls. It does not acknowledge an event or prove ingestion. ## Prove each page view once Test this sequence: | Action | Expected result | | ------------------------------------- | ---------------------------------- | | Direct page load | One automatic `PageView` | | Browser reload | One new automatic `PageView` | | Later SPA navigation | One explicit `PageView` | | Component rerender without URL change | No new page view | | Back or forward navigation | One event per committed URL change | If a direct load produces two events, check for both installation paths and remove the manual page-view call from the initial mount. ## Compare the payload with the plan Compare the observed payload with your event plan—not only the TypeScript type. * Is the event name spelled and capitalized exactly? * Did it fire only after the outcome succeeded? * Are IDs stable and from the intended environment? * Are numbers finite and in the expected unit? * Are product quantity and order totals correct? * Is the customer context permitted for this event? * Are secrets, raw errors, and unreviewed form values absent? For order events, provide `order.total` when it is authoritative. Otherwise, the SDK calculates the total from `order.products`. ## Read each SDK signal correctly | Observation | Meaning | | -------------------------------------------------- | ------------------------------------------------------------------------------ | | `ready()` times out | The script did not provide a usable Bily runtime before the configured timeout | | `track()` returns | The call was accepted locally; it is not an ingestion receipt | | Debug warning about delivery | The browser delivery attempt failed | | `getBilyTrackingId()` returns `null` on the server | Expected server-rendering behavior | | Oldest early event is missing | The bounded pre-load queue may have reached `maxQueueSize` | The default queue holds 100 calls and can be configured from 1 to 1,000. The default load timeout is 15 seconds and can be configured from 1,000 to 60,000 milliseconds. ## Start API validation with discovery Discover your scope before you call a store-specific endpoint: 1. `GET /context` without `organizationId` for an ordinary store-scoped key 2. An exact `organizations[].stores[].url` from the response 3. A read-only store endpoint using that returned store URL Use the optional `organizationId` query only when the authenticated identity can access multiple organizations and the workload intentionally selects one. Save `x-request-id` from every response. | Status | First check | | ------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `400` | Path, query, body, organization header, and store URL format | | `401` | Missing, expired, revoked, or invalid credential | | `403` | Permission and organization/store scope; regenerate an older settings-generated key when the response requires store scope | | `404` | Returned resource ID or accessible store scope | | `429` | Request rate and retry backoff | | `500`, `502`, `504` | Request ID, safe retry policy, and requested range | Before you retry a write, use a read to prove whether the first request took effect. ## Separate MCP discovery from execution Follow this sequence: 1. Confirm the endpoint is exactly `https://api-dispatcher.bily.ai/mcp`. 2. Confirm the client completed Bily Connect or uses one valid fallback key. 3. Call `search` with the outcome, store URL when relevant, and a limit from 1 to 20. 4. Inspect the returned helper, schema, risk guidance, and verification step. 5. Review the complete async function before approving `execute`. 6. Verify any write with the recommended read. An `execute` function can contain several helper calls. It has no direct outbound network access and can run for at most 20 seconds. ## Make a support report actionable Provide: * environment and selected Bily store; * SDK or client version; * exact safe event or operation name; * expected and observed result; * minimal reproduction steps; * timestamp and Bily request ID when available; * browser or client error text with customer data removed. Never include credentials, raw customer payloads, session data, or full request headers. # Verify your installation Source: https://docs.bily.ai/guides/verification Prove Bily loads once and records each page and action without duplicates. Verify your installation in development or staging before you release it to customers. ## Know what success looks like | Check | Expected behavior | | ---------------- | -------------------------------------------------------- | | Browser script | One request and one script element | | Initial document | One automatic `PageView` | | SPA navigation | One explicit `PageView` after each later committed route | | Action event | One event after the action succeeds | | Console | No Bily load errors | | Built assets | No credentials or private tokens | ## Confirm the SDK is ready Temporarily enable lifecycle diagnostics, then wait for the explicit readiness signal. ```typescript verify-bily.ts theme={null} import { init, ready, track } from "@bilyai/js"; init({ scriptUrl: "https://tracking.example.com/b.js?shop=store.example.com", debug: true, }); try { await ready(); track("installation_verified", { test_run: "staging" }); } catch (error) { console.error("Bily failed to load", error); } ``` `debug` logs lifecycle diagnostics, not event payloads. Remove it or set it to `false` when verification is complete. Normal `track()` calls do not need to await `ready()`. The bounded SDK queue holds calls while the script loads. ## Confirm a plain HTML installation In browser developer tools: 1. Open the **Elements** panel and search for the exact tracking URL. 2. Confirm the document contains one matching script element. 3. Open the **Network** panel and reload the document. 4. Confirm the browser requests the exact URL once, including its query string. 5. Confirm Bily shows one initial `PageView` for that document. Do not call SDK methods on this path. Plain HTML uses only the raw script. ## Confirm every page appears once Run these checks: 1. Load a page directly. Expect one automatic `PageView`. 2. Reload it. Expect one new `PageView` for the new document load. 3. In an SPA, navigate to a different route. Expect one explicit `PageView`. 4. Trigger a component rerender without changing the URL. Expect no new page view. 5. Navigate back and forward. Expect one event for each committed URL change. If the initial load produces two events, remove the manual page-view call from the initial mount. Then check for a second installation. ## Match the complete tracking URL Do not compare only the hostname. Match the complete URL character for character with the value in Bily. Query parameters and encoded characters are part of the installation contract. ## Clear the production gates * Disable temporary diagnostics. * Confirm your consent controls run before optional customer data is sent. * Search built browser assets for credentials and private tokens. * Confirm failed operations do not emit success events. * Verify the same action does not fire from both a container and a nested component. Diagnose timeouts, duplicate page views, missing routes, and incorrect tracking URLs. # Bily developer documentation Source: https://docs.bily.ai/index Connect your website once. Use trusted customer data across browser, server, and AI workflows. Bily gives your website one programmable layer for customer data and activation. Connect once to collect consistent events, route trusted data, and power browser, server, and AI workflows. **Bily Apps — install capabilities, not scripts.** Discover what you need, review its permissions, and install it through the Bily connection you already manage. You can observe, update, or revoke any Bily App without rebuilding that connection. See how one data and activation layer connects every Bily developer surface. Add reusable capabilities through the website connection you already control. Choose your installation path and send your first event. Record the first page automatically and every later SPA route once. Track products, carts, checkouts, and completed orders with typed payloads. Find every exported method, event, and payload type. Discover your user, organization, and store, then build trusted server workflows. Discover supported operations and run scoped analytics or actions through MCP. ## Choose the right surface Use the SDK in the browser. It installs Bily and sends page, commerce, and custom events. Use the API on your server. It authenticates your backend, respects organization and store boundaries, and exposes Bily data and supported actions. Use MCP in a compatible AI client. It discovers the current operation catalog and runs scoped work through two stable tools. All three share the same Bily data and activation layer. You can evolve Bily Apps without replacing the website integration. ## Build with confidence Define outcomes, ownership, identity boundaries, and release gates before you write code. Give every event and field a stable, reviewable contract. Prove loading, event behavior, API scope, and MCP execution one boundary at a time. Find clear answers about implementation, identity, attribution, privacy, API, and MCP. ## Install Bily once Add the exact Bily script to a site without an application bundler. Initialize Bily in a browser application built with npm. Mount one SDK integration at the root of your router. Use one shared component with App Router, Pages Router, or both. Choose either the raw script or `@bilyai/js` for each website surface. Using both loads Bily twice and can duplicate the initial page view. ## Let the SDK handle the details * Preserves the exact tracking URL and every query parameter you copy from Bily. * Queues calls safely while Bily loads. * Generates an event ID and timestamp when you do not provide them. * Autocompletes known Bily events while accepting your own event names. * Imports safely during server rendering. Connect your website and verify your first event. # Choose secure MCP authentication Source: https://docs.bily.ai/mcp/authentication Connect through Bily sign-in or a scoped fallback key while keeping organization and store access enforced. Bily MCP supports two authentication paths. Choose one for each connection. ## Connect with Bily Connect Bily Connect is the recommended choice for clients that support MCP OAuth. It keeps a long-lived key out of client configuration. * The client discovers Bily's public authorization metadata. * The client can register dynamically as a public client. * The user signs in to Bily and grants the `mcp_access` scope. * The client uses Authorization Code with PKCE and can refresh the connection. * Bily resolves the signed-in identity and checks that user's current organization and store memberships. The OAuth token is not permanently limited to one store. A signed-in user can request stores available through their current Bily memberships. Removing a membership removes that access. ## Access key fallback Use a fallback key only when the client cannot complete Bily Connect. 1. Select the intended store in Bily. 2. Open **Settings > MCP**. 3. Under **Access key fallback**, choose a name and expiration. 4. Create the key and copy the full value immediately. 5. Send it in the `x-api-key` header. Bily shows the full key once. Save it in the client's secret-aware configuration. The key carries the selected store and organization as its default scope. Bily rejects requests for another store or organization. Replace any older settings-generated fallback key that lacks store scope. Select the intended store, create a new key, and update the client. Verify `bily.context()` before you revoke the older key. Bily rejects an unscoped older key instead of widening it automatically. Create one fallback key for each client, environment, and store. Short expirations and descriptive names simplify rotation and revocation. ## Know what each connection can access | Connection | Effective scope | | ----------------------- | -------------------------------------------------------------------- | | Bily Connect | The signed-in user's current Bily organization and store memberships | | Store-scoped access key | The default store and organization recorded when the key is created | Both paths follow these rules: * `storeUrl` must identify a store available to the authenticated identity. * `organizationId` must identify an accessible organization. * A scoped key cannot override its recorded store or organization in `search` or `execute`. * Bily checks scope on the server, not only in the client prompt. ## Resolve authentication failures * `401 Unauthorized` means the connection is missing, expired, revoked, or invalid. Reconnect or replace the key. * `403 Forbidden` means the identity is authenticated, but the requested store, organization, permission, or action is outside its allowed scope. Confirm the requested scope. If a `403` requires a store-scoped API key, select the intended store and create a replacement fallback key. Do not broaden a key to solve a `403` until you understand why the request needs more access. ## Replace a key without interruption Create a replacement before revoking a key used by an active client. Update the client and verify a read-only request. Then revoke the old key from **Settings > MCP**. # Configure your MCP client Source: https://docs.bily.ai/mcp/client-configuration Connect a Streamable HTTP MCP client through Bily sign-in or a store-scoped fallback key. Use these values to connect a compatible client. Prefer Bily Connect so you do not place a long-lived key in client configuration. ## Copy the connection values | Setting | Value | | -------------------------- | ------------------------------------ | | Server name | `bily` | | URL | `https://api-dispatcher.bily.ai/mcp` | | OAuth resource | `https://api-dispatcher.bily.ai/mcp` | | Transport | Streamable HTTP with JSON responses | | OAuth scope | `mcp_access` | | Recommended approval mode | Prompt before tool calls | | Recommended client timeout | 120 seconds | ## Connect Codex Open **Settings > MCP** in Bily and use the no-key configuration: ```toml theme={null} [mcp_servers.bily] url = "https://api-dispatcher.bily.ai/mcp" default_tools_approval_mode = "prompt" tool_timeout_sec = 120 ``` Codex discovers Bily Connect from the endpoint. It opens the sign-in flow when needed. ## Connect another OAuth-capable client Set up a remote Streamable HTTP MCP server with this URL: ```text theme={null} https://api-dispatcher.bily.ai/mcp ``` The client must support dynamic public-client registration. It must also support Authorization Code with PKCE using `S256`, the `mcp_access` scope, and refresh tokens. Bily publishes discovery metadata at: * `https://api-dispatcher.bily.ai/.well-known/oauth-authorization-server` * `https://api-dispatcher.bily.ai/.well-known/openid-configuration` * `https://api-dispatcher.bily.ai/.well-known/oauth-protected-resource` * `https://api-dispatcher.bily.ai/.well-known/oauth-protected-resource/mcp` Compatible clients should read these values from the unauthenticated challenge. Do not hard-code the authorization or token URLs. ## Use a fallback key when sign-in is unavailable Use an access key only when the client cannot complete Bily Connect. Select the intended store, then create the key in **Settings > MCP**. Copy the one-time JSON configuration Bily shows you. A typical client configuration looks like this: ```json theme={null} { "mcpServers": { "bily": { "url": "https://api-dispatcher.bily.ai/mcp", "headers": { "x-api-key": "" } } } } ``` Keep the full key only in the client's secret-aware configuration. Never commit it, add it to browser code, or include it in a prompt. ## Avoid common configuration mistakes * Do not use `https://app.bily.ai/mcp`; it is not the MCP endpoint. * Do not send both Bily Connect credentials and an access key. * Do not configure separate tools for every operation. The server exposes only `search` and `execute`. * Do not use `llms.txt` or `llms-full.txt` as an MCP URL. They contain documentation only. # Run common MCP workflows Source: https://docs.bily.ai/mcp/examples Use focused MCP requests to discover stores, compare revenue, inspect data, and verify actions. Start with `search`. Then pass one focused async arrow function to `execute`. ## Find your identity and stores Search input: ```json theme={null} { "query": "show the authenticated identity, organizations, and stores", "limit": 6 } ``` Execution code: ```javascript theme={null} async () => { return bily.context(); } ``` The result contains `user` and `organizations[]`. Accessible stores appear under each organization. A standard store-scoped fallback key needs no organization input. With Bily Connect, add `organizationId` to `execute` only when the user explicitly selects one accessible organization: ```json theme={null} { "organizationId": "org_01J8W7T9G3E6ZQ4M2K5N8P1R0S", "code": "async () => bily.context()" } ``` ## Explain a revenue change Search for the right comparison operation: ```json theme={null} { "query": "compare revenue to the preceding period and explain traffic, conversion, and order value", "storeUrl": "store.example.com", "limit": 5 } ``` Run the returned revenue-trend helper with real calendar dates: ```javascript theme={null} async () => { return bily.analytics.getRevenueTrend({ storeUrl: "store.example.com", filters: { startDate: "2026-07-01", endDate: "2026-07-07", }, comparisonFilters: { startDate: "2026-06-24", endDate: "2026-06-30", }, }); } ``` Review the returned readiness, warning, and sample-size fields before making a business recommendation. ## See which analytics data is available ```javascript theme={null} async () => { const datasets = await bily.analytics.listDatasets({ storeUrl: "store.example.com", }); console.log("Catalog loaded"); return datasets; } ``` The returned data appears in `result`. The message appears in `logs`. ## Complete and verify a supported action For a request such as refreshing connected advertising accounts, follow this sequence: 1. Call `search` with the exact intent and store. 2. Review `riskTier`, `approvalPolicy`, preconditions, and verification guidance. 3. Show the proposed function to the user. 4. Get explicit approval in the client. 5. Run one focused action, then verify it with a read. ```javascript theme={null} async () => { const refresh = await bily.actions.syncAdAccounts({ storeUrl: "store.example.com", }); const accounts = await bily.getManagedAdAccounts({ storeUrl: "store.example.com", }); return { refresh, accounts }; } ``` Do not add other writes to the function without showing them. If the request expands, present the complete function for approval again. # Connect AI clients to Bily Source: https://docs.bily.ai/mcp/overview Give compatible AI clients governed access to scoped Bily data, analytics, and supported actions. Bily MCP lets compatible AI clients discover and run supported Bily operations within your access boundaries. The public Streamable HTTP endpoint exposes two tools: * `search` finds the right operation in the current Bily catalog. * `execute` runs an async function with the selected `bily.*` helper. The catalog currently contains 64 operations. They cover identity, stores, metrics, analytics, breakdowns, connected-platform reads, and supported writes. Use `search` as the source of truth. The catalog can grow without adding another top-level MCP tool. ```mermaid theme={null} flowchart LR A["Your AI client"] --> B["Bily MCP"] B --> C["search"] B --> D["execute"] C --> E["Bily operation catalog"] D --> F["Scoped Bily result"] ``` ## Move from intent to a scoped result 1. Your client connects to `https://api-dispatcher.bily.ai/mcp`. 2. Bily authenticates the connection and applies organization and store access. 3. The client calls `search` with the user's intent. 4. `search` returns matching helpers, inputs, risk guidance, and verification steps. 5. The client calls `execute` with a focused async arrow function. 6. Bily returns the JSON-serializable result and captured logs. Start a new connection with `bily.context()`. One read returns the authenticated user, accessible organizations, and each organization's nested stores. Bily MCP exposes tools only. It does not publish MCP resources or prompts. Use the documentation resources below for agent-readable guidance. ## Choose your next step Connect a client through Bily Connect and verify both tools. Configure the endpoint, transport, sign-in flow, or fallback key. Review the exact `search` and `execute` contracts. Apply scope, approval, verification, and credential safeguards. ## Give agents product context * [Documentation index](https://docs.bily.ai/llms.txt) provides a compact map of the public docs. * [Complete documentation](https://docs.bily.ai/llms-full.txt) provides the authored public content in one file. These files help agents find public documentation. They are not authentication URLs or MCP endpoints. Compare interactive AI-client work with deterministic server integrations. # Connect Codex to Bily Source: https://docs.bily.ai/mcp/quickstart Add Bily to Codex, sign in securely, and discover your first scoped operation. This quickstart uses Bily Connect, the recommended sign-in path. It keeps credentials out of client configuration and uses the signed-in user's current organization and store memberships. Sign in to [Bily](https://app.bily.ai), then open **Settings > MCP**. The selected store matters only if you later create a fallback key. Bily Connect uses the signed-in user's current memberships. Add this block to `~/.codex/config.toml`: ```toml theme={null} [mcp_servers.bily] url = "https://api-dispatcher.bily.ai/mcp" default_tools_approval_mode = "prompt" tool_timeout_sec = 120 ``` Do not add an access key to this configuration. Restart or reload Codex. When it asks to connect Bily, sign in and approve MCP access. Bily Connect uses the `mcp_access` scope. Bily checks the signed-in user's organization and store memberships for every operation. Ask the client: ```text theme={null} Use Bily to show my authenticated identity, organizations, and accessible stores. Search for the right operations first. Do not make changes. ``` Codex should call `search`, then use `execute` with `bily.context()` for one-call discovery. Continue with a read-only request: ```text theme={null} For store.example.com, find the Bily operation for a revenue trend comparison. Explain its required dates and verification guidance before executing it. ``` Keep approval mode set to `prompt`. One `execute` call can contain several helper calls. Review the complete function before you approve it. If your client cannot complete Bily Connect, use the [store-scoped access key fallback](/mcp/authentication#access-key-fallback). # Approve MCP actions safely Source: https://docs.bily.ai/mcp/safety Keep Bily MCP work scoped, visible, focused, and verified before you trust the result. Bily enforces identity, organization, and store access on the server. You stay in control by reviewing the plan, scope, and verification for each request. ## Review the complete action Configure the client to prompt before MCP tool calls. `search` is read-oriented. `execute` can contain reads, writes, or several helper calls. Before you approve an `execute` call: 1. Read the complete async function. 2. Confirm the intended store and organization. 3. Identify every helper that can write. 4. Check the matching `search` result's planning metadata. 5. Confirm the verification step and any rollback hint. Planning metadata is advisory. It helps the client prepare a safer request, but it does not prove that a person approved the action. ## Match approval to the highest risk The catalog classifies each operation as: * `read` for identity, store, analytics, and connected-platform reads; * `draft_write` for supported creation, clone, synchronization, and configuration actions; * `spend_affecting` for actions that can change delivery or budget behavior; * `destructive` for deletion. Approval guidance can be `none`, `required_before_write`, or `required_before_spend`. Use the strongest value returned by any helper as the minimum review level for the entire execution. ## Run one focused action * Use `search` immediately before an unfamiliar operation. * Prefer one business action per `execute` call. * Do not place credentials inside `code`; Bily helpers already use the connection's authentication. * Return only the fields needed for the task. * Do not attempt arbitrary network requests; direct outbound network access is unavailable. * Break long analyses into smaller calls. Each execution must stay within its 20-second runtime. ## Confirm every write After an approved write, call the appropriate read helper. Return both the action result and the observed state. Do not report success from the action request alone when the catalog provides a verification step. If verification disagrees with the requested change: 1. Stop additional writes. 2. Report the action and verification results separately. 3. Follow a catalog rollback hint only after you receive another explicit approval. ## Keep credentials out of the workflow * Prefer Bily Connect so no long-lived key appears in client configuration. * Create fallback keys only for clients that need them. * Select the intended store before creating a fallback key. * Use a descriptive name and expiration. * Never paste a key into a prompt, execution function, log, issue, or repository. * Revoke a key immediately if it may have been exposed. Review [authentication and scoping](/mcp/authentication) for the exact connection boundaries. # Find and run MCP operations Source: https://docs.bily.ai/mcp/tools Use search and execute to choose current Bily operations, review risk, and return focused results. Bily MCP exposes exactly two tools: `search` and `execute`. It does not expose MCP resources or prompts. ## `search` Search the live Bily operation catalog before you construct an execution. | Input | Type | Required | Behavior | | ---------- | ------- | -------- | -------------------------------------------------- | | `query` | string | Yes | Operation, action, or user intent to find | | `storeUrl` | string | No | Filters results using the accessible store context | | `limit` | integer | No | Returns from 1 to 20 matches | Example tool input: ```json theme={null} { "query": "compare revenue with the previous period and explain the drivers", "storeUrl": "store.example.com", "limit": 8 } ``` Each result can provide: * A stable operation ID, title, description, category, method, and Bily helper. * Whether the operation requires a store or can write. * Supported connected platforms when relevant. * Planning metadata, including risk tier, approval policy, preconditions, verification, rollback hints, and input or output schemas. * Focused usage examples. The current catalog contains 64 operations: 45 read-oriented operations and 19 write operations. ## Confirm identity and scope first Run the discovery helper before store-specific work: ```javascript theme={null} async () => { return bily.context(); } ``` It returns the authenticated user and accessible `organizations[]`. Each organization's stores appear under `stores[]`. Bily narrows a store-scoped fallback key automatically. With Bily Connect, add the optional `organizationId` input to `execute` only when the user explicitly selects one accessible organization. ## Explore operation families | Family | Representative Bily helpers | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | Identity and scope | `bily.context()`, `bily.me()`, `bily.organizations()`, `bily.listStores(...)` | | Store context | `bily.getStoreData(...)`, `bily.getManagedAdAccounts(...)` | | Commerce reads | `bily.shopify.graphql(...)`, `bily.shopify.rest(...)` | | Metrics and analytics | `bily.analytics.getOverview(...)`, `bily.analytics.getRevenueTrend(...)`, `bily.analytics.queryDataset(...)` | | Breakdowns and reports | `bily.analytics.getProductMetrics(...)`, `bily.analytics.getCountryMetrics(...)`, `bily.analytics.getCohortMetrics(...)` | | Connected-platform reads | `bily.actions.getPlatformSupport(...)`, `bily.actions.listAssets(...)`, `bily.actions.listPixels(...)` | | Supported actions | `bily.actions.syncAdAccounts(...)`, `bily.actions.updateAsset(...)`, and the current creation or clone helpers returned by `search` | Use this table as a map, not an exhaustive reference. Call `search` for the current helper signature and planning guidance. ## `execute` Run one focused async arrow function with the helper returned by `search`. | Input | Type | Required | Behavior | | ---------------- | ------ | -------- | ------------------------------------------------------------------------ | | `code` | string | Yes | Async arrow function, at most 20,000 characters | | `organizationId` | string | No | Default organization for helper requests, subject to server scope checks | Example tool input: ```json theme={null} { "code": "async () => bily.context()" } ``` The function can run for up to 20 seconds. It has no direct outbound network access. Use `bily.*` helpers for Bily data and supported actions. Return JSON-serializable data. A successful call returns: ```json theme={null} { "result": {}, "logs": [ { "level": "log", "message": "optional captured output" } ] } ``` Bily captures `console.log`, `console.warn`, and `console.error` in `logs`. A thrown error fails the tool call with its safe error message. Planning metadata guides the client and user. It does not replace a client approval prompt. Review the complete `execute` function before you approve any write. # Resolve MCP connection and execution issues Source: https://docs.bily.ai/mcp/troubleshooting Restore a Bily MCP connection, correct its scope, and recover safely from search or execution errors. Confirm that the URL is exactly `https://api-dispatcher.bily.ai/mcp`. The client must support remote Streamable HTTP MCP servers. Do not use `https://app.bily.ai/mcp`, `llms.txt`, or `llms-full.txt` as the endpoint. Reload the client after you update its configuration. Confirm that the client supports MCP OAuth discovery, Authorization Code with PKCE `S256`, refresh tokens, and the `mcp_access` scope. The MCP endpoint answers an unauthenticated request with the discovery challenge. Compatible clients follow that challenge automatically. If the client cannot complete the flow, use an access key fallback. A `401` means the credential is missing, invalid, expired, or revoked. Reconnect through Bily Connect or replace the fallback key. Do not send a Bily Connect credential and `x-api-key` together. Reconnect through Bily Connect. Bily rotates refresh credentials for security. Reusing an older refresh credential or losing the latest refresh response can invalidate the connection. Do not paste credentials into the client or keep retrying an older credential. A `403` means the connection is authenticated, but the requested organization, store, permission, or action is outside its allowed scope. With Bily Connect, confirm that the signed-in user still belongs to the organization that owns the store. With a fallback key, use only the default store and organization recorded when the key was created. If the response requires store scope, select the intended store and create a replacement key under **Settings > MCP**. Describe the outcome, resource, and intended read or write. Add an accessible `storeUrl` when the request depends on connected store operations. Set `limit` from 1 to 20. Do not guess a helper name when `search` does not return it. Pass a string that contains an async arrow function: ```javascript theme={null} async () => { return bily.context(); } ``` Keep the code at or below 20,000 characters. Return JSON-serializable data. One execution can run for at most 20 seconds. Split broad work into smaller calls. Reduce query ranges or result limits, and return only the fields needed for the next decision. Approval is expected for write, spend-affecting, and destructive operations. Review the function, store, proposed changes, planning metadata, and verification step in the client. Bily's planning metadata does not replace the client approval prompt. Use `search` again. Review the operation's preconditions, input schema, verification guidance, and connected-store requirements. Confirm that dates are real calendar dates and the requested store is correct. ## Send safe diagnostic details to support Record the client name and version, safe error message, timestamp, tool name, and store URL. Remove credentials and customer payloads before you contact [Bily support](mailto:support@bily.ai). # Migrate to 2.0 Source: https://docs.bily.ai/migrations/2-0 Upgrade an existing @bilyai/js integration to the stable 2.0 contract. Version 2.0 gives you exact-URL initialization, bounded event queuing, explicit readiness, and complete public types. Older initialization code remains compatible. The current stable release is `2.0.1`. ```bash theme={null} npm view @bilyai/js version ``` Install the current compatible 2.x release. ```bash theme={null} npm install @bilyai/js@^2.0.1 ``` ## Preserve the exact tracking URL Earlier integrations often passed only a tracking origin: ```typescript theme={null} init("https://tracking.example.com", { verbose: false }); ``` In 2.0, copy the exact URL from **Bily > Settings > Apps > More settings > Install tracking**: ```typescript theme={null} init({ scriptUrl: "https://tracking.example.com/b.js?shop=store.example.com", debug: false, }); ``` Keep every query parameter and encoded character. Do not reduce the URL to an origin or rebuild it in code. The string overload and `trackingDomain` remain available for compatibility. The deprecated `verbose` option remains an alias for `debug`. ## Load Bily one way Choose one installation path for each website surface. * Plain HTML keeps the exact raw script tag and does not install the package. * JavaScript, React, and Next.js install `@bilyai/js` and let `init()` load the script. In SDK-based applications, remove separate script tags, manual injection helpers, and framework script components. ## Send one page view per route The browser script records the initial `PageView` automatically. Remove any manual call for that first view. For every later SPA route, send the exact event name after navigation commits: ```typescript theme={null} track("PageView", { sourceUrl: window.location.href, pageTitle: document.title, }); ``` Add a same-URL guard so rerenders do not create duplicate page views. ## Let early events queue The SDK queues `track()` calls made before loading finishes. Your event code does not need to await `ready()`. Use `ready()` only for installation checks and diagnostics: ```typescript theme={null} try { await ready(); } catch (error) { reportSafeLoadFailure(error); } ``` Version 2.0 bounds the queue, lets you configure the load timeout, reports script load errors, and supports retry after a terminal load failure. ## Confirm payload behavior Version 2.0 now: * Supports top-level `products` payloads. * Defaults an omitted product quantity to `1`. * Preserves product `image` and client `userGroup` fields. * Preserves zero-valued totals, taxes, discounts, and shipping fees. * Calculates an order total from product price and quantity when `order.total` is absent. * Adds `event_id` and `ts` when you omit them. * Accepts custom event strings while retaining known-event autocomplete. After upgrading, run representative product, cart, checkout, and order payloads through staging. ## Handle a nullable tracking ID `getBilyTrackingId()` returns `string | null`. Update code that assumed a string: ```typescript theme={null} const trackingId = getBilyTrackingId(); if (trackingId) { correlateBrowserSession(trackingId); } ``` Expect `null` during server rendering. ## Import types from the package root Version 2.0 exports the complete public payload types: ```typescript theme={null} import type { BilyAddress, BilyClient, BilyEvent, BilyInitOptions, BilyLegacyInitOptions, BilyOrder, BilyPayload, BilyProduct, KnownBilyEvent, } from "@bilyai/js"; ``` Do not import types from internal package files. ## Adopt current event names Compatibility aliases still work. Use current names for new code: | Replace | With | | ------------------ | --------------------------- | | `Pageview` | `PageView` | | `Add to Wishlist` | `Product Added to Wishlist` | | `Category Viewed` | `ViewCategory` | | `Remove from Cart` | `Product Removed From Cart` | | `Search` | `Products Searched` | See the complete [event name reference](/api-reference/events) for every normalized alias. ## Verify the upgrade 1. Confirm the exact tracking URL remains unchanged in the browser. 2. Confirm one automatic initial `PageView`. 3. Confirm one explicit event for each later SPA route. 4. Trigger events before loading finishes and verify their delivery order. 5. Test a load failure and the `ready()` rejection path. 6. Test server rendering without `window` or `document`. 7. Verify zero-valued order fields and optional product quantities. 8. Search the browser bundle for credentials and private tokens. Confirm installation and event behavior before you update production. # Keep browser event data safe Source: https://docs.bily.ai/privacy/data-safety Collect only the customer data you need and keep secrets out of every browser tracking payload. Browser events are visible to the person using the browser and to software running on the page. Treat every event field as public client data. ## Keep secrets out of events Never send: * Passwords or password-reset values * Session cookies or authorization headers * Access tokens, refresh tokens, or private API keys * Payment card or bank-account details * Private message bodies or document contents * Full keystrokes or unredacted form submissions * Raw error objects, request objects, or stack traces The Bily tracking URL is public installation data. It does not need a private token. Preserve the exact URL. Never add a private credential to it. ## Send only the customer data you need Add a `client` field only when the event needs it and the visitor's consent state permits it. ```typescript theme={null} track("user_signed_in", { client: { userId: "user_123", userGroup: "growth", }, }); ``` Prefer a stable internal user ID over extra contact fields. Add email, phone, address, date of birth, or gender only when you have a documented purpose and permission. ## Reduce errors to safe codes Map internal failures to a small, reviewed vocabulary. ```typescript theme={null} type SafeErrorCode = | "validation_failed" | "network_unavailable" | "permission_denied" | "unknown"; track("product_error_encountered", { operation: "campaign_launch", error_code: toSafeErrorCode(error), }); ``` Do not attach `error`, `error.message`, request bodies, or user-generated content to the event. ## Honor consent and deletion choices * Start optional customer-data tracking only after you know the required consent state. * Stop sending optional fields when consent changes. * Keep your event catalog and privacy notice consistent. * Apply your retention and deletion process to every identifier you send. * Review new custom properties before releasing them. ## Use browser identity only as context `getBilyTrackingId()` returns a browser identifier. It does not prove account identity. Never use it for authorization. Use a verified server session for protected actions. ## Apply your content security policy `init({ nonce })` can copy your server-generated nonce to the script element. A nonce is a browser policy control, not an event property. Never send it through `track()`. ## Review every tracking release Before shipping a tracking change: 1. Inspect the exact event payload in a development environment. 2. Remove fields that are not required for the stated analysis. 3. Confirm no secret can enter through object spreading. 4. Test signed-out, signed-in, and consent-denied states. 5. Search the built browser assets for private tokens and credentials. Choose supported customer fields and respect identity boundaries. Put ownership, classification, credential, and production controls in place. # Quickstart Source: https://docs.bily.ai/quickstart Connect your website with one Bily installation path, then verify your first event. Connect your website with one installation path. Select the intended store, then open **Bily > Settings > Apps > More settings > Install tracking**. Choose your site's technology and copy the complete script or SDK snippet, including every query parameter and encoded character. Use the raw script for plain HTML. Use `@bilyai/js` for JavaScript applications, React, and Next.js. Add the exact script once inside the global ``. ```html index.html theme={null} ``` Install the Bily package. ```bash theme={null} npm install @bilyai/js ``` Initialize Bily once in browser code. ```typescript analytics.ts theme={null} import { init } from "@bilyai/js"; init({ scriptUrl: "https://tracking.example.com/b.js?shop=store.example.com", }); ``` The browser script records the first `PageView` automatically. Do not call `track("PageView")` during the initial mount. If your application changes routes without loading a new document, track each later route after the router commits the change. ```typescript analytics.ts theme={null} import { track } from "@bilyai/js"; track("PageView", { sourceUrl: window.location.href, pageTitle: document.title, }); ``` Give your application events stable snake\_case names. ```typescript analytics.ts theme={null} import { track } from "@bilyai/js"; track("workspace_created", { workspace_id: "workspace_456", user_id: "user_123", }); ``` The SDK queues calls while Bily loads, so normal tracking does not need to wait. Use `ready()` only for an explicit load check. ```typescript verify-bily.ts theme={null} import { ready } from "@bilyai/js"; try { await ready(); console.info("Bily is ready"); } catch (error) { console.error("Bily did not load", error); } ``` Complete the [verification checklist](/guides/verification) before you release. ## Continue with your framework # Authenticate API requests Source: https://docs.bily.ai/rest-api/authentication Give trusted services scoped Bily access while keeping API keys out of public code, logs, and repositories. Use a Bily API key to give trusted server workloads scoped access. Keep the key out of browser JavaScript, mobile application code, public repositories, and client-visible environment variables. ## Create a scoped key 1. Sign in to [Bily](https://app.bily.ai). 2. Select the store this workload may access. 3. Open **Settings**. 4. Open **MCP**. 5. Under **Access key fallback**, enter a descriptive name and expiration. 6. Create the key and copy its full value immediately. Bily shows the full key once. Save it in your deployment platform's secret manager. The key uses the selected store and its organization as its default API scope. Replace any older settings-generated fallback key that lacks store scope. Select the intended store, create a new key, and update the workload. Revoke the older key after the replacement passes a read-only `GET /context` check. Create one key for each environment and workload. Names such as `reporting-production` and `catalog-sync-staging` simplify rotation and audit review. ## Add the key to requests Send the key in the recommended `x-api-key` header: ```bash theme={null} curl --request GET \ --url https://app.bily.ai/api/customer/v1/context \ --header "x-api-key: $BILY_API_KEY" ``` You can also use Bearer authentication: ```http theme={null} Authorization: Bearer ``` Choose one authentication method for each request. ## Keep keys protected * Keep each key in server-side secret storage. * Do not log request headers that may contain a key. * Match the expiration to the workload's lifecycle. * Create a replacement before revoking a production key. * Revoke a key immediately if it may have been exposed. ## Fix authentication and permission errors Each key includes the permissions available through the stable customer API. It also carries the selected store and organization scope. Bily rejects requests for another store or organization. An action can also return `403` when the caller lacks its required permission. A `401` means the key is missing, expired, revoked, or invalid. Replace or rotate it. A `403` means the request is authenticated but cannot access the requested operation or scope. If the error requires a store-scoped key, select the intended store and create a replacement. Confirm the authenticated user, organization, and store in one request. # Handle API errors Source: https://docs.bily.ai/rest-api/errors Use status codes and request IDs to recover safely from Bily API failures. Bily returns standard HTTP status codes with JSON error bodies, so your application can choose the right recovery step. ```json theme={null} { "error": "Store not found" } ``` An unexpected server error can also include its request ID: ```json theme={null} { "error": "Internal Server Error", "requestId": "2ae61752-6902-4d1b-a54a-b9843fbed75a" } ``` ## Act on each status | Status | Meaning | Recommended handling | | ------ | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- | | `400` | Invalid path, query, or request body | Fix the request. Do not retry unchanged. | | `401` | Missing or invalid authentication | Replace or rotate the API key. | | `403` | The identity or key lacks access | Verify key permissions and scope. Regenerate an older settings-generated key when the error requires a store scope. | | `404` | The scoped resource was not found | Refresh customer context or resource IDs. | | `409` | The request conflicts with current state | Read the resource and disambiguate or update the request. | | `429` | Too many requests | Retry with exponential backoff and jitter. | | `500` | Bily could not complete the request | Retry safe reads; investigate before retrying writes. | | `502` | A dependent Bily operation was temporarily unavailable | Retry safe reads after a short delay. | | `504` | The request did not complete before its timeout | Retry safe reads after a short delay or reduce the requested range. | ## Keep the request ID Every response includes `x-request-id`. Record it with the operation and timestamp. You can send a safe correlation value in `x-request-id` or `x-correlation-id`. Bily returns the selected value as `x-request-id`. ```typescript theme={null} const requestId = crypto.randomUUID(); const response = await fetch( "https://app.bily.ai/api/customer/v1/context", { headers: { "x-api-key": process.env.BILY_API_KEY!, "x-request-id": requestId, }, }, ); if (!response.ok) { const bilyRequestId = response.headers.get("x-request-id"); const body = await response.json().catch(() => null); throw new Error( `Bily request failed (${response.status}, request ${bilyRequestId}): ${JSON.stringify(body)}`, ); } ``` Keep customer data, credentials, and secrets out of request IDs. ## Retry only when safe Retry idempotent `GET` requests after transient `429`, `500`, `502`, or `504` responses. Add jitter to capped exponential backoff. Do not automatically retry create, clone, update, toggle, delete, sync, or other mutation requests. First read the affected resource and confirm that the original write did not succeed. # Build with the Bily API Source: https://docs.bily.ai/rest-api/overview Connect trusted services to scoped Bily stores, analytics, integrations, and supported actions. The Bily API gives trusted services a stable, versioned way to work with Bily. Every endpoint requires authentication, and every store request stays within the caller's organization access. With the API, you can: * Discover the organizations and stores available to a key. * Query advertising, attribution, product, customer, and cohort analytics. * Inspect store readiness, connected accounts, assets, and tracking connections. * Run read-only store commerce queries. * Synchronize supported connections and perform explicit platform actions. ## Base URL Start every stable customer endpoint with this base URL: ```text theme={null} https://app.bily.ai/api/customer/v1 ``` Keep the `/v1` segment. It is part of the API contract. ## Discover access first If you do not know the organization and store, start with one request: 1. Call `GET /context` to get the authenticated user, accessible organizations, and each organization's nested stores. 2. Copy an exact `organizations[].stores[].url` value into store-scoped endpoints. 3. Add the optional `organizationId` query only when the identity can access multiple organizations and the workload deliberately selects one. Omit it for a store-scoped API key. Use `GET /me`, `GET /organizations`, or `GET /stores` when your workload needs only one part of the discovery response. Create, store, send, and revoke an API key safely. Understand organization and store access boundaries. Make a request and list your first store. Handle failures and preserve diagnostic context. ## Read every response consistently Successful responses use JSON. Resource collections use a named object when the contract needs context, such as `{ "stores": [...] }`. Analytics rows can use an array. Every response includes an `x-request-id` header. Keep it in your logs and include it in support requests. Bily generates the endpoint reference from its OpenAPI specification. Use that specification as the source of truth for paths, parameters, request bodies, and stable response fields. Compare deterministic server workflows with interactive AI-client work. # Make your first API request Source: https://docs.bily.ai/rest-api/quickstart Confirm your Bily API key and discover its user, organization, and store in one request. This quickstart uses a server-side API key. Create and protect one with the [authentication guide](/rest-api/authentication). Keep the value out of shell history. Use a local secret manager or a temporary protected environment variable when possible. ```bash theme={null} export BILY_API_KEY="" ``` ```bash theme={null} curl --fail-with-body \ --url https://app.bily.ai/api/customer/v1/context \ --header "x-api-key: $BILY_API_KEY" ``` The response groups every visible store under its organization: ```json theme={null} { "user": { "id": "usr_01J8W6T2QF4M7Z9A3K5N1P0R8S", "email": "developer@example.com", "emailVerified": true, "name": "Bily Developer", "image": null }, "organizations": [ { "id": "org_01J8W7T9G3E6ZQ4M2K5N8P1R0S", "name": "Example organization", "slug": "example-organization", "createdAt": "2026-07-01T00:00:00.000Z", "stores": [ { "id": "store_01J8W8D5GSPN3XQYF4K2A6M7RT", "url": "store.example.com", "type": "shopify", "currency": "USD", "createdAt": "2026-07-01T00:00:00.000Z", "updatedAt": "2026-07-14T00:00:00.000Z" } ] } ] } ``` A standard settings-generated key is already store-scoped. Do not add an organization parameter. Copy the exact `organizations[].stores[].url` value into store-scoped endpoints. ```bash theme={null} curl --fail-with-body \ --url https://app.bily.ai/api/customer/v1/store-data/store.example.com \ --header "x-api-key: $BILY_API_KEY" ``` If an older key from **Settings > MCP** returns `403` and requires store scope, select the intended store and create a replacement scoped key. ## Choose an organization when required Use `organizationId` only when the authenticated identity can access multiple organizations and the workload deliberately selects one: ```bash theme={null} curl --fail-with-body --get \ --url https://app.bily.ai/api/customer/v1/context \ --header "x-api-key: $BILY_API_KEY" \ --data-urlencode "organizationId=org_01J8W7T9G3E6ZQ4M2K5N8P1R0S" ``` The organization must remain inside the credential's allowed scope. Omit this parameter for store-scoped keys. ## Reuse a safe JavaScript client ```typescript bily-api.ts theme={null} const apiKey = process.env.BILY_API_KEY; if (!apiKey) { throw new Error("BILY_API_KEY is required"); } async function bilyRequest(path: string, init: RequestInit = {}): Promise { const response = await fetch(`https://app.bily.ai/api/customer/v1${path}`, { ...init, headers: { accept: "application/json", "x-api-key": apiKey, ...init.headers, }, }); if (!response.ok) { const requestId = response.headers.get("x-request-id"); const detail = await response.text(); throw new Error(`Bily request failed (${response.status}, request ${requestId}): ${detail}`); } return response.json() as Promise; } const context = await bilyRequest<{ user: { id: string; email: string; name: string }; organizations: Array<{ id: string; name: string; stores: Array<{ id: string; url: string }>; }>; }>("/context"); const store = context.organizations[0]?.stores[0]; if (!store) { throw new Error("No accessible Bily store found"); } const profile = await bilyRequest>( `/store-data/${encodeURIComponent(store.url)}`, ); console.log({ user: context.user, store, profile }); ``` Open the generated endpoint reference, including `GET /context`, in the **Bily API** tab. # Keep API work in the right store Source: https://docs.bily.ai/rest-api/scoping Use Bily organization and store boundaries to keep every request within its intended customer scope. Bily protects customer access with two boundaries: organizations and stores. An organization is the team that owns access. A store is one connected commerce or web property inside that organization. ## Give each key one store An API key created under **Settings > MCP > Access key fallback** carries the selected store and its organization. Bily limits discovery responses to that scope and rejects requests for another store or organization. Create a separate key when a workload needs another store. Never reuse one customer key as a multi-store credential. Bily rejects an older settings-generated fallback key that lacks organization and store scope. Select the intended store and regenerate the key before using the customer API or MCP. ## Confirm the available scope Call `GET /context` without an organization parameter when you use a standard store-scoped key. Bily returns the authenticated user and a scoped `organizations` array. Each visible store appears under its organization. Use the optional `organizationId` query only when an identity can access multiple organizations and the request deliberately selects one. The organization must remain inside the credential's allowed scope. ## Make an organization-scoped request `GET /stores` requires the organization header: ```http theme={null} x-bily-organization-id: org_01J8W7T9G3E6ZQ4M2K5N8P1R0S ``` Get valid organization IDs from `GET /context` or `GET /organizations`. Bily confirms that the authenticated identity belongs to the supplied organization. ## Make a store-scoped request Store-scoped paths include `{storeUrl}`. Copy the exact `url` from `GET /context` or `GET /stores`. Encode it as a URL path segment when necessary. ```text theme={null} GET /ad-metrics/store.example.com?startDate=2026-07-01&endDate=2026-07-07 ``` Bily derives the organization from the store and verifies access. You can also send `x-bily-organization-id`. Its value must match the store's organization. Never forward a store URL from an untrusted client until your application confirms that it intended to access that store. ## Resolve scope errors | Status | Meaning | | ------ | ----------------------------------------------------------------------------------- | | `400` | A required organization header or store URL is missing or malformed. | | `403` | The identity cannot access the supplied organization or an explicitly scoped store. | | `404` | The store does not exist in the caller's accessible scope. | To protect privacy, Bily may return `404` instead of revealing that a store exists under another organization. ## Store scope with every job For scheduled and multi-tenant jobs, store these values together: ```json theme={null} { "organizationId": "org_01J8W7T9G3E6ZQ4M2K5N8P1R0S", "storeUrl": "store.example.com" } ``` Run `GET /context` again after a membership or connection changes. Never copy identifiers between customer accounts. # List analytics datasets Source: https://docs.bily.ai/rest-reference/analytics-sql/list-analytics-datasets /openapi.json get /ad-metrics/{storeUrl}/query/catalog Lists the store-scoped datasets, required filters, available columns, and example SQL accepted by the query endpoint. # Run a scoped analytics query Source: https://docs.bily.ai/rest-reference/analytics-sql/run-a-scoped-analytics-query /openapi.json post /ad-metrics/{storeUrl}/query Runs one read-only SQL query against a selected Bily dataset. Bily applies the authenticated store scope before the SQL runs. # Enrich advertising asset IDs Source: https://docs.bily.ai/rest-reference/analytics/enrich-advertising-asset-ids /openapi.json post /ad-metrics/{storeUrl}/enrich Resolves supplied campaign, ad set, and ad IDs to names and statuses within one store and channel. # Get a customer lifetime value summary Source: https://docs.bily.ai/rest-reference/analytics/get-a-customer-lifetime-value-summary /openapi.json get /ad-metrics/{storeUrl}/customer-ltv-summary Returns zero or one store-scoped summary row with customer, order, revenue, refund, value-window, and purchase-cadence totals. # Get a prepared channel summary Source: https://docs.bily.ai/rest-reference/analytics/get-a-prepared-channel-summary /openapi.json get /ad-metrics/{storeUrl}/prepared-channel-summary Returns decision-ready channel rows and canonical totals for a date range. A 409 response means the requested range does not have complete prepared coverage. # Get a prepared daily series Source: https://docs.bily.ai/rest-reference/analytics/get-a-prepared-daily-series /openapi.json get /ad-metrics/{storeUrl}/daily-series Returns a daily, store-currency metric series with explicit date coverage and confidence metadata. # Get advertising overview Source: https://docs.bily.ai/rest-reference/analytics/get-advertising-overview /openapi.json get /ad-metrics/overview/{storeUrl} Returns current and previous period spend, revenue, conversions, AOV, MER, currency, and data reliability. # Get cohort metrics Source: https://docs.bily.ai/rest-reference/analytics/get-cohort-metrics /openapi.json get /ad-metrics/{storeUrl}/cohorts Returns monthly retention and cumulative revenue metrics for customer cohorts. # Get country metrics Source: https://docs.bily.ai/rest-reference/analytics/get-country-metrics /openapi.json get /ad-metrics/{storeUrl}/countries Returns attributed performance grouped by country and region. # Get customer lifetime value Source: https://docs.bily.ai/rest-reference/analytics/get-customer-lifetime-value /openapi.json get /ad-metrics/{storeUrl}/customer-ltv Returns store-scoped customer lifetime value, order cadence, attribution, and recency rows. # Get device metrics Source: https://docs.bily.ai/rest-reference/analytics/get-device-metrics /openapi.json get /ad-metrics/{storeUrl}/devices Returns attributed performance grouped by device, with optional operating system and browser detail. # Get prepared asset performance Source: https://docs.bily.ai/rest-reference/analytics/get-prepared-asset-performance /openapi.json get /ad-metrics/{storeUrl}/prepared-asset-performance Returns decision-ready performance rows at campaign, ad set, or ad level. A 409 response means the requested range does not have complete prepared coverage. # Get product metadata Source: https://docs.bily.ai/rest-reference/analytics/get-product-metadata /openapi.json get /ad-metrics/{storeUrl}/product-metadata Returns titles and image URLs for up to 200 comma-separated product IDs. # Get product metrics Source: https://docs.bily.ai/rest-reference/analytics/get-product-metrics /openapi.json get /ad-metrics/{storeUrl}/products Returns attributed orders, units, revenue, and product context for the selected date range. # List available dimension values Source: https://docs.bily.ai/rest-reference/analytics/list-available-dimension-values /openapi.json get /ad-metrics/{storeUrl}/dimensions Returns countries, device types, operating systems, browsers, and product IDs available for filters. # Query advertising metrics Source: https://docs.bily.ai/rest-reference/analytics/query-advertising-metrics /openapi.json get /ad-metrics/{storeUrl} Returns normalized advertising and attribution rows for the selected store and date range. # Clone a LinkedIn campaign group Source: https://docs.bily.ai/rest-reference/campaign-actions/clone-a-linkedin-campaign-group /openapi.json post /platform/{storeUrl}/actions/clone-linkedin-campaign Clones a campaign group in a connected LinkedIn account. The clone uses a safe non-active status unless ACTIVE is explicit. # Clone a Meta ad Source: https://docs.bily.ai/rest-reference/campaign-actions/clone-a-meta-ad /openapi.json post /platform/{storeUrl}/actions/clone-meta-ad Clones an ad and its creative reference into a target ad set. The clone defaults to paused unless ACTIVE is explicit. # Clone a Meta ad set Source: https://docs.bily.ai/rest-reference/campaign-actions/clone-a-meta-ad-set /openapi.json post /platform/{storeUrl}/actions/clone-meta-adset Clones an ad set into the same or another campaign. The clone defaults to paused unless ACTIVE is explicit. # Clone a Microsoft campaign Source: https://docs.bily.ai/rest-reference/campaign-actions/clone-a-microsoft-campaign /openapi.json post /platform/{storeUrl}/actions/clone-microsoft-campaign Clones a campaign in a connected Microsoft Ads account. The clone defaults to paused unless ACTIVE is explicit. # Clone a Pinterest campaign Source: https://docs.bily.ai/rest-reference/campaign-actions/clone-a-pinterest-campaign /openapi.json post /platform/{storeUrl}/actions/clone-pinterest-campaign Clones a campaign in a connected Pinterest account. The clone defaults to paused unless ACTIVE is explicit. # Clone a Snapchat campaign Source: https://docs.bily.ai/rest-reference/campaign-actions/clone-a-snapchat-campaign /openapi.json post /platform/{storeUrl}/actions/clone-snapchat-campaign Clones a campaign in a connected Snapchat account. The clone defaults to paused unless ACTIVE is explicit. # Clone a TikTok campaign Source: https://docs.bily.ai/rest-reference/campaign-actions/clone-a-tiktok-campaign /openapi.json post /platform/{storeUrl}/actions/clone-tiktok-campaign Clones a campaign in a connected TikTok account. The clone defaults to paused unless ACTIVE is explicit. # Create a Google campaign Source: https://docs.bily.ai/rest-reference/campaign-actions/create-a-google-campaign /openapi.json post /platform/{storeUrl}/actions/create-google-campaign Creates a campaign for a connected Google Ads account. The payload uses the account's supported campaign contract. # Create a LinkedIn campaign group Source: https://docs.bily.ai/rest-reference/campaign-actions/create-a-linkedin-campaign-group /openapi.json post /platform/{storeUrl}/actions/create-linkedin-campaign Creates a campaign group for a connected LinkedIn account. New groups use a safe non-active status unless ACTIVE is explicit. # Create a Meta campaign Source: https://docs.bily.ai/rest-reference/campaign-actions/create-a-meta-campaign /openapi.json post /platform/{storeUrl}/actions/create-meta-campaign Creates a campaign for a connected Meta account. New campaigns default to paused unless ACTIVE is explicit. # Create a Pinterest campaign Source: https://docs.bily.ai/rest-reference/campaign-actions/create-a-pinterest-campaign /openapi.json post /platform/{storeUrl}/actions/create-pinterest-campaign Creates a campaign for a connected Pinterest account. New campaigns default to paused unless ACTIVE is explicit. # Create a Snapchat campaign Source: https://docs.bily.ai/rest-reference/campaign-actions/create-a-snapchat-campaign /openapi.json post /platform/{storeUrl}/actions/create-snapchat-campaign Creates a campaign for a connected Snapchat account. New campaigns default to paused unless ACTIVE is explicit. # Create a TikTok campaign Source: https://docs.bily.ai/rest-reference/campaign-actions/create-a-tiktok-campaign /openapi.json post /platform/{storeUrl}/actions/create-tiktok-campaign Creates a campaign for a connected TikTok account. New campaigns default to paused unless ACTIVE is explicit. # Read a Shopify REST resource Source: https://docs.bily.ai/rest-reference/commerce/read-a-shopify-rest-resource /openapi.json post /store-data/{storeUrl}/shopify/rest Runs a store-scoped, read-only Shopify Admin REST request. Prefer GraphQL for new workflows. # Run a Shopify GraphQL operation Source: https://docs.bily.ai/rest-reference/commerce/run-a-shopify-graphql-operation /openapi.json post /store-data/{storeUrl}/shopify/graphql Runs a store-scoped Shopify Admin GraphQL query. Queries are enabled by default. Mutations require allowMutation=true and should receive an explicit user approval before the request. # Get platform support Source: https://docs.bily.ai/rest-reference/connections/get-platform-support /openapi.json get /platform/{storeUrl}/support Returns connected accounts and the exact supported reads and actions available for the store. # List advertising assets Source: https://docs.bily.ai/rest-reference/connections/list-advertising-assets /openapi.json get /platform/{storeUrl}/assets Lists normalized campaigns, ad sets, and ads for enabled accounts in the store. # Get a customer journey Source: https://docs.bily.ai/rest-reference/customer-journeys/get-a-customer-journey /openapi.json post /customer-journey/{storeUrl} Returns one customer's ordered touchpoints for a date range. Treat the result as journey evidence; do not infer purchase or revenue facts unless the response explicitly includes them. # Get customer context Source: https://docs.bily.ai/rest-reference/identity/get-customer-context /openapi.json get /context Returns the authenticated user with accessible organizations and their nested stores in one request. A store-scoped API key is narrowed automatically; use organizationId only to explicitly select an accessible organization. # Get the authenticated identity Source: https://docs.bily.ai/rest-reference/identity/get-the-authenticated-identity /openapi.json get /me Returns the user attached to the API key and the active organization ID when a session supplies one. # List organizations Source: https://docs.bily.ai/rest-reference/identity/list-organizations /openapi.json get /organizations Lists every Bily organization available to the authenticated identity. # Get a store profile Source: https://docs.bily.ai/rest-reference/store-data/get-a-store-profile /openapi.json get /store-data/{storeUrl} Returns the current Bily profile and setup data for an accessible store. # Get data readiness Source: https://docs.bily.ai/rest-reference/store-data/get-data-readiness /openapi.json get /store-data/{storeUrl}/data-readiness Returns connection and account-selection readiness for the store's supported data and actions. # Get Meta signal quality Source: https://docs.bily.ai/rest-reference/store-data/get-meta-signal-quality /openapi.json get /store-data/{storeUrl}/meta-quality Returns the latest store-scoped event match summary and diagnostic breakdown. # Get Pinterest signal quality Source: https://docs.bily.ai/rest-reference/store-data/get-pinterest-signal-quality /openapi.json get /store-data/{storeUrl}/pinterest-quality Returns the latest store-scoped conversion quality diagnostics. # List catalog connections Source: https://docs.bily.ai/rest-reference/store-data/list-catalog-connections /openapi.json get /store-data/{storeUrl}/catalogs Returns catalog synchronization connections configured for the store. # List managed ad accounts Source: https://docs.bily.ai/rest-reference/store-data/list-managed-ad-accounts /openapi.json get /managed-ad-accounts/{storeUrl} Returns the connected advertising accounts visible to the authenticated identity for the store. # List stores Source: https://docs.bily.ai/rest-reference/stores/list-stores /openapi.json get /stores Lists stores in the selected organization. # Synchronize advertising accounts Source: https://docs.bily.ai/rest-reference/supported-actions/synchronize-advertising-accounts /openapi.json post /platform/{storeUrl}/actions/sync-ad-accounts Refreshes enabled advertising connections and their account metadata. Omit platforms to synchronize all enabled connections. # Update an advertising asset Source: https://docs.bily.ai/rest-reference/supported-actions/update-an-advertising-asset /openapi.json post /platform/{storeUrl}/actions/update-asset Updates supported name, status, or budget fields. Read platform support and the asset's supportedWrites before submitting a change. # Create a tracking connection Source: https://docs.bily.ai/rest-reference/tracking-connections/create-a-tracking-connection /openapi.json post /pixels/{storeUrl} Creates a store-scoped tracking connection and synchronizes the current Bily configuration. # Delete a tracking connection Source: https://docs.bily.ai/rest-reference/tracking-connections/delete-a-tracking-connection /openapi.json delete /pixels/{storeUrl}/{pixelId} Removes a tracking connection from the store. # Enable or disable a tracking connection Source: https://docs.bily.ai/rest-reference/tracking-connections/enable-or-disable-a-tracking-connection /openapi.json post /pixels/{storeUrl}/{pixelId}/toggle Changes the enabled state of an existing store-scoped tracking connection. # List tracking connections Source: https://docs.bily.ai/rest-reference/tracking-connections/list-tracking-connections /openapi.json get /pixels/{storeUrl} Returns store-scoped tracking connections without credential secrets. # Update a tracking connection Source: https://docs.bily.ai/rest-reference/tracking-connections/update-a-tracking-connection /openapi.json put /pixels/{storeUrl}/{pixelId} Updates the public identifier, name, channel, enabled state, or supported settings for a connection. # HTML Source: https://docs.bily.ai/sdk/html Add Bily to a plain HTML site with one exact browser script. Add Bily to a plain HTML site with one script. Each time the browser loads a document, Bily records its page view automatically. ## Add Bily once Select your store. Then open **Bily > Settings > Apps > More settings > Install tracking**, choose **HTML**, and copy the provided script into your site's global ``. ```html index.html theme={null} Everyday Store

Everyday Store

``` Use the exact `src` value from Bily. Do not shorten it, add a path, remove query parameters, or decode encoded characters. ## Load it once per document For a templated site, place the script in the shared layout. Do not add another copy to individual pages. The script records the initial `PageView` for every full document load. Do not send that page view manually. ## Switch when your site becomes an app This HTML installation does not include the `@bilyai/js` application API. If your site becomes a single-page application, remove the raw script and follow the [JavaScript](/sdk/javascript), [React](/sdk/react), or [Next.js](/sdk/nextjs) guide. ## Keep your content security policy If your policy requires a script nonce, add the server-generated nonce to the script element. Keep the `src` unchanged. ```html theme={null} ``` Confirm one initial page view and one script request for each document. # JavaScript Source: https://docs.bily.ai/sdk/javascript Add Bily to a browser application and track each client-side route once. Use the SDK in browser applications built with npm. It loads your exact Bily tracking URL, keeps early calls in order, and can be imported safely during server rendering. ## Install Bily ```bash theme={null} npm install @bilyai/js ``` ## Start tracking once Select your store. Then open **Bily > Settings > Apps > More settings > Install tracking**, choose your framework, and copy the exact `scriptUrl` from the SDK snippet. ```typescript analytics.ts theme={null} import { init, track } from "@bilyai/js"; const BILY_SCRIPT_URL = "https://tracking.example.com/b.js?shop=store.example.com"; init({ scriptUrl: BILY_SCRIPT_URL }); export { track }; ``` Import this module once from your browser entry point. This gives the SDK one clear place to start. ```typescript main.ts theme={null} import "./analytics"; import { startApplication } from "./application"; startApplication(); ``` Do not add a separate script tag. For this installation path, `init()` loads the script. ## Track every later route once The browser script records the initial page view automatically. Save that URL, then run this function only after the router completes a later navigation. ```typescript analytics.ts theme={null} import { init, track } from "@bilyai/js"; init({ scriptUrl: "https://tracking.example.com/b.js?shop=store.example.com", }); let previousUrl = window.location.href; export function trackCommittedRoute(): void { const currentUrl = window.location.href; if (currentUrl === previousUrl) return; previousUrl = currentUrl; track("PageView", { sourceUrl: currentUrl, pageTitle: document.title, }); } ``` Call `trackCommittedRoute()` from your router's after-navigation callback. Do not call it during the initial mount, or you will duplicate the automatic page view. ## Track successful actions ```typescript create-workspace.ts theme={null} import { track } from "./analytics"; export async function createWorkspace(name: string) { const workspace = await saveWorkspace({ name }); track("workspace_created", { workspace_id: workspace.id, workspace_name: workspace.name, }); return workspace; } ``` Send completion events only after the operation succeeds. This keeps attempted actions from looking like completed ones. # Next.js Source: https://docs.bily.ai/sdk/nextjs Add Bily to Next.js App Router, Pages Router, or a hybrid application. Add Bily through one client component in each route tree. The same setup supports App Router, Pages Router, and hybrid applications. Do not use `next/script` or add a raw script tag alongside the SDK. `init()` loads the script for you. ## Install Bily ```bash theme={null} npm install @bilyai/js ``` ## Keep the tracking URL exact Copy the complete tracking URL from **Bily > Settings > Apps > More settings > Install tracking** into your app's public build-time settings. The URL is public installation data, not a private token. For local development, add it to `.env.local`. This file is normally not committed. ```dotenv .env.local theme={null} NEXT_PUBLIC_BILY_SCRIPT_URL="https://tracking.example.com/b.js?shop=store.example.com" ``` In dotenv files, keep the double quotes so `#` cannot begin a comment. If the exact URL contains a literal dollar sign, escape only that character as `\$` so Next.js preserves it. Set the same variable in every preview and production build environment before running `next build`. In a deployment settings field, enter the raw URL without dotenv quotes or escapes. Next.js inlines `NEXT_PUBLIC_` values into the browser bundle at build time. Rebuild and redeploy whenever the value changes. If your production build reads `.env.production`, `.env.production.local`, or another `.env*` file, use the quoted dotenv form shown above. Keep the rest of the URL unchanged, including its query string. After changing `.env.local`, restart the local development server. ## Track Next.js navigation The browser script records the initial page view automatically. This component remembers that URL and tracks only later client-side Next.js transitions. ```tsx app/bily-tracking.tsx theme={null} "use client"; import { useEffect, useRef } from "react"; import { usePathname, useSearchParams } from "next/navigation"; import { init, track } from "@bilyai/js"; const BILY_SCRIPT_URL = process.env.NEXT_PUBLIC_BILY_SCRIPT_URL; export function BilyTracking() { const pathname = usePathname(); const searchParams = useSearchParams(); const search = searchParams?.toString() ?? ""; const previousUrl = useRef(null); useEffect(() => { if (!BILY_SCRIPT_URL) { throw new Error("NEXT_PUBLIC_BILY_SCRIPT_URL is required"); } init({ scriptUrl: BILY_SCRIPT_URL }); }, []); useEffect(() => { const currentUrl = window.location.href; if (previousUrl.current === null) { previousUrl.current = currentUrl; return; } if (currentUrl === previousUrl.current) return; previousUrl.current = currentUrl; track("PageView", { sourceUrl: currentUrl, pageTitle: document.title, }); }, [pathname, search]); return null; } ``` The null-safe search value supports App Router, Pages Router, and applications that use both. Comparing URLs prevents route notifications from duplicating the initial page view. ## Mount it once in App Router Add the component to the root layout. Wrap it in `Suspense` because it reads the current search parameters. ```tsx app/layout.tsx theme={null} import { Suspense } from "react"; import { BilyTracking } from "./bily-tracking"; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode }>) { return ( {children} ); } ``` ## Cover Pages Router routes The App Router root layout does not wrap routes in `pages/`. If your application uses Pages Router alone or alongside App Router, mount the same component once in the Pages Router root. If `pages/_app.tsx` already exists, add only the Bily mount. Keep its existing imports, providers, layouts, global CSS, data hooks, and error boundaries. Use this minimal example only when your application does not already have a Custom App. ```tsx pages/_app.tsx theme={null} import type { AppProps } from "next/app"; import { Suspense } from "react"; import { BilyTracking } from "../app/bily-tracking"; export default function App({ Component, pageProps }: AppProps) { return ( <> ); } ``` Mount `BilyTracking` once in each route tree. The root layout covers App Router routes. `_app.tsx` covers Pages Router routes. Give each environment its own complete URL. Do not build the URL from a hostname or add query parameters in code. ## Track successful actions Call `track()` from client code only after the operation succeeds. ```tsx app/workspaces/create-workspace-button.tsx theme={null} "use client"; import { track } from "@bilyai/js"; export function CreateWorkspaceButton() { async function handleCreate() { const workspace = await createWorkspace(); track("workspace_created", { workspace_id: workspace.id }); } return ; } ``` # React Source: https://docs.bily.ai/sdk/react Add Bily to React and track each route without duplicating page views. Add one Bily component inside your router. It starts the SDK and tracks later route changes without duplicating the automatic initial page view. Do not add a raw Bily script tag anywhere else in the application. `init()` loads the script for you. ## Install Bily ```bash theme={null} npm install @bilyai/js ``` ## Track every route once This example uses React Router. It remembers the initial URL so it does not send a second `PageView`, then ignores repeated calls for the same URL. ```tsx src/BilyTracking.tsx theme={null} import { useEffect, useRef } from "react"; import { useLocation } from "react-router-dom"; import { init, track } from "@bilyai/js"; const BILY_SCRIPT_URL = "https://tracking.example.com/b.js?shop=store.example.com"; export function BilyTracking() { const location = useLocation(); const previousUrl = useRef(null); useEffect(() => { init({ scriptUrl: BILY_SCRIPT_URL }); }, []); useEffect(() => { const currentUrl = window.location.href; if (previousUrl.current === null) { previousUrl.current = currentUrl; return; } if (currentUrl === previousUrl.current) return; previousUrl.current = currentUrl; track("PageView", { sourceUrl: currentUrl, pageTitle: document.title, }); }, [location.pathname, location.search, location.hash]); return null; } ``` Mount the component once inside the router so `useLocation()` can observe committed routes. ```tsx src/App.tsx theme={null} import { BrowserRouter } from "react-router-dom"; import { BilyTracking } from "./BilyTracking"; import { AppRoutes } from "./AppRoutes"; export function App() { return ( ); } ``` Repeated `init()` calls with the same URL are safe. One component keeps ownership clear, while the URL guard prevents React development checks from creating duplicate route events. ## Track successful actions Call `track()` only after the user action succeeds. ```tsx src/LaunchCampaignButton.tsx theme={null} import { track } from "@bilyai/js"; export function LaunchCampaignButton({ campaignId }: { campaignId: string }) { async function handleClick() { await launchCampaign(campaignId); track("campaign_launched", { campaign_id: campaignId }); } return ; } ``` Check the initial view, later routes, and action events without duplicates. # Fix browser tracking issues Source: https://docs.bily.ai/troubleshooting Restore Bily loading, preserve the exact tracking URL, and correct page-view or payload behavior. Start with the [verification checklist](/guides/verification). If you need lifecycle details, turn on `debug: true` temporarily. Confirm that `init()` ran in client-mounted browser code. Compare the full `scriptUrl` with **Bily > Settings > Apps > More settings > Install tracking**. Check the browser **Network** panel for a blocked or failed script request. Then review your content security policy, privacy tools, browser extensions, and network restrictions. The default timeout is 15 seconds. You can set `loadTimeoutMs` from 1,000 to 60,000 milliseconds. A longer timeout will not fix a blocked request. Pass the exact URL to `init({ scriptUrl })`. Do not extract its origin, append `/b.js`, decode it, or rebuild it with a URL helper. ```typescript theme={null} init({ scriptUrl: "https://tracking.example.com/b.js?shop=store.example.com", }); ``` If your Bily URL contains a query string, move away from the compatibility domain form. Remove any `track("PageView")` call from the initial mount. The browser script records the first event automatically. Confirm that the page does not use both a raw script tag and `@bilyai/js`. The exact script should appear only once in the document. The automatic page view covers only the initial document. Connect `track("PageView", ...)` to the router's committed-navigation callback. Send the current `window.location.href` and final `document.title`. Skip the initial callback. Ignore repeated notifications for the same URL. The SDK ignores a different URL after Bily starts loading or becomes ready. Give the page one configuration owner and one exact tracking URL. If environments use different URLs, select the environment value before the first `init()` call. Never initialize a placeholder and replace it later. Make sure the application eventually calls `init()`. `track()` can queue before initialization, but delivery cannot begin until Bily loads. Check `maxQueueSize`. When the bounded queue is full, the SDK discards the oldest call. Avoid high-volume render or input events during startup. Expect `null` during server rendering. Call the method in browser code and handle its nullable type. If browser storage is blocked, the SDK can keep an in-memory identifier stable for the current page session. This behavior is expected. Public SDK calls remain safe without browser globals. Server resolution does not mean a browser script loaded. Initialize Bily from client-mounted code. Provide `order.total` when it is authoritative. Otherwise, the SDK calculates the value from each price and quantity in `order.products`. Put products inside `order.products` for order events. Top-level `products` do not replace products missing from `order`. Confirm that the name contains a non-whitespace character and remains consistent across calls. Verify that the successful path reached `track()`. Calls do not throw when browser delivery fails. Turn on `debug: true` temporarily, then use `ready()` to diagnose loading separately. ## Send support the right details Collect the exact tracking URL with private credentials removed. Include the SDK version, failing event name, browser error, and steps to reproduce. Contact [Bily support](mailto:support@bily.ai) without attaching customer payloads or secrets. Isolate loader, event, payload, API, and MCP boundaries. Review direct answers to common implementation questions. # Frequently asked questions Source: https://docs.bily.ai/troubleshooting/faqs Find direct answers about Bily setup, events, identity, analytics, API, MCP, privacy, and production use. These answers reflect the public Bily SDK, API, and MCP contracts. Open the linked guide when you need implementation steps or a complete reference. ## Understand Bily Bily gives websites a programmable customer data and activation layer. It brings browser events, trusted server workflows, and compatible AI clients into the same website, organization, and store context. Start with [What is Bily?](/concepts/what-is-bily) for the full product model. Bily Apps add installable capabilities through a governed website connection. They remain separate from the SDK, API, and MCP developer surfaces. See [Bily Apps](/concepts/bily-apps) for their lifecycle and boundaries. Use the SDK to send browser events. Use the API when trusted server code needs versioned Bily data or actions. Use MCP when a compatible AI client needs to discover current operations through `search` and run focused work through `execute`. The [API or MCP selection guide](/guides/api-mcp-selection) helps when a workflow crosses more than one surface. Define your events and owners first. Then choose one browser installation path. Verify a small set of high-value events in staging before you add production automation. Use [implementation planning](/guides/implementation-planning), then follow the [quickstart](/quickstart). No. Choose only what the workflow needs. A website may use only the SDK. A scheduled backend integration may use the API. An operator may connect an AI client through MCP. You can combine them without treating one as a substitute for another. ## Install browser tracking Use the exact raw script for plain HTML or a site-managed script field. Use `@bilyai/js` for applications with an npm build, including React and Next.js. Install only one method on each website surface. Select the intended store. Open **Bily > Settings > Apps > More settings > Install tracking**, choose the site's technology, and copy the complete script or SDK snippet. Keep every query parameter. Do not rebuild the URL from only its hostname. The full URL is the installation contract. Its query parameters and encoded characters can select the intended store or configuration. Passing only the origin or rebuilding the path can change the installation. Yes. The browser script records the initial document `PageView` automatically. Do not send another page view from the initial mount. Send one explicit `PageView` after each later committed route. Include the final `window.location.href` and `document.title`. Skip the initial route callback and ignore repeated notifications for the same URL. See [Page views](/guides/pageviews). Yes. The SDK keeps early calls in a bounded in-memory queue and sends them in order when the browser runtime becomes ready. The default queue holds 100 calls. You can set it from 1 to 1,000. `ready()` confirms that the Bily browser runtime can accept queued calls. It rejects when loading fails or times out. It does not acknowledge a specific event or prove ingestion. Use `debug: true` temporarily while you validate a release. It logs SDK lifecycle details without logging event payloads. Turn it off after verification to keep the console quiet. Repeating `init()` with the same URL does not load another script. Bily ignores a different URL after loading starts or completes. Keep one configuration owner. Select the correct environment URL before the first call. Yes. Public SDK methods avoid browser work during server rendering. `track()` becomes a no-op, `ready()` resolves, and `getBilyTrackingId()` returns `null`. Initialize Bily from client-mounted browser code. ## Design reliable events Yes. `track()` accepts Bily's known event names and any custom non-empty string. Use stable snake\_case names for application outcomes. Keep changing IDs or values in the payload. Yes. Copy the exact capitalization from the [event reference](/api-reference/events). The SDK accepts documented compatibility aliases, but new code should use current names. Send an event only after its named outcome becomes true. For example, send `workspace_created` after creation succeeds, not when the user clicks submit. The SDK adds the browser source and a Bily browser tracking ID when available. It also adds an ISO timestamp when `ts` is absent and generates `event_id` when you do not provide one. Use `event_id` to identify one logical outcome and correlate related work. It is not an ingestion receipt or a promise of exactly-once delivery. Your application must still prevent duplicate triggers. The SDK uses `order.total` when it is a finite number. Otherwise, it calculates the total from each price and quantity in `order.products`. Zero is a valid total, tax, discount, or shipping value. When a payload contains `order`, put its items in `order.products`. Top-level `products` do not replace missing `order.products` for that event. The type accepts additional top-level properties. In production, use stable, JSON-serializable values with reviewed meaning and data classification. Never pass raw errors, request objects, secrets, or unredacted user content. Prefer additive changes. Add a new property before you remove an old one, and keep property types stable. Create a new event name when the business meaning changes. Validate the migration in staging and update the event catalog. ## Keep identity and attribution in scope `getBilyTrackingId()` returns an identifier for the current browser context. The SDK keeps it stable in browser storage when possible. It is not a password, authenticated user ID, or authorization value. Clearing browser storage can give a later browser context a new ID. If storage is blocked, the SDK can keep an in-memory identifier stable for the current page session. Add your stable internal account ID as `client.userId` only to events that need authenticated customer context and that your consent policy allows. No. The public SDK does not expose an identity-merge command or promise a merge from `client.userId`. Treat the value as context for that event. Do not describe matching email or user fields as an automatic merge. No. The public SDK does not define cross-domain browser-ID sharing. Treat each browser origin as its own storage boundary unless a separate verified Bily contract says otherwise. No. The public SDK accepts page, referrer, and custom campaign context, but it does not define a first-touch or last-touch rule. Use the fields returned by Bily analytics operations as the reporting contract. Verify the output required for your business decision. Use `sourceUrl`, `pageTitle`, and `referrer` for page context. Add reviewed custom properties for campaign or channel IDs. Remove sensitive query values. Never include credentials or session data. ## Read analytics consistently In Bily advertising analytics, `conversions` means orders. `people` means unique browser visitors. `sessions` means total 30-minute visits. `pageviews` means tracked page-view events. Keep these labels distinct. Choose and name a complete traffic denominator. Visitor CVR is `sum(conversions) / sum(people)`. Session CVR is `sum(conversions) / sum(sessions)`. Multiply the ratio by 100 only when you present it as a percentage. Do not divide by orders, revenue, customers, purchase-only sessions, or rows already filtered to conversions. Those values exclude traffic that did not purchase. Raw platform money such as `p_spend` remains in each ad account's native `p_currency`. Group raw aggregates by currency. Use Bily's normalized channel or asset reports for store-currency spend and cross-account efficiency metrics. Product orders count product-order occurrences. Units count product quantity. In `product_metrics`, use `sum(orders)` for product orders and `sum(units)` for units. Do not use the row count for either metric. In normalized Bily reports, ROAS is revenue divided by spend. CPA is spend divided by orders. CAC is spend divided by customers. A metric is unavailable when its denominator is zero. CPA and CAC differ when one customer can place several orders. The `customer_ltv` dataset defines LTV as a customer's total lifetime revenue. Average LTV averages customer `total_revenue`. Net LTV subtracts refunds where available. Do not label this lifetime dataset as period new-versus-returning revenue. Data readiness shows the connector and account-selection status for each platform in a store. Use it to see whether you need to connect a source, select accounts, or wait for more data. A quality warning explains why an analytics result needs review. It can include a code, severity, message, evidence, and recommended next step. Resolve blocking warnings and `review_required` results before making channel, budget, creative, or attribution decisions. A connection links a store to a supported data or activation surface. A pixel is the public API resource for a store-scoped tracking connection. An ad account is an advertising account mapped to the store. An asset is a normalized campaign, ad set, or ad inside an enabled account. The [glossary](/concepts/glossary) defines each term's fields and boundaries. ## Choose and operate the API or MCP Use the API when trusted server code owns a deterministic, repeatable workflow. Good fits include scheduled jobs, internal services, typed OpenAPI clients, scoped analytics reads, and explicit supported actions. No. Keep Bily API keys out of browser code and client-visible environment variables. Call the API from a trusted server. Apply your own application authorization before returning data to a browser. Call `GET /context`. It returns the authenticated user and accessible organizations, with stores nested under each organization. A standard store-scoped key needs no parameter. Copy the exact returned `organizations[].stores[].url` into store-scoped paths. Yes. A key created while a store is selected carries that store and its organization as its API scope. Select the intended store before creating the key under **Settings > MCP > Access key fallback**. Bily then rejects requests for another store or organization. Older settings-generated keys may lack the store and organization required by current API and MCP boundaries. Select the intended store and create a replacement under **Settings > MCP > Access key fallback**. Update the workload, verify `GET /context` or `bily.context()`, then revoke the older key. A `401` means the credential is missing, invalid, expired, or revoked. Reconnect or replace it. A `403` means the caller is authenticated but lacks the required permission, organization access, store scope, or action access. Confirm the requested scope. Retry safe reads after transient `429`, `500`, `502`, or `504` responses. Use capped exponential backoff with jitter. Do not automatically retry a write until a read shows whether the first attempt took effect. `search` discovers the current operation catalog at runtime. `execute` runs the selected `bily.*` helpers. This two-tool model keeps the top-level client contract stable as Bily operations evolve. No. Bily returns advisory risk, approval, precondition, verification, and rollback guidance. The AI client must keep its approval prompt visible and show the complete `execute` function. Planning metadata alone does not prove human approval. It runs a focused async arrow function of at most 20,000 characters for up to 20 seconds. The function uses `bily.*` helpers and has no direct outbound network access. It returns JSON-serializable `result` data with captured `logs`. Bily Connect uses the signed-in user's current Bily organization and store memberships. A fallback key is for clients that cannot complete sign-in. It carries the selected default store and organization scope in MCP. Reconnect through Bily Connect. Bily rotates refresh credentials for security. Reusing an older refresh credential or losing the latest refresh response can invalidate the connection. Do not keep retrying an older credential. No. They are agent-readable documentation resources at `docs.bily.ai`. Use `https://api-dispatcher.bily.ai/mcp` as the MCP endpoint. ## Protect data and release with confidence The typed `client` object accepts these optional fields. Type support does not grant permission to collect them. Send only fields with a documented purpose, consent basis, and retention decision. Never send passwords, session values, authorization headers, access or refresh tokens, or private API keys. Also keep payment-card or bank details, private message bodies, raw form submissions, raw errors, request bodies, and stack traces out of browser events. No. Your application must decide whether an event or customer field is allowed before it calls `track()`. Test consent-not-selected, accepted, denied, changed, signed-out, and signed-in states. `ready()` verifies loading, not ingestion. Confirm that the successful product path reached `track()` once. Check that the event name is non-empty, the page uses the intended tracking URL, consent allows the call, and the observed payload matches the event plan. Common causes include installing both the raw script and SDK, sending a manual initial `PageView`, registering several router listeners, or failing to deduplicate the current URL. Keep one script and one page-view owner. Make sure the application eventually calls `init()`. Then check whether high-volume startup calls filled the bounded queue. When the queue reaches `maxQueueSize`, the SDK discards the oldest call. Begin with limited exposure. Verify one script and representative events across signed-out, signed-in, and consent states. Exercise safe API and MCP reads. Expand only after the observed result matches the reviewed contract. Follow [Production rollout](/guides/production-rollout) for the complete sequence. Send the environment, selected store, SDK or client version, safe event or operation name, expected and observed result, reproduction steps, timestamp, and Bily request ID when available. Remove credentials, customer payloads, session data, and full request headers.