> ## 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.
> Lists use cursor pagination via the page[cursor] and page[limit] query params; follow meta.pagination.next_cursor until it is null.
> Never use /api/company/v1 or /api/v1 paths, page/per_page params, or offset pagination — they are legacy.
> 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/auth-v0.yaml covers the unversioned auth surface (/api/... paths — authentication, MFA, social auth, and token exchange); 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).
> Successful responses wrap the resource payload alongside a top-level integer status and a meta object.

# Show a playlist (public, by slug)

> PUBLIC endpoint — NO authentication required. Returns any non-discarded
playlist by slug: `draft`, `inactive`, and live playlists all resolve
(so preview / pre-launch links work). Only discarded playlists return 404.
Non-live playlists render with `noindex` metadata. The response uses the
`:detail` view — includes `library_items` — not the lean catalog card.




## OpenAPI

````yaml /api-reference/storefront-v2026-04.yaml get /api/v202604/playlists/{slug}
openapi: 3.1.0
info:
  title: Fluid Storefront v2026-04 API
  version: v2026-04
  description: |
    The modernized storefront surface, covering all eight storefront
    resources: **Categories**, **Collections**, **Products**, **Posts**,
    **Pages**, **Media**, **Enrollment Packs**, and **Playlists**.

    ## Two surfaces

    Every resource is exposed twice, and the pair is the thing to
    understand first:

    - **Public** — `/api/v202604/{resource}` and
      `/api/v202604/{resource}/{slug}`. Unauthenticated
      (`security: []`); the store is resolved from the request
      subdomain. Addressed by `slug`, the same identifier that appears
      in `canonical_url`. Returns only live rows — non-live content
      never leaks here.
    - **Company** — `/api/v202604/company/{resource}`. Bearer-token
      authenticated, addressed by `id`, and returns every row whatever
      its lifecycle state, so an admin can manage drafts and archives.
      Full CRUD plus the standardized `shares`, `lighthouse`, and
      `compliance` member actions on each resource.

    Path segments use the storefront's nouns, not the database's:
    `enrollment-packs` and `playlists` (the Library model).

    ## One response shape

    Every resource response opens with the same canonical "common top",
    in the same order, before any resource-specific fields:

        id, slug, title, description, image_url, canonical_url, images,
        active, status, publish_at, seo, metafields, languages

    That means a consumer can render any storefront resource from one
    code path. `seo` is always present and always fully populated — the
    server resolves it through a priority chain (SEO record → resource
    field → store default → built-in), so there is no client-side
    fallback logic and the API can never disagree with the rendered
    `<head>`. `metafields`, `languages`, and (where applicable)
    `countries` are always arrays, never null.

    ## Lifecycle

    `status` is the canonical four-value contract — `draft`,
    `scheduled`, `published`, `archived` — and resolves at the API
    boundary: a stored `scheduled` row whose `publish_at` has passed
    reads as `published` with no write required. `active` is the
    separate merchant on/off toggle; a resource is live only when it is
    both active and resolved-published. Company create and update
    auto-queue Lighthouse and compliance scans, so the scores on the
    member actions always describe the current state of the resource.

    ## Listing and pagination

    Index endpoints share one query surface: `lang`, `q` (Elasticsearch
    over title and description), `filter[…]`, `sort`, and cursor
    pagination via `page[cursor]` / `page[limit]` (default 25, max 100).
    Follow `meta.pagination.next_cursor`; do not construct cursors.
    Products additionally take `country`, which selects the
    country-relative price and currency AND gates availability.

    Categories and Collections expose their products from their own
    path — `GET /api/v202604/categories/{slug}/products` — which is the
    products catalog scoped to one owner, with the same card shape,
    filters, sorting, and pagination.

    ## Translating a resource

    Translations are written inline on the resource's own create/update.
    There is no separate translations endpoint and no `translations`
    object in the payload — the `lang` query parameter selects the locale
    a request reads or writes.

    **Read** a locale with `lang` on any GET:

        GET /api/v202604/company/collections/4821?lang=fr

    Translated fields (`title`, `description`, `image_url`, and each
    resource's own translated fields) come back inline in that locale.
    On the public surface any field with no `fr` translation falls back
    to its `en` value, so a partially-translated resource never renders
    blank. The `languages` array on every response lists exactly the
    locales that carry a translation, always including the default.

    **Write** a locale with `lang` on POST/PATCH:

        PATCH /api/v202604/company/collections/4821?lang=fr
        { "collection": { "title": "Essentiels bien-être" } }

    Omitting `lang` writes the store's default locale — the store's own
    default language, not the platform's. A store selling in French
    treats `lang=fr` as its default write.

    A translation PATCH is TEXT ONLY: against any other locale,
    everything but the translated fields is ignored, so translating a
    title can never regenerate the slug, move `status`, or change
    `country_isos`, metafields, SEO, or associations. Send those in a
    request against the store's default locale instead.

    Creating is different: POST always writes the store's default locale
    whatever `lang` says, and keeps its full payload. A resource created
    under a translation would have no value in the store's own language
    and would take its slug from the translation. `lang` is still
    validated on POST, so an unsupported locale is a 422 rather than a
    surprise.

    A `lang` the store does not sell in returns 422 — translations are
    never written to a locale the store has not enabled. Add the language
    to the store first.

    The typical flow is: create in the default locale, then one PATCH per
    additional locale carrying only the translated text.
  contact:
    email: support@fluid.app
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
servers:
  - url: https://api.fluid.app
  - url: https://{company}.fluid.app
    description: Production server with company subdomain
    variables:
      company:
        default: myco
        description: Company subdomain
security: []
tags:
  - name: storefront
    description: |
      Unauthenticated public surface that backs storefront pages and
      AEO crawlers. All operations are `security: []`.
  - name: company
    description: |
      Token-authenticated company surface that backs the admin UI and
      integrations.
paths:
  /api/v202604/playlists/{slug}:
    get:
      tags:
        - storefront
      summary: Show a playlist (public, by slug)
      description: >
        PUBLIC endpoint — NO authentication required. Returns any non-discarded

        playlist by slug: `draft`, `inactive`, and live playlists all resolve

        (so preview / pre-launch links work). Only discarded playlists return
        404.

        Non-live playlists render with `noindex` metadata. The response uses the

        `:detail` view — includes `library_items` — not the lean catalog card.
      operationId: storefrontPlaylistShow
      parameters:
        - name: slug
          description: The URL slug of the playlist.
          in: path
          required: true
          schema:
            type: string
          example: primex-wellness-playlist
        - name: lang
          in: query
          schema:
            type: string
          description: ISO locale for the response. Falls back to `en`.
      responses:
        '200':
          description: Playlist detail.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaylistShowResponse'
        '404':
          $ref: '#/components/responses/NotFound'
      security: []
components:
  schemas:
    PlaylistShowResponse:
      allOf:
        - $ref: '#/components/schemas/Envelope'
        - type: object
          required:
            - playlist
          properties:
            playlist:
              $ref: '#/components/schemas/Playlist'
    Envelope:
      type: object
      description: |
        Shared success envelope for list / show / create / update
        responses: the resource payload is returned alongside a top-level
        integer `status` and `meta`. Composed onto each resource response
        via `allOf`.
      required:
        - status
        - meta
      properties:
        status:
          type: integer
          example: 200
        meta:
          $ref: '#/components/schemas/Meta'
    Playlist:
      description: |
        Public Playlist read shape — the storefront (unauthenticated)
        projection. Closed with `unevaluatedProperties: false` so any
        authenticated-only field (notably `custom_slug`) leaking onto the
        public surface is caught in conformance.
      allOf:
        - $ref: '#/components/schemas/PlaylistBase'
      unevaluatedProperties: false
    ErrorResponse:
      type: object
      required:
        - error
        - status
        - meta
      properties:
        error:
          type: object
          required:
            - message
          properties:
            message:
              type: string
            details:
              type: object
              additionalProperties: true
        status:
          type: integer
        meta:
          $ref: '#/components/schemas/Meta'
    Meta:
      type: object
      description: |
        Response metadata included on every successful response body.
        `request_id` and `timestamp` are always present; list endpoints
        also add `pagination`.
      required:
        - request_id
        - timestamp
      properties:
        request_id:
          type:
            - string
            - 'null'
        timestamp:
          type: string
          format: date-time
        pagination:
          $ref: '#/components/schemas/Pagination'
    PlaylistBase:
      type: object
      description: |
        Storefront Playlist (backed by the Library model). The first thirteen
        fields are the canonical common top shared by every storefront resource
        in v202604. Playlists are schedulable but have no archived state:
        `status` derives from the `active` toggle + `publish_at` (active +
        future publish_at → `scheduled`). `total_items_count` and the
        `library_items` array are
        returned only on the SHOW response (`:detail` view), not the catalog
        card, so neither is in the schema-level required set.
      required:
        - id
        - slug
        - title
        - description
        - image_url
        - canonical_url
        - images
        - active
        - status
        - publish_at
        - seo
        - metafields
        - languages
        - kind
        - countries
      properties:
        id:
          type: integer
          example: 3812
        slug:
          type: string
          example: primex-wellness-playlist
        title:
          type: string
          example: Primex Wellness Playlist
        description:
          type:
            - string
            - 'null'
          example: A curated collection of wellness resources for Primex members.
        image_url:
          type:
            - string
            - 'null'
          example: https://cdn.fluid.app/libraries/3812.png
        canonical_url:
          type: string
          example: https://acme.fluid.app/home/libraries/primex-wellness-playlist
        images:
          $ref: '#/components/schemas/ResponsiveImageSet'
        active:
          type: boolean
          example: true
        status:
          type: string
          enum:
            - draft
            - scheduled
            - published
          description: |
            Lifecycle, derived from `active` + `publish_at`. `published` =
            active and live now; `scheduled` = active with a future
            `publish_at` (auto-goes-live when it passes); `draft` =
            inactive. Playlists have no archived state. "Live" =
            `active == true` AND resolved `status == published`.
          example: published
        publish_at:
          type:
            - string
            - 'null'
          format: date-time
          description: When a scheduled playlist goes live. null = no schedule.
          example: null
        seo:
          $ref: '#/components/schemas/Seo'
        metafields:
          type: array
          items:
            $ref: '#/components/schemas/Metafield'
        languages:
          type: array
          items:
            type: string
          example:
            - en
            - fr
        kind:
          type: string
          enum:
            - manual
            - auto
          description: Curation mode. `manual` = hand-picked items, `auto` = rule-based.
          example: manual
        total_items_count:
          type: integer
          description: Number of items currently in the playlist.
          example: 5
        countries:
          type: array
          items:
            type: string
          description: |
            ISO codes restricting availability. Empty array means available
            in every country the company sells in (unrestricted).
          example:
            - GB
            - US
        library_items:
          type: array
          description: SHOW only. Ordered items in this playlist (up to 50).
          items:
            type: object
            properties:
              id:
                type: integer
              relateable_type:
                type: string
              relateable_id:
                type: integer
              order:
                type: integer
    Pagination:
      type: object
      required:
        - cursor
        - limit
        - prev_cursor
        - next_cursor
      properties:
        cursor:
          type:
            - string
            - 'null'
        limit:
          type: integer
          example: 25
        prev_cursor:
          type:
            - string
            - 'null'
        next_cursor:
          type:
            - string
            - 'null'
    ResponsiveImageSet:
      type:
        - object
        - 'null'
      description: |
        Three ImageKit-transformed renditions of the resource's
        primary image: 200/600/1200 wide, scale-down only, format
        auto-negotiated. `null` when the resource has no image.
      properties:
        thumb:
          $ref: '#/components/schemas/ImageRendition'
        medium:
          $ref: '#/components/schemas/ImageRendition'
        large:
          $ref: '#/components/schemas/ImageRendition'
    Seo:
      type: object
      description: >
        Render-ready search-engine optimization metadata. ALWAYS present

        on every storefront response — never null. Values are resolved

        server-side via this priority chain:


        - `title`         SEO title → resource title → company name

        - `description`   SEO desc → resource desc → company default → "Live
        Free."

        - `image_url`     SEO image → resource og_image → resource image →
        company default → hardcoded

        - `block_crawler` SEO block_crawler (false when no SEO row)


        Frontends, AEO crawlers, and SDKs can render directly from these

        values — no client-side fallback logic, no chance of HTML <head>

        and JSON API drifting. `id` is exposed only on the authenticated

        surface and distinguishes a merchant-configured SEO row from

        inherited defaults.
      required:
        - title
        - description
        - image_url
        - block_crawler
      properties:
        title:
          type: string
          example: Summer Sale — Up to 40% Off
          description: Resolved title. Never null.
        description:
          type: string
          example: Shop the July clearance event before it ends.
          description: Resolved description. Never null.
        image_url:
          type: string
          example: >-
            https://ik.imagekit.io/fluidapp/tr:w-1200,h-630,c-maintain_ratio,fo-auto,f-auto,q-80/seo/9215.png
          description: Resolved OG-ready 1200×630 image URL. Never null.
        block_crawler:
          type: boolean
          example: false
          description: When true, the resource page emits a noindex meta tag.
        id:
          type:
            - integer
            - 'null'
          example: 9215
          description: >-
            Authenticated-only. SearchEngineOptimizer row id when one exists;
            null when the values are inherited from the resource / company /
            hardcoded defaults.
    Metafield:
      type: object
      required:
        - namespace
        - key
        - value
        - value_type
      properties:
        namespace:
          type: string
          example: custom
        key:
          type: string
          example: material
        value:
          example: cotton
        value_type:
          type: string
          example: single_line_text_field
        description:
          type:
            - string
            - 'null'
          example: The primary material used in this product
        created_at:
          type: string
          format: date-time
          example: '2026-05-01T12:00:00Z'
        updated_at:
          type: string
          format: date-time
          example: '2026-05-01T12:00:00Z'
        locked:
          type:
            - boolean
            - 'null'
          example: false
    ImageRendition:
      type: object
      required:
        - url
        - width
      properties:
        url:
          type: string
          example: >-
            https://ik.imagekit.io/fluidapp/tr:w-600,c-at_max,f-auto,q-75/categories/4821.png
        width:
          type: integer
          example: 600
  responses:
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

````