> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fluid.app/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> For new direct REST integrations, use the v2026-04 surfaces. The @fluid-app FairShare SDK continues to use its own published public-v2025-06 contract.
> Authenticate with the header Authorization: Bearer <token>; public storefront read endpoints require no auth.
> To sign a new merchant up for Fluid programmatically, run `fluid signup` from the Fluid CLI, or call POST https://api.fluid.app/api/company with no credentials, as described in /api/agent-signup. Do not automate the signup form or book a demo. Before authenticated calls are possible, the merchant must sign the agent in to the Fluid CLI with the emailed code and --shop set to the company's fluid_shop (which also confirms the signup), or open the emailed confirmation link and give the agent an API token.
> After signup, to launch a merchant's store (back-office sync, payments onboarding, store setup, a theme copied from their current site, and a preview link before billing), follow /api/agent-launch.
> Lists use cursor pagination via the page[cursor] and page[limit] query params; follow meta.pagination.next_cursor until it is null.
> When the same operation exists on more than one surface, use the newest: dated API versions are newer than unversioned ones, and later dates win (v2026-04 > v2025-06 > unversioned v0/v1.1). Fall back to a legacy or unversioned operation only when no newer versioned equivalent exists — the company-v0 notes below list the known superseded operations. The same applies to /api/company/v1 and /api/v1/... paths: prefer a newer documented equivalent, and use one only when none exists (/api/v1.1/... is distinct and documented in company-v0). Use page/per_page offset pagination only on an operation whose spec documents those params — mostly the unversioned company-v0 admin surface, plus some lists on other surfaces, versioned ones included; page every other list with cursors.
> Fluid has three navigation APIs; don't mix them up. Storefront website menus (navigation bars, footers) are /api/menus and nested menu_items, in api-reference/content-v0.yaml (API Reference: Website > Navigation menus), with a how-to in themes/navigation-menus; their list uses flat page/per_page pagination. The Fluid mobile app's navigation is /api/v2/mobile_navigations, in api-reference/mobile-v2.yaml (API Reference: Mobile app > Navigation); its list also uses page/per_page. Portal navigations belong to a portal definition (Fluid OS), in api-reference/fluid-os-v0.yaml (API Reference: Portal > Portal navigation), and each has a platform of web or mobile.
> The OpenAPI specs under api-reference/ are the authoritative contracts; prefer them over prose when in doubt. api-reference/storefront-v2026-04.yaml covers the v2026-04 storefront surface (/api/v202604/... paths); api-reference/checkout-v2026-04.yaml covers the v2026-04 checkout surface (/api/checkout/v2026-04/... paths — carts, cart auth, discounts, items, subscriptions, orders, enrollments, and store config); api-reference/public-v2025-06.yaml covers the Public SDK surface used by the @fluid-app FairShare SDK, including its parallel cart lifecycle, browser integrations, versioned payment callbacks, unversioned public utilities, and the cart price-override operation; api-reference/payment-v2026-04.yaml covers the v2026-04 payment gateway admin surface (/api/payment/v2026-04/... paths, bearer-authenticated — gateway CRUD, gateway purchase/authorize/$0-verify, transaction list/show and capture/void/credit, and merchant payment configuration); api-reference/payments-v2026-04.yaml covers the v2026-04 cart payment surface (/api/payments/v2026-04/carts/{cart_token}/... paths, authenticated by the cart token in the path with no bearer — payment-method selection, VGS card tokenization, 3D Secure verification, and PayPal/Braintree/Klarna/Apple Pay flows); api-reference/commerce-v2026-04.yaml covers the v2026-04 commerce order-editing surface (/api/v202604/orders/{order_id}/edits paths, bearer-authenticated — post-checkout order edits that atomically insert items and add adjustments/discounts, with an optional dry-run preview); api-reference/webhooks-v0.yaml covers the unversioned webhooks surface (/api/... paths — webhook registration, delivery payloads, callback registrations, company events, and webhook/callback schemas); api-reference/company-v0.yaml covers the legacy unversioned company admin surface (/api/... paths, bearer-authenticated except two operations that take no credential, POST /api/company (create a company) and GET /api/agreements/{id} — company settings and management, customers, users, roles, subscription plans, subscription bundles, subscriptions, media, pages, catch-ups, inventory levels, domains, agreements, and admin order actions). company-v0 caveats: it is the legacy v0 admin contract and its lists use flat page/per_page offset pagination, which is expected there despite the general cursor-pagination rule; where an operation exists in both company-v0 and a versioned spec, prefer the versioned spec — the subscriptions lifecycle (list/create/show/update, cancel, pause, reactivate, resume, retry, skip, failed-cycle-waiver, discounts) and subscription bundles are superseded by checkout-v2026-04, and company pages/media CRUD plus the public pages, categories, products, and media list endpoints are superseded by storefront-v2026-04. Subscription plan management (/api/subscription_plans, resource-wrapped {"subscription_plan": {...}} bodies) exists only in company-v0. api-reference/members-v2025-06.yaml covers the v2025-06 unified Member identity surface (/api/v2025-06/members/... paths, bearer-authenticated — member list/create/show/update, lookup by email/username/external_id/legacy_customer_id, member-type assignment, and the sponsor genealogy read). Prefer it over the customers and reps surfaces when the member type matters: /customers does not serialize member_type. api-reference/analytics-v2026-04.yaml covers the v2026-04 Home dashboard analytics surface (/api/v202604/analytics/dashboard/... paths, bearer-authenticated — read-only endpoints for the Home > Overview, Home > Live, and Home > Field tabs, each accepting an optional country ISO alpha-2 query param that scopes aggregations to a single country).
> api-reference/analytics-v0.yaml covers the unversioned analytics surface that backs the fluid-admin Traffic tab (/api/analytics/... and /api/analytics/traffic/... paths, bearer-authenticated — the legacy shares/views/visitors summary plus traffic overview, ranked campaigns, sources, geographies, flows, and per-rep breakdown, all sharing one reporting-period contract).
> Successful responses wrap the resource payload alongside a top-level integer status and a meta object.
> Portal Definition authoring edits and synchronizes the portal JSON resource graph. Widget Package authoring builds either a company-owned or Droplet-owned Remote DOM package. These are separate contracts; do not imply that one defines the other.
> For Widget Package worker code, use only @fluid-app/portal-sdk/widgets/worker. Use only the Portal Definition and Widget Package workflows and public entry points documented here; do not infer support for undocumented surfaces.
> Every portal function and declarative capability used by a widget must appear in that widget's uses array. Use the same typed function value in uses; do not invent capability-name strings.
> Widget styling must use the portal's semantic theme variables for colors, typography, spacing, radii, borders, focus, and charts whenever a token represents the visual decision. Do not create a separate light or dark palette or duplicate theme controls as widget properties.
> Prefer worker-safe Fluid UI components exported by @fluid-app/portal-sdk/widgets/worker when they fit the interaction. When no exported component fits, use semantic HTML, accessible behavior, and the portal theme variables.
> A Portal Definition push updates the remote working definition. A portal version is an immutable snapshot, and activation is a separate live release operation.
> The Help Center (/help/...) is for merchants, admins and reps using Fluid. Its admin pages mirror the admin's routes: the screen at admin.fluid.app/settings/taxes is documented at /help/admin/settings/taxes. Use the Help Center for how-to questions about the admin, and the Developer Platform and API Reference for building integrations.
> Help Center pages describe what a company admin sees. A reader's role can hide screens and actions; admins manage roles on Settings > Roles (/help/admin/settings/roles). If someone can't find a screen or button, their role's permissions are the first thing to check.
> Send people who need Fluid support to /help/getting-help. Don't invent support email addresses, phone numbers or response times.

