openapi: 3.1.0
info:
  title: Admetrics Query API
  version: "3"
  summary: Query Admetrics e-commerce marketing analytics over HTTP.
  description: >
    The Admetrics API is the data engine behind your dashboard. Describe the
    metrics and dimensions you want, the filters and date range to apply, and the shape
    of the response; it resolves attribution, blends ad-platform spend with shop
    revenue, and returns analysis-ready rows.


    Authenticate every request with a JWT bearer token (a long-lived API key issued by
    Admetrics support, or an access token from `POST /api/users/login`).
  contact:
    name: Admetrics Support
    email: support@admetrics.io
    url: https://kb.admetrics.io/en/articles/10242604-how-to-retrieve-data-using-api-endpoints
  license:
    name: Proprietary

servers:
  - url: https://app.admetrics.io
    description: Production

security:
  - bearerAuth: []

tags:
  - name: Auth
    description: Obtain and refresh access tokens.
  - name: Query
    description: Run synchronous or asynchronous data queries.
  - name: Discovery
    description: Introspect metrics, dimensions and dimension values.
  - name: Health
    description: Liveness probes.

paths:
  /api/users/login:
    post:
      tags: [Auth]
      summary: Log in
      description: >
        Log in with dashboard credentials to obtain tokens when your integration
        uses user-based authentication.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [login, password]
              properties:
                login: { type: string, example: analyst@brand.com }
                password: { type: string, format: password }
      responses:
        "200":
          description: Authenticated
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TokenResponse" }

  /api/users/refresh:
    get:
      tags: [Auth]
      summary: Refresh the access token
      description: >
        Send the refresh token as the bearer to mint a new access token.
      responses:
        "200":
          description: New tokens
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TokenResponse" }

  /apiv3/query:
    post:
      tags: [Query]
      summary: Run a query synchronously
      operationId: runQuerySync
      description: >
        Execute a query and return the final result in one response. This is
        best for smaller server-to-server calls that can wait for completion.
        Set `shape.format` explicitly so your integration controls the response shape.
      parameters:
        - $ref: "#/components/parameters/AcceptLanguage"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/Query" }
      responses:
        "200":
          description: Final query result rendered in the requested format.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/QueryResult" }
        "400": { $ref: "#/components/responses/Error" }
        "401": { $ref: "#/components/responses/Error" }
        "504": { $ref: "#/components/responses/Error" }

  /apiv3/queue_query:
    post:
      tags: [Query]
      summary: Run a query (asynchronous)
      operationId: runQueryAsync
      description: >
        Submit a query job. The API validates the body, runs the query in the background,
        and returns URLs to poll for status and fetch the result.
      parameters:
        - $ref: "#/components/parameters/AcceptLanguage"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/Query" }
      responses:
        "200":
          description: Query accepted and queued.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/QueueResponse" }
        "400": { $ref: "#/components/responses/Error" }
        "401": { $ref: "#/components/responses/Error" }

  /apiv3/query_status/{results_id}:
    get:
      tags: [Query]
      summary: Poll query status
      parameters:
        - $ref: "#/components/parameters/ResultsId"
      responses:
        "200":
          description: Current task status.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/QueryStatus" }
        "404": { $ref: "#/components/responses/Error" }

  /apiv3/query_result/{results_id}:
    get:
      tags: [Query]
      summary: Fetch query result
      parameters:
        - $ref: "#/components/parameters/ResultsId"
      responses:
        "200":
          description: Result rows in the format requested at enqueue time.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/QueryResult" }
        "504": { $ref: "#/components/responses/Error" }

  /apiv3/context:
    get:
      tags: [Discovery]
      summary: List metrics, dimensions, and supported query metadata
      parameters:
        - name: keys
          in: query
          required: true
          description: >
            Comma-separated sections to return. Publicly useful sections include
            `dimensions`, `metrics`, `type_defs`, `attribution_models`,
            `attribution_windows`, `horizon_days`, and `formats`.
          schema:
            type: string
            example: dimensions,type_defs,metrics,attribution_models,attribution_windows,formats,horizon_days
        - name: stack_name
          in: query
          required: false
          description: >
            Stack to inspect. Use the same value you send in `info.stack_name`:
            fetch `/api/shopify/activeclients`, find your `client_id`, and copy
            its `stack` value. If omitted, Admetrics resolves it from your request
            context when possible.
          schema: { type: string, example: chprod }
        - $ref: "#/components/parameters/AcceptLanguage"
      responses:
        "200":
          description: Requested context sections.
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: integer, example: 200 }
                  api_version: { type: string, example: v3 }
                  data: { type: object, additionalProperties: true }

  /apiv3/index:
    get:
      tags: [Discovery]
      summary: Enumerate a dimension's values (simple)
      parameters:
        - name: dimension
          in: query
          required: true
          schema: { type: string, example: campaign_name }
        - name: like
          in: query
          schema: { type: string }
        - name: filter
          in: query
          description: Alias for `like`.
          schema: { type: string }
        - name: stack_name
          in: query
          required: false
          description: >
            Stack to inspect. Use the same value you send in `info.stack_name`:
            fetch `/api/shopify/activeclients`, find your `client_id`, and copy
            its `stack` value.
          schema: { type: string, example: chprod }
        - name: tables
          in: query
          required: false
          schema: { type: string }
        - name: views
          in: query
          required: false
          description: Alias for `tables`.
          schema: { type: string }
        - name: offset
          in: query
          schema: { type: integer, default: 0 }
        - name: limit
          in: query
          schema: { type: integer, default: 100 }
      responses:
        "200": { $ref: "#/components/responses/IndexResponse" }
    post:
      tags: [Discovery]
      summary: Enumerate a dimension's values (filtered)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [dimension, filters]
              properties:
                dimension: { type: string, example: dow }
                stack_name:
                  type: string
                  description: >
                    Use the same client `stack` value you send in `info.stack_name`.
                    Fetch `/api/shopify/activeclients`, find your `client_id`,
                    and copy its `stack` value.
                  example: chprod
                offset: { type: integer, default: 0 }
                limit: { type: integer, default: 100 }
                tables: { type: string }
                filters:
                  type: array
                  items: { $ref: "#/components/schemas/Filter" }
      responses:
        "200": { $ref: "#/components/responses/IndexResponse" }

  /apiv3/health/api:
    get:
      tags: [Health]
      summary: Liveness probe (unauthenticated)
      security: []
      responses:
        "200":
          description: ok
          content:
            text/plain: { schema: { type: string, example: ok } }

  /apiv3/health/jwt:
    get:
      tags: [Health]
      summary: Authenticated liveness probe
      responses:
        "200":
          description: ok
          content:
            text/plain: { schema: { type: string, example: ok } }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: API key (valid 2 years) or access token from /api/users/login.

  parameters:
    AcceptLanguage:
      name: Accept-Language
      in: header
      required: false
      description: >
        BCP-47 locale controlling number, currency, and date formatting in
        formatted results and context metadata.
      schema: { type: string, example: en-US }
    ResultsId:
      name: results_id
      in: path
      required: true
      description: ID returned by queue_query (tail of status_url / result_url).
      schema: { type: string }

  responses:
    Error:
      description: Error envelope.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    IndexResponse:
      description: Dimension values with counts.
      content:
        application/json:
          schema:
            type: object
            properties:
              status: { type: integer, example: 200 }
              api_version: { type: string, example: v3 }
              data:
                type: array
                items:
                  type: object
                  properties:
                    id: { type: string, example: Monday }
                    count: { type: integer, example: 12345 }

  schemas:
    TokenResponse:
      type: object
      properties:
        user: { type: string }
        host: { type: string }
        proto: { type: string }
        url: { type: string }
        access_token: { type: string }
        refresh_token: { type: string }

    QueryResult:
      type: object
      properties:
        status: { type: integer, example: 200 }
        api_version: { type: string, example: v3 }
        query_id: { type: string }
        query_info:
          type: object
          additionalProperties: true
        shape:
          type: object
          additionalProperties: true
        data:
          description: Result payload in the format requested by `shape.format`.
          oneOf:
            - type: array
              items: { type: object, additionalProperties: true }
            - type: array
              items:
                type: array
                items: {}
            - type: object
              additionalProperties: true
        totals:
          type: object
          additionalProperties: true
        aggregates:
          type: object
          additionalProperties: true

    Selectable:
      type: object
      required: [id]
      properties:
        id:
          type: string
          description: A metric or dimension ID.
        as:
          type: string
          description: Optional output alias.
        filter:
          type: object
          description: Optional selection-scoped filter payload.
          additionalProperties: true
        params:
          type: object
          description: Per-metric tuning. Use values advertised by `GET /apiv3/context`
            for attribution settings such as model, window, and horizon.
          properties:
            attribution_model:
              type: string
              description: >-
                Attribution model ID returned by `GET /apiv3/context?keys=attribution_models`.
                Example only: `last_touch_non_direct`.
              example: last_touch_non_direct
            attribution_window:
              type: string
              description: >-
                Attribution window ID returned by `GET /apiv3/context?keys=attribution_windows`.
                Example only: `30`.
              example: "30"
            horizon_days:
              type: string
              description: >-
                Horizon ID returned by `GET /apiv3/context?keys=horizon_days`.
                Example only: `90`.
              example: "90"
            lift: { type: boolean }
            baseline: { type: string, enum: [debiased] }

    SortSelection:
      type: object
      required: [id]
      properties:
        id:
          type: string
          description: Metric or dimension ID to sort by.
        direction:
          type: string
          enum: [asc, desc]
          default: asc
        output:
          type: string
          description: Output variant to sort on when a selection exposes multiple outputs.
          example: vals
        case_sensitive:
          type: boolean
          default: false

    Filter:
      type: object
      description: >
        Canonical form { id, filter }. Operators: $eq, $ne, $gt, $gte, $lt, $lte,
        $in, $nin, $regex / $match (+ $notregex / $notmatch — RE2 contains-match),
        $exists / $notexists. Within a regex, `|` is alternation (OR); $eq / $ne also
        accept a `||`-joined value as an OR set (e.g. "facebook||google||tiktok").
        Use this canonical shape for integrations.
      properties:
        id: { type: string, example: campaign_name }
        filter:
          type: object
          description: Operator map.
          additionalProperties: true
          example: { "$regex": "Prospecting|Retargeting" }
        params:
          type: object
          description: Optional filter-scoped parameters such as attribution settings.
          additionalProperties: true

    Query:
      type: object
      properties:
        query_id:
          type: string
          format: uuid
          description: Client-generated UUID v4 (tracing + cache key).
        stack_name:
          type: string
          description: >
            Compatibility alias for `info.stack_name`. Prefer `info.stack_name`
            for new integrations and send the client's `stack` value from
            `/api/shopify/activeclients`.
          example: chprod
        select:
          type: object
          properties:
            dimensions:
              type: array
              items: { $ref: "#/components/schemas/Selectable" }
            metrics:
              type: array
              items: { $ref: "#/components/schemas/Selectable" }
        filters:
          type: array
          items: { $ref: "#/components/schemas/Filter" }
        shape:
          type: object
          properties:
            format:
              type: string
              enum: [records, columnar, rows, csv, xlsx, txt, arrow]
              default: columnar
            offset: { type: integer, default: 0 }
            limit: { type: integer, default: -1 }
            sort:
              description: >
                Sort selection. A single object or an array is accepted; one
                object is recommended for simple queries.
              anyOf:
                - $ref: "#/components/schemas/SortSelection"
                - type: array
                  items: { $ref: "#/components/schemas/SortSelection" }
            is_relative_date:
              type: boolean
              description: Set to `true` to generate the date range from
                `relative_date_type` and `relative_date_delta`.
            relative_date_type:
              type: string
              description: >-
                Relative window type. Supported examples include `year`,
                `quarter`, `month`, `isoWeek`, `today`, `yesterday`,
                `last-d-7`, and `last-m-6`.
              example: month
            relative_date_delta:
              type: object
              description: >-
                Offset object used with `relative_date_type`. Example:
                `{ "months": 1 }` with `relative_date_type: "month"` means
                "last month", and `{ "days": 7 }` with
                `relative_date_type: "last-d-7"` means "last 7 days".
              example:
                years: 0
                quarters: 0
                months: 1
                weeks: 0
                days: 0
              properties:
                years: { type: integer }
                quarters: { type: integer }
                months: { type: integer }
                weeks: { type: integer }
                days: { type: integer }
        options:
          type: object
          properties:
            aggregates:
              type: array
              items: { type: string }
              example: [sum]
            totals: { type: boolean, default: true }
            noise_filter: { type: boolean, default: false }
            timeout: { type: integer, description: Seconds; 0 = server default. }
            lang: { type: string }
            pretty: { type: boolean, default: false }
            simple_colnames: { type: boolean, default: true }
            column_desc: { type: boolean, default: false }
            column_infos: { type: boolean, default: false }
            precision: { type: integer, default: -1 }
            iso8601: { type: boolean, default: true }
            csv_sep: { type: string, default: "," }
            csv_header: { type: boolean, default: true }
            csv_decimal: { type: string, default: "." }
        info:
          type: object
          properties:
            stack_name:
              type: string
              description: >-
                Fetch `/api/shopify/activeclients`, find the object for the
                `client_id` you are querying, and copy its `stack` value here.
                Example: if that response includes `{ "client_id": "2133416",
                "stack": "chprod" }`, send `"stack_name": "chprod"`.
              example: chprod
            request_source:
              type: string
              description: >
                Caller identifier. Use a stable name
                such as `api|CLIENT_NAME` for traceability.
              example: api|CLIENT_NAME
      example:
        query_id: 8e0abc8c-4d0e-152f-901c-e1fed3cb8da5
        info:
          stack_name: chprod
          request_source: api|CLIENT_NAME
        select:
          dimensions: [{ id: month }, { id: campaign_name }]
          metrics:
            - { id: spend }
            - { id: revenue }
            - id: roas
              params: { attribution_model: last_touch_non_direct, attribution_window: "30" }
            - { id: poas }
        filters:
          - { id: date, filter: { "$gte": "2026-01-01", "$lte": "2026-03-31" } }
          - { id: client_id, filter: { "$in": ["2133416"] } }
        shape: { format: records, offset: 0, limit: 500 }
        options: { aggregates: [sum], timeout: 25 }

    QueueResponse:
      type: object
      properties:
        status: { type: integer, example: 200 }
        api_version: { type: string, example: v3 }
        status_url: { type: string, format: uri }
        result_url: { type: string, format: uri }
        cancel_url:
          type: string
          format: uri
          description: Reserved for future use. Ignore unless Admetrics support instructs you otherwise.

    QueryStatus:
      type: object
      properties:
        status: { type: integer, example: 200 }
        api_version: { type: string, example: v3 }
        query_id: { type: string }
        task_status:
          type: string
          enum: [pending, processing, done_ok, done_fail]
        updates:
          type: array
          items: { type: string }

    Error:
      type: object
      properties:
        status: { type: integer, example: 400 }
        msg: { type: string, example: "Unknown metric id: 'roass'." }
        api_version: { type: string, example: v3 }
