> ## 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.
> 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. /api/company/v1 and /api/v1/... paths are documented in no spec here and must never be used (/api/v1.1/... is distinct and documented in company-v0). Use page/per_page offset pagination only where a spec documents it — in practice the unversioned company-v0 admin surface; every versioned surface uses cursor pagination.
> 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); api-reference/company-v0.yaml covers the legacy unversioned company admin surface (/api/... paths, bearer-authenticated — 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.
> Successful responses wrap the resource payload alongside a top-level integer status and a meta object.

# Get traffic geography

> Returns where the store's visitors were for a named reporting period.

The payload carries the resolved period window, the days of it geography
could answer, the country breakdown ranked by visitors (capped at ten
with a trailing "(other)" row folding the tail), the nested city
breakdown, and the visitor totals the shares divide by.

Unlike the channel split on traffic/sources, the country breakdown is a
partition: each visitor resolves to exactly one country, and visitors
with no placed visit in the window are reported as unplaced_visitors
rather than dropped, so the parts provably sum to the whole. Shares are
not renormalised — their gap is the unplaced traffic.




## OpenAPI

````yaml /api-reference/company-v0.yaml get /api/analytics/traffic/geography
openapi: 3.1.0
info:
  title: Fluid Company API
  version: v0
  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: []
paths:
  /api/analytics/traffic/geography:
    get:
      tags:
        - analytics
      summary: Get traffic geography
      description: |
        Returns where the store's visitors were for a named reporting period.

        The payload carries the resolved period window, the days of it geography
        could answer, the country breakdown ranked by visitors (capped at ten
        with a trailing "(other)" row folding the tail), the nested city
        breakdown, and the visitor totals the shares divide by.

        Unlike the channel split on traffic/sources, the country breakdown is a
        partition: each visitor resolves to exactly one country, and visitors
        with no placed visit in the window are reported as unplaced_visitors
        rather than dropped, so the parts provably sum to the whole. Shares are
        not renormalised — their gap is the unplaced traffic.
      operationId: company_v0_get_analytics_traffic_geography
      parameters:
        - name: period
          in: query
          required: false
          description: |
            Named reporting period (default last_30_days). Supported names:
            today, last_7_days, last_30_days, last_90_days, last_12_months,
            this_month, last_month. An unsupported name returns 422.
          schema:
            type: string
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompanyTrafficGeographyResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedResponse'
        '422':
          description: Unsupported period
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StandardErrorResponse'
      security:
        - bearer_auth: []
components:
  schemas:
    CompanyTrafficGeographyResponse:
      description: >-
        Store-traffic geography for a period — the resolved window, the days of
        it geography could answer, the ranked country breakdown, and a nested
        city breakdown carrying its own capture window, wrapped in the standard
        API response envelope.
      type: object
      additionalProperties: false
      required:
        - period
        - effective_window
        - capture_start
        - countries
        - cities
        - total_visitors
        - unplaced_visitors
        - status
        - meta
      properties:
        period:
          type: object
          additionalProperties: false
          required:
            - name
            - starts_on
            - ends_on
            - bucket
          properties:
            name:
              type: string
              description: The resolved period name (e.g. last_30_days).
            starts_on:
              type: string
              format: date
              description: Inclusive first day of the requested window (YYYY-MM-DD, UTC).
            ends_on:
              type: string
              format: date
              description: Inclusive last day of the requested window (YYYY-MM-DD, UTC).
            bucket:
              type: string
              description: The bucket granularity of the period window (always "day").
        effective_window:
          type:
            - object
            - 'null'
          description: |
            The days of the requested period that geography could answer — the
            requested window intersected with the days this store has carried
            placed visits. Every count in the payload is taken over this window
            rather than the requested one.

            Null when the two do not meet, which means the period lies entirely
            before this store began carrying placed visits. Null arrives with an
            empty countries list and zeroed totals, and is deliberately distinct
            from an answerable window whose counts are all zero — that means
            nobody came. Do not coerce one into the other.
          additionalProperties: false
          required:
            - starts_on
            - ends_on
          properties:
            starts_on:
              type: string
              format: date
              description: Inclusive first answerable day (YYYY-MM-DD, UTC).
            ends_on:
              type: string
              format: date
              description: Inclusive last answerable day (YYYY-MM-DD, UTC).
        capture_start:
          type:
            - string
            - 'null'
          format: date
          description: >
            The day this store first carried a placed visit (YYYY-MM-DD, UTC),
            or

            null when it never has. Reported whether or not the requested period

            overlaps it, so a client can say when geography began rather than

            presenting an empty period as an absence of traffic.
        countries:
          type: array
          description: >
            Countries ranked by resolved visitors, capped at ten and optionally

            ending in a single "(other)" row folding the tail.


            This is a partition, not an attribution split: each visitor resolves

            to exactly one country — the country of their most recent placed
            visit

            in the window — so the rows are disjoint and

            Σ(visitors) + unplaced_visitors == total_visitors holds exactly.
          items:
            type: object
            additionalProperties: false
            required:
              - code
              - name
              - visitors
              - page_views
              - share
              - other
            properties:
              code:
                type: string
                description: >-
                  ISO 3166-1 alpha-2 country code, or "(other)" for the folded
                  remainder row.
              name:
                type: string
                description: >-
                  The country's ISO short name, falling back to the code when
                  the ISO data does not recognise it, or "Other" for the
                  remainder row.
              visitors:
                type: integer
                description: Distinct bot-free visitors who resolved to this country.
              page_views:
                type: integer
                description: >-
                  Gross page views from this country over the window — bots and
                  cookie-less visits included, read from the daily country
                  rollup. Reconciles with no visitor number on this payload, by
                  design.
              share:
                type: number
                minimum: 0
                maximum: 1
                description: >-
                  The country's visitor share of total_visitors, rounded to four
                  decimals.
              other:
                type: boolean
                description: >-
                  True for the single folded remainder row, false for a real
                  country.
        cities:
          type: object
          description: |
            The city breakdown, nested rather than listed beside the countries
            because it answers a different set of days. City capture began after
            country capture, so this object carries its own capture_start, its
            own effective_window, and its own total_visitors.

            cities.total_visitors will usually differ from the top-level
            total_visitors, because each counts the visitors of its own window.
            Both are correct. Divide city shares by cities.total_visitors and
            never by the top-level one.
          additionalProperties: false
          required:
            - capture_start
            - effective_window
            - rows
            - total_visitors
            - cityless_visitors
            - unplaced_visitors
          properties:
            capture_start:
              type:
                - string
                - 'null'
              format: date
              description: |
                The day this store first carried a visit placed to a city
                (YYYY-MM-DD, UTC), or null when it never has. Later than the
                top-level capture_start, since the edge began sending cities
                after it began sending countries. Reported whether or not the
                requested period overlaps it, so a client can say when city
                capture began rather than presenting unrecorded days as an
                absence of traffic.
            effective_window:
              type:
                - object
                - 'null'
              description: |
                The days of the requested period the city breakdown could
                answer — the requested window intersected with the days this
                store has carried visits placed to a city. Every figure inside
                this object is taken over this window, never the top-level one.

                Null when the two do not meet, which means the period lies
                entirely before this store began carrying cities. Null arrives
                with empty rows and zeroed totals, and is deliberately distinct
                from an answerable window whose counts are all zero — that means
                nobody came.
              additionalProperties: false
              required:
                - starts_on
                - ends_on
              properties:
                starts_on:
                  type: string
                  format: date
                  description: Inclusive first answerable day (YYYY-MM-DD, UTC).
                ends_on:
                  type: string
                  format: date
                  description: Inclusive last answerable day (YYYY-MM-DD, UTC).
            rows:
              type: array
              description: |
                Cities ranked by resolved visitors, capped at ten and optionally
                ending in a single remainder row folding the tail.

                A partition, like the countries above: each visitor resolves to
                exactly one city — the city of their most recent city-carrying
                visit in the window — so the rows are disjoint and
                Σ(visitors) + cityless_visitors + unplaced_visitors ==
                total_visitors holds exactly.
              items:
                type: object
                additionalProperties: false
                required:
                  - country_code
                  - subdivision
                  - city
                  - name
                  - visitors
                  - page_views
                  - share
                  - other
                properties:
                  country_code:
                    type:
                      - string
                      - 'null'
                    description: >-
                      ISO 3166-1 alpha-2 code of the country the city is in, or
                      null for the folded remainder row, which names no place.
                  subdivision:
                    type:
                      - string
                      - 'null'
                    description: >-
                      CLDR subdivision id — the country code followed by the ISO
                      3166-2 suffix, e.g. USUT — or null when the edge resolved
                      a city but no subdivision, and for the remainder row.
                  city:
                    type: string
                    description: >-
                      The city name as the edge resolved it, or "(other)" for
                      the folded remainder row. Not unique on its own — the
                      place is the country, subdivision and city together.
                  name:
                    type: string
                    description: >-
                      Display label — "City, Subdivision, Country", dropping the
                      subdivision segment when the row carries none, and "Other"
                      for the remainder row.
                  visitors:
                    type: integer
                    description: Distinct bot-free visitors who resolved to this city.
                  page_views:
                    type: integer
                    description: >-
                      Gross page views from this city over the city window —
                      bots and cookie-less visits included, read from the daily
                      city rollup. Reconciles with no visitor number on this
                      payload, by design.
                  share:
                    type: number
                    minimum: 0
                    maximum: 1
                    description: >-
                      The city's visitor share of cities.total_visitors, rounded
                      to four decimals.
                  other:
                    type: boolean
                    description: >-
                      True for the single folded remainder row, false for a real
                      city.
            total_visitors:
              type: integer
              description: >-
                Total distinct bot-free visitors over the city effective window,
                in a city or not. The denominator every city share divides by,
                and not interchangeable with the top-level total_visitors.
            cityless_visitors:
              type: integer
              description: |
                Visitors the edge placed in a country but not in a city over the
                city effective window. Kept separate from unplaced_visitors
                rather than folded into it: they are a coverage gap in the city
                signal, not visitors the edge failed to place at all.
            unplaced_visitors:
              type: integer
              description: >-
                Visitors with no placed visit at all in the city effective
                window — counted, not dropped, so the three states are
                exhaustive and the city rows provably sum to the whole.
        total_visitors:
          type: integer
          description: >-
            Total distinct bot-free visitors over the effective window, placed
            and unplaced together. The denominator every share divides by.
        unplaced_visitors:
          type: integer
          description: |
            Visitors with no placed visit in the effective window — counted, not
            dropped, so the country breakdown provably sums to the whole.

            Shares are against total_visitors and are deliberately not
            renormalised, so Σ(shares) is the placed fraction rather than 1. The
            gap is this number, and it is meant to stay visible.
        status:
          type: integer
          description: The HTTP status code echoed in the response envelope.
        meta:
          type: object
          additionalProperties: false
          required:
            - request_uuid
            - timestamp
          properties:
            request_uuid:
              type:
                - string
                - 'null'
              description: The request correlation id, or null when unset.
            timestamp:
              type: string
              format: date-time
              description: ISO 8601 timestamp when the response was built.
    UnauthorizedResponse:
      type: object
      properties:
        message:
          type: string
      required:
        - message
    StandardErrorResponse:
      description: >-
        Common legacy error response envelope. Older endpoints may return one or
        more of these fields depending on the controller path.
      type: object
      properties:
        message:
          type: string
        error:
          $ref: '#/components/schemas/ErrorMessage'
        error_message:
          $ref: '#/components/schemas/ErrorMessage'
        errors:
          $ref: '#/components/schemas/ErrorBag'
        meta:
          $ref: '#/components/schemas/Meta'
      additionalProperties:
        $ref: '#/components/schemas/JsonValue'
    ErrorMessage:
      description: An API error message represented as text or structured JSON.
      anyOf:
        - type: string
        - $ref: '#/components/schemas/ErrorBag'
        - type: 'null'
    ErrorBag:
      description: >-
        Validation errors keyed by field, a list of errors, a single error
        message, or null when no structured error details are available.
      anyOf:
        - type: string
        - type: array
          items:
            $ref: '#/components/schemas/ErrorValue'
        - type: object
          additionalProperties:
            $ref: '#/components/schemas/ErrorValue'
        - type: 'null'
    Meta:
      type:
        - object
        - 'null'
      properties:
        request_id:
          type:
            - string
            - number
            - integer
            - 'null'
        timestamp:
          type:
            - string
            - number
            - integer
            - 'null'
          format: date-time
    JsonValue:
      description: >-
        Any valid JSON value for provider, integration, theme, metadata, or
        other dynamic payloads whose keys are not fixed by the API contract.
      anyOf:
        - type: string
        - type: number
        - type: boolean
        - type: 'null'
        - type: array
          items:
            $ref: '#/components/schemas/JsonValue'
        - type: object
          additionalProperties:
            $ref: '#/components/schemas/JsonValue'
    ErrorValue:
      description: A validation or API error value.
      anyOf:
        - type: string
        - type: array
          items:
            type: string
        - type: object
          additionalProperties:
            $ref: '#/components/schemas/JsonValue'
  securitySchemes:
    bearer_auth:
      type: http
      scheme: bearer
      description: Bearer token authentication

````