# Write a batch of price-grid cells

> Writes up to 500 (variant, market, currency, price type, purchase type) cells as one whole-or-nothing batch. While the company is on the bundle pricing gate, a cell naming a bundle product's MASTER variant is refused for the whole request with 422 (error.details.error is bundle_price_edit_in_grid): the bundle price grid is the only editor of those prices. A cell naming a custom price type writes only the catalog (Retail::PriceListManager); one naming the system Retail or Wholesale tier also writes the legacy per-country columns through the phase 009 task 9 catalog-write path, with the same dual-write, Retail-to-Preferred copy and advisory locking that path already has. A Retail or Wholesale cell writes every country in the edited market whose currency equals the cell's currency, all with identical values, and is refused as validation_error when those countries already differ on a column the cell does not overwrite; if no country has that currency it is refused as currency_mismatch. If any cell is refused, nothing in the request is written. Requires admin tier, products.update permission, and Retail::PricingWrites.catalog_writes_enabled? for the company -- refused with 403 when that capability is off, independently of the permission check.



## OpenAPI

````yaml /api-reference/admin-v2025-06.yaml patch /api/v2025-06/prices
openapi: 3.1.0
info:
  title: Fluid Admin v2025-06 API
  description: |
    **Sandbox test companies.** A sandbox test company is linked to a live
    company. While the test company cannot be reached, because sandbox access
    is turned off for its live company or the live company was deleted, every
    operation in this API that resolves the company from the request's
    credential refuses a credential for it with `403` and the body
    `{"error": "<message>"}`, before the operation runs. The refusal is
    documented here once rather than on each operation.
  version: v2025-06
  contact:
    email: support@fluid.app
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
servers:
  - url: https://api.fluid.app
security: []
tags:
  - name: orders
    description: >-
      Read an order's detail and journey, and preview the points a refund of a
      selection returns before it is sent.
    x-fluid-section: Commerce
  - name: payments-reports
    description: >-
      Report on a company's payments over a period: transaction volume and
      amount, gateway latency, and approval and error rates, overall or per
      payment account.
    x-fluid-section: Payments
  - name: feature-flags
    description: >-
      Read and change a company's feature flags, and, for root admins, read the
      flag catalogue with each flag's summary and description.
    x-fluid-section: Developers
  - name: tokens
    description: >-
      Create and manage the company's API credentials: partner and public
      tokens, and the scoped clients that replace them for a company on identity
      sign-in, and for every company once scoped client creation is switched on
      for all.
    x-fluid-section: Developers
  - name: products
    description: >-
      Products, variants and their prices, including the per-price-type price
      grid of a bundle product.
    x-fluid-section: Commerce
  - name: tiktok
    description: >-
      The company's TikTok Shop connection, its logistics warehouses, category
      tree, product listings, and orders.
    x-fluid-section: Sales channels
  - name: ads
    description: >-
      The ad accounts a company advertises from. List what a connected Meta
      Business token can see and pick the ad account, Facebook Page and
      Instagram account Fluid launches ads into. Campaigns themselves stay on
      Meta.
    x-fluid-section: Sales channels
  - name: members
    description: >-
      Members and the member types that group them, including the company's
      default shopper member type.
    x-fluid-section: Members
paths:
  /api/v2025-06/prices:
    patch:
      tags:
        - products
      summary: Write a batch of price-grid cells
      description: >-
        Writes up to 500 (variant, market, currency, price type, purchase type)
        cells as one whole-or-nothing batch. While the company is on the bundle
        pricing gate, a cell naming a bundle product's MASTER variant is refused
        for the whole request with 422 (error.details.error is
        bundle_price_edit_in_grid): the bundle price grid is the only editor of
        those prices. A cell naming a custom price type writes only the catalog
        (Retail::PriceListManager); one naming the system Retail or Wholesale
        tier also writes the legacy per-country columns through the phase 009
        task 9 catalog-write path, with the same dual-write, Retail-to-Preferred
        copy and advisory locking that path already has. A Retail or Wholesale
        cell writes every country in the edited market whose currency equals the
        cell's currency, all with identical values, and is refused as
        validation_error when those countries already differ on a column the
        cell does not overwrite; if no country has that currency it is refused
        as currency_mismatch. If any cell is refused, nothing in the request is
        written. Requires admin tier, products.update permission, and
        Retail::PricingWrites.catalog_writes_enabled? for the company -- refused
        with 403 when that capability is off, independently of the permission
        check.
      operationId: admin_v2025_06_prices_update
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PricesWriteRequest'
      responses:
        '200':
          description: Every cell written.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PricesWriteResponse'
        '401':
          description: Missing or invalid credentials.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PricesUnauthorizedResponse'
        '403':
          description: >-
            The caller is not admin tier, lacks products.update permission, or
            the company does not have catalog writes enabled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PricesForbiddenResponse'
        '422':
          description: >-
            One or more cells could not be written (invalid parameters,
            cross-tenant reference, archived/Preferred tier, a currency
            mismatch, a manager rejection, or lock contention), or the batch
            itself was empty, oversized, or hit a request-level lock timeout.
            Nothing was written.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/PricesCellsInvalidResponse'
                  - $ref: '#/components/schemas/PricesErrorResponse'
      security:
        - bearer_auth: []
components:
  schemas:
    PricesWriteRequest:
      type: object
      required:
        - cells
      additionalProperties: false
      properties:
        cells:
          type: array
          minItems: 1
          maxItems: 500
          items:
            $ref: '#/components/schemas/PriceCellRequest'
    PricesWriteResponse:
      type: object
      required:
        - written_count
        - status
        - meta
      additionalProperties: false
      properties:
        written_count:
          type: integer
          description: >-
            The number of cells written; always every cell in the request, since
            a batch with any refused cell writes nothing.
        status:
          type: integer
          const: 200
        meta:
          $ref: '#/components/schemas/Meta'
    PricesUnauthorizedResponse:
      type: object
      required:
        - message
      additionalProperties: false
      properties:
        message:
          type: string
    PricesForbiddenResponse:
      oneOf:
        - type: object
          required:
            - message
          additionalProperties: false
          properties:
            message:
              type: string
        - type: object
          required:
            - error
          additionalProperties: false
          properties:
            error:
              type: string
    PricesCellsInvalidResponse:
      type: object
      description: At least one cell was refused; nothing in the request was written.
      required:
        - error
        - errors
        - status
        - meta
      additionalProperties: false
      properties:
        error:
          type: object
          required:
            - message
            - details
          additionalProperties: false
          properties:
            message:
              type: string
            details:
              type: object
        errors:
          type: array
          items:
            $ref: '#/components/schemas/PriceCellError'
        status:
          type: integer
        meta:
          $ref: '#/components/schemas/Meta'
    PricesErrorResponse:
      type: object
      description: >-
        A whole-request refusal with no per-cell breakdown (an empty or
        oversized cells array, or lock contention).
      required:
        - error
        - status
        - meta
      additionalProperties: false
      properties:
        error:
          type: object
          required:
            - message
            - details
          additionalProperties: false
          properties:
            message:
              type: string
            details:
              type: object
        status:
          type: integer
        meta:
          $ref: '#/components/schemas/Meta'
    PriceCellRequest:
      type: object
      description: >-
        One (variant, market, currency, price type, purchase type) write. A
        pricing_tier_id naming a custom price type writes only the catalog; one
        naming the system Retail or Wholesale tier also writes the legacy
        per-country columns through the phase 009 task 9 path. Preferred is its
        own price for a company that reads and writes the catalog. No key
        outside the ones listed here is accepted; an extra key refuses the whole
        cell.

        A system SUBSCRIPTION cell may carry only price -- compare_at_price and
        points have no subscription-specific storage on the legacy columns and
        are refused (:unsupported_subscription_field) rather than silently
        dropped. cv and qv are refused there too, UNLESS the company's
        subscription CV/QV is editable apart from one-time
        (PLAN-SUBSCRIPTION-VOLUME.md slice S1c), in which case both must be
        supplied as a pair or one at a time: both numeric sets them apart from
        one-time; ONE numeric (an older client) sets that side and pairs it with
        the other side's effective value (the stored override when the row is
        already an override, else the one-time value); both null reverts to
        following one-time (the row then carries the one-time cv/qv); a null
        beside a numeric, or a lone null, is refused
        (:subscription_volume_pair_required). Setting them (numeric) while this
        same edit clears the subscription price is refused
        (:subscription_volume_requires_price) -- there would be no row left to
        carry them; the same refusal applies when price is OMITTED (keep
        current) and no row exists yet at all. A null pair (revert) with no
        existing row is a no-op either way -- there is nothing to revert. A
        custom subscription cell may carry all five under the same pair rule
        when editable; its price list row stores them independently of the
        one-time row.
      required:
        - variant_id
        - market_id
        - currency_code
        - pricing_tier_id
        - purchase_type
      additionalProperties: false
      properties:
        variant_id:
          type: integer
        market_id:
          type: string
          format: uuid
        currency_code:
          type: string
          minLength: 3
          maxLength: 3
          description: >-
            Case-insensitive; normalized to uppercase before it is compared,
            locked, or persisted, so "usd" and "USD" resolve to and write the
            same price list.
        pricing_tier_id:
          type: string
          format: uuid
        purchase_type:
          type: string
          enum:
            - one_time
            - subscription
        price:
          type:
            - string
            - number
            - 'null'
          description: >-
            A decimal amount ("19.99") or a JSON number; required for a one_time
            cell. Must be finite -- NaN and Infinity (including a JSON number
            too large for a double, such as 1e400) are refused per cell rather
            than reaching Money arithmetic -- and must fit its destination
            column: `price_list_prices.price_cents` (bigint, custom price types)
            or `variant_countries.price` / `wholesale` (decimal(15,5), system
            Retail/Wholesale). A finite but oversized amount (1e100) is refused
            the same way, per cell. Required for a one_time cell; on a
            subscription cell, omitting the key leaves the row's current price
            alone (refused if no row exists yet, since there is then nothing to
            leave alone). For a company priced with the one-time fallback, an
            explicit null, or exactly 0, removes a custom price type's
            subscription price, or writes a system tier's legacy "unset" value
            -- both read as "follows the one-time price" (decision option 1: 0
            means unset, the same rule as a blank price). For a no-fallback
            company a blank or 0 subscription price is refused with
            price_required and the row is kept.
        compare_at_price:
          type:
            - string
            - number
            - 'null'
          description: >-
            Must be finite, and fit its destination column (see price). Omitting
            the key leaves the row's current value alone (a new custom row
            starts with no compare price). An explicit null clears it to no
            compare price.
        cv:
          type:
            - string
            - number
            - 'null'
          description: >-
            Must be finite, and fit its destination column (see price; a system
            tier's cv/qv are Volume cents, bigint, not the decimal(15,5) price
            columns).

            On a one_time cell, or a subscription cell outside the
            subscription-CV/QV gate (PLAN-SUBSCRIPTION-VOLUME.md slice S1c --
            see purchase_type): omitting the key leaves the row's current value
            alone (a new custom row starts at zero); an explicit null clears it
            to zero -- the storage has no other "cleared" state, including on a
            system Wholesale cell, whose legacy `pc_cv` column is otherwise
            nullable.

            On a subscription cell UNDER the gate, cv and qv are a pair (see the
            class description): both numeric sets them apart from one-time; both
            null reverts to following one-time (the row, if any, takes the
            tier's current one-time cv -- NOT zero); both omitted preserves a
            flagged override (volume_overrides_one_time) but refreshes an
            unflagged (inherited) row's cv/qv from the one-time row; ONE numeric
            value alone sets that side and pairs it with the other side's
            effective value (the stored override when the row is already an
            override, else the one-time value); a null beside a numeric, or a
            lone null, is refused (:subscription_volume_pair_required).
        qv:
          type:
            - string
            - number
            - 'null'
          description: >-
            Must be finite, and fit its destination column (see cv). Follows the
            exact same one_time/gate-off-subscription vs. gate-on-subscription
            distinction as cv -- see its description.
        points:
          type:
            - string
            - number
            - 'null'
          description: >-
            Omitting the key leaves the row's current value alone (a new custom
            row starts at zero). An explicit null clears it to zero. Must be a
            non-negative whole number (a decimal string is accepted but its
            value must have no fractional part) that fits a 32-bit integer; a
            fractional or out-of-range value is refused (:invalid_points), never
            truncated.
    Meta:
      type: object
      properties:
        request_id:
          type:
            - string
            - 'null'
        timestamp:
          type: string
          format: date-time
        pagination:
          type: object
          description: >-
            Rotulus cursor-pagination metadata (Api::MetaPagination#to_h),
            present on cursor-paginated responses such as GET
            /api/v2025-06/prices. total_count is only present when the endpoint
            opts into a total count.
          properties:
            cursor:
              type:
                - string
                - 'null'
            limit:
              type: integer
            prev_cursor:
              type:
                - string
                - 'null'
            next_cursor:
              type:
                - string
                - 'null'
            total_count:
              type: integer
    PriceCellError:
      type: object
      description: >-
        One refused cell, echoing the request fields it was sent with so a
        caller can report the refusal without re-reading its own request.
      required:
        - index
        - variant_id
        - market_id
        - currency_code
        - pricing_tier_id
        - purchase_type
        - fields
        - messages
      additionalProperties: false
      properties:
        index:
          type: integer
          description: The cell's position in the request's cells array.
        variant_id:
          type:
            - integer
            - 'null'
        market_id:
          type:
            - string
            - 'null'
        currency_code:
          type:
            - string
            - 'null'
        pricing_tier_id:
          type:
            - string
            - 'null'
        purchase_type:
          type:
            - string
            - 'null'
        fields:
          type: array
          description: >-
            The wire field(s) this refusal is about -- any of "price",
            "compare_at_price", "cv", "qv", "points" -- so the editor can
            highlight the exact input instead of every field in the cell. Empty
            when the refusal concerns the whole cell (an archived or
            cross-tenant price type, an unresolved variant/market/tier, a market
            with no country in the cell's currency). An array rather than a
            single nullable field because :unsupported_subscription_field can
            legitimately name more than one.
          items:
            type: string
            enum:
              - price
              - compare_at_price
              - cv
              - qv
              - points
        messages:
          type: array
          items:
            type: string
  securitySchemes:
    bearer_auth:
      type: http
      scheme: bearer

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.