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

# Retrieve a custom result query

> Returns a custom result query by id. Beta.

This never recomputes: a query id names one already-computed result,
whatever its age. Rows carry metric values only: no lift against
Control, no confidence interval, no significance. For those, read
[`GET /experiments/{id}/results`](/api-reference/results/retrieve-the-results-snapshot).




## OpenAPI

````yaml /api-reference/openapi.yaml get /experiments/{id}/results/{query_id}
openapi: 3.1.0
info:
  title: ABConvert Public API
  version: 1.0.0-beta
  description: |
    Create, read, update, and run A/B tests on your Shopify store from your
    own code.

    This version covers test CRUD, lifecycle actions, archiving, results,
    order exports, scheduling, and token scopes. There are no webhooks: to
    see a test's current state, call
    [`GET /experiments/{id}`](/api-reference/experiments/retrieve-a-test).

    Every path, field, and identifier in this API spells the resource
    `experiment`. An experiment is a test: the same object you create and
    run in the ABConvert admin. These docs say "test" in prose.

    A finding is one problem the server found, carrying a `code`, a
    `message`, and a `severity`. An `error` finding blocks the request and
    returns 422. A `warning` finding rides along with a successful response,
    in `warnings`.

    Conventions shared by every endpoint (identifiers, data formats,
    pagination, sparse updates, idempotency, rate limits, per-type support,
    and feature availability) live in the
    [API overview](/api-reference/overview). Tokens and scopes live in
    [Authentication](/api-reference/authentication).
servers:
  - url: https://api.abconvert.io/v1
security:
  - bearerAuth: []
tags:
  - name: Experiments
    description: Create, read, update, and list tests.
  - name: Lifecycle
    description: Status transitions. See the state machine in each action's description.
  - name: Results
    description: Read-only views of pipeline-computed snapshots. Beta.
  - name: Exports
    description: Async order-export jobs.
paths:
  /experiments/{id}/results/{query_id}:
    get:
      tags:
        - Results
      summary: Retrieve a custom result query
      description: >
        Returns a custom result query by id. Beta.


        This never recomputes: a query id names one already-computed result,

        whatever its age. Rows carry metric values only: no lift against

        Control, no confidence interval, no significance. For those, read

        [`GET
        /experiments/{id}/results`](/api-reference/results/retrieve-the-results-snapshot).
      operationId: getExperimentResultsQuery
      parameters:
        - $ref: '#/components/parameters/ExperimentId'
        - name: query_id
          in: path
          required: true
          schema:
            type: string
          description: >-
            The `id` from [`POST
            /experiments/{id}/results`](/api-reference/results/create-a-custom-result-query).
      responses:
        '200':
          description: The query, `completed` or `failed`.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResultsQuery'
        '202':
          description: Still computing.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds to wait before asking again.
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResultsQuery'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        default:
          $ref: '#/components/responses/UnexpectedError'
      security:
        - bearerAuth: []
components:
  parameters:
    ExperimentId:
      name: id
      in: path
      required: true
      schema:
        type: string
        pattern: ^[0-9]+$
        example: '3021'
      description: The test's numeric ID as a string, for example `"3021"`.
  headers:
    XRateLimitLimit:
      description: |
        How many requests this token may make in the current 60-second window.
        Reads, writes, and result queries each have a separate budget, so this
        is the budget for the class this request falls in.
      schema:
        type: integer
    XRateLimitRemaining:
      description: Requests left in the current window.
      schema:
        type: integer
    XRateLimitReset:
      description: Unix timestamp when the window resets.
      schema:
        type: integer
  schemas:
    ResultsQuery:
      type: object
      description: |
        A custom result query job. Beta.

        `status` is `pending` until the snapshot is computed, then `completed`
        or `failed`. The result fields are present only when `completed`.
      required:
        - object
        - id
        - experiment_id
        - status
      properties:
        object:
          type: string
          const: results_query
        id:
          type: string
          description: Treat as opaque. Never parse it.
          example: 66d3b91c4f2a7e5081c9a3b7
        experiment_id:
          type: string
          example: '3021'
        status:
          type: string
          enum:
            - pending
            - completed
            - failed
        failure_reason:
          type:
            - string
            - 'null'
          description: |
            Why the query failed, in plain language. Present only when `status`
            is `failed`. Run the query again; if it fails again, send support
            this text and the query `id`.
        computed_at:
          type:
            - string
            - 'null'
          format: date-time
        test_groups:
          type: array
          description: |
            Whole-test totals. Present on a query with no `breakdown`; a
            dimensional query answers with `breakdown.rows` instead, and
            carries no separate totals.
          items:
            $ref: '#/components/schemas/TestGroupValues'
        breakdown:
          type:
            - object
            - 'null'
          description: Present when the query named a breakdown.
          properties:
            dimensions:
              type: array
              description: |
                The dimensions of these rows, in canonical (sorted) order.
                That is also the order a composite `dimension_value` joins them
                in, whatever order the query listed them in.
              items:
                $ref: '#/components/schemas/QueryDimension'
            rows:
              type: array
              items:
                allOf:
                  - $ref: '#/components/schemas/TestGroupValues'
                  - type: object
                    required:
                      - dimension_value
                    properties:
                      dimension_value:
                        type: string
                        description: Composite values join their parts with `::`.
                        example: US::Mobile
    TestGroupValues:
      type: object
      description: >
        One test group's metric values, with no comparison against Control.

        This is the whole shape a custom result query answers with: it carries

        no lift, p-values, or intervals. Read those from

        [`GET
        /experiments/{id}/results`](/api-reference/results/retrieve-the-results-snapshot).
      required:
        - test_group_index
        - sample_size
      properties:
        test_group_index:
          type: integer
          example: 1
        sample_size:
          type: integer
          example: 5231
        session_count:
          type:
            - integer
            - 'null'
          example: 6816
        conversion_rate:
          type:
            - number
            - 'null'
          example: 0.034
        revenue_per_visitor:
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
        average_order_value:
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
        profit_per_visitor:
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
        add_to_cart_rate:
          type:
            - number
            - 'null'
          example: 0.081
        reached_checkout_rate:
          type:
            - number
            - 'null'
          example: 0.052
        orders:
          type:
            - integer
            - 'null'
          example: 179
        revenue:
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
    QueryDimension:
      type: string
      enum:
        - date
        - country
        - market
        - device
        - visitor_type
        - traffic_channel
        - traffic_platform
        - referral_domain
        - landing_page
        - utm_source
        - utm_medium
        - utm_campaign
        - utm_term
        - utm_content
        - product_id
      example: country
      description: |
        A dimension a custom result query can group by. `product_id` requires
        `product_group_id`, which scopes it to that product group's products.
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - type
            - code
            - message
          properties:
            type:
              type: string
              enum:
                - invalid_request_error
                - authentication_error
                - permission_error
                - not_found_error
                - conflict_error
                - validation_error
                - rate_limit_error
                - api_error
            code:
              type: string
              description: |
                The code set is fixed for each error type.

                - 400: `malformed_json`, `unknown_parameter`,
                  `invalid_cursor`, `invalid_include`,
                  `invalid_idempotency_key` (an `Idempotency-Key` header that
                  is empty or over 255 characters), `invalid_request`
                  (a query parameter sent more than once, or `limit` not
                  a whole number between 1 and 100)
                - 401: `missing_token`, `invalid_token`, `token_revoked`
                - 403: `insufficient_scope`, `api_access_disabled`,
                  `subscription_inactive`, `billing_cap_reached`,
                  `feature_not_in_plan`, `carrier_service_required`
                - 404: `experiment_not_found`, `export_not_found`,
                  `results_query_not_found`
                - 409: `invalid_status_transition`, `locked_field`,
                  `idempotency_key_in_use`, `checks_pending`,
                  `editor_owned_content`
                - 413: `request_too_large` (the body is over 10 MB)
                - 422: read the `findings` array. Each entry's `param` names
                  the field and its `message` names what to fix; the top-level
                  `code` repeats the first blocking finding. Some codes aren't
                  finding rules:
                  `invalid_request` (a body or parameter the schema refuses),
                  `invalid_type` (a `type` this API can't create),
                  `invalid_configuration` (the test as stored isn't valid to
                  act on; `message` names what to fix),
                  `launch_blocked` (a launch check failed with no finding to
                  name), `preview_unsupported` (this test can't show a single
                  test group through a preview link; preview it in the
                  ABConvert admin), and
                  `export_not_available` (the test hasn't run yet, so there is
                  nothing to export).
                  A custom result query refuses its own parameters
                  with `invalid_breakdown_dimension`,
                  `breakdown_too_many_dimensions`,
                  `duplicate_breakdown_dimension`, `invalid_date_range`,
                  `date_range_out_of_bounds` (the window ends before the test
                  started, or begins after today), `sample_basis_unsupported`,
                  `product_group_not_found`,
                  `product_id_requires_product_group`, or
                  `results_not_available` (the test hasn't run yet).

                  A query parameter value outside the published vocabulary
                  (`status`, `type`, `scheduled`, a `created_at` bound) returns
                  `invalid_request` with `param` naming the parameter.

                  On create, the codes fall into four families:
                  - a resource this shop doesn't have:
                    `product_not_found`, `collection_not_found`,
                    `theme_not_found`, `template_not_found`,
                    `zone_not_found`, `rate_not_found`
                  - a test group with nothing to test, so add a change:
                    `template_required`,
                    `destination_required`, `change_required`,
                    `rate_required`, `offer_required`
                  - two parts of the request disagree, and the code names
                    which:
                    `duplicate_change`, `duplicate_group_name`,
                    `duplicate_rule_key`, `rule_key_not_found`,
                    `zone_not_declared`, `control_group_invalid`,
                    `split_sum_invalid`
                  - a value this API won't write, so author it in the
                    ABConvert admin instead:
                    `currency_mismatch`, `control_offers_rates`,
                    `match_type_unsupported`,
                    `max_discount_amount_not_allowed`,
                    `discount_combination_invalid`,
                    `condition_range_invalid`, `product_limit_exceeded`,
                    `market_pricing_incomplete`
                - 429: `rate_limited`
                - 500: `internal_error`
                - 501: `not_implemented` (a test type this API can't create
                  yet, or a test whose stored form this API can't read back;
                  use the ABConvert admin instead)
                - 503: `rate_limit_unavailable` (the limiter could not be
                  reached, so the write was refused rather than let through
                  uncounted; reads are served in this case),
                  `audit_unavailable` (the write could not be recorded in the
                  audit log, so it was not performed),
                  `idempotency_unavailable` (ABConvert could not check your
                  `Idempotency-Key`, so the request was refused rather than run
                  without replay protection; retry with the same key),
                  `internal_error` (the request could not be run alongside
                  other changes to this shop, so nothing was attempted; retry)
                - 504: `internal_error` (the write took too long and was rolled
                  back, so nothing changed)

                Every 5xx carries a `request_id`. Quote it when you contact
                support.
            message:
              type: string
            param:
              type:
                - string
                - 'null'
              description: |
                JSON path of the offending request field. On
                `feature_not_in_plan` it carries the feature slug instead:
                `price`, `offer`, `multi_market`, or `checkout_blocks`.
            request_id:
              type: string
              description: |
                Correlation id for one refused request. Present on every 5xx
                and on nothing else: a 4xx is about the request, and there is
                nothing on our side to look up. Send an `x-correlation-id`
                header and that value is the id you get back.
            details:
              type: object
              additionalProperties: true
              description: |
                Machine-readable context, present where a code defines it.
                `invalid_status_transition` carries
                `{"current_status": "...", "allowed_actions": [...]}` so a
                client can branch without parsing `message`.
            findings:
              type: array
              description: Present on `validation_error`.
              items:
                $ref: '#/components/schemas/Finding'
    Money:
      type: object
      required:
        - amount
        - currency
      properties:
        amount:
          type: string
          pattern: ^-?\d+(\.\d+)?$
          example: '17.99'
          description: >-
            A decimal string. Only fields that express a delta accept a leading
            minus sign.
        currency:
          type: string
          pattern: ^[A-Z]{3}$
          example: USD
          description: |
            ISO 4217 code. On write it must be the shop's own currency;
            anything else returns 422 `currency_mismatch`.
    Finding:
      type: object
      required:
        - severity
        - code
        - message
      properties:
        severity:
          type: string
          enum:
            - error
            - warning
        code:
          type: string
          description: |
            A snake_case identifier for the finding. Published codes never
            change, and new ones are only added.
          enum:
            - product_not_found
            - collection_not_found
            - theme_not_found
            - template_not_found
            - zone_not_found
            - rate_not_found
            - market_not_found
            - rule_key_not_found
            - template_required
            - destination_required
            - change_required
            - rate_required
            - offer_required
            - theme_required
            - market_empty
            - product_not_testable
            - theme_not_testable
            - duplicate_change
            - duplicate_group_name
            - duplicate_rule_key
            - duplicate_trigger_url
            - duplicate_theme
            - duplicate_template
            - control_group_invalid
            - control_offers_rates
            - control_redirects
            - control_runs_code
            - split_sum_invalid
            - test_groups_too_many
            - zone_not_declared
            - discount_combination_invalid
            - currency_mismatch
            - match_type_unsupported
            - max_discount_amount_not_allowed
            - condition_range_invalid
            - condition_unit_invalid
            - product_limit_exceeded
            - country_limit_exceeded
            - rule_limit_exceeded
            - market_pricing_incomplete
            - path_invalid
            - rate_name_required
            - rate_price_invalid
            - offer_title_required
            - filter_value_invalid
            - filter_operator_invalid
            - filter_key_required
            - filter_type_unsupported
            - custom_js_invalid
            - split_changed
            - country_served_by_other_market
            - country_ownership_ambiguous
            - market_not_contextual
            - trigger_overlaps
            - destination_matches_trigger
            - zone_not_configured
            - theme_not_published
            - filter_value_unrecognized
            - test_groups_missing
            - split_invalid
            - theme_test_running
            - visual_editor_conflict
            - app_embed_disabled
            - resource_claimed
            - test_overrides_personalization
            - audience_empty
            - force_assign_group_invalid
            - block_placement_invalid
            - modification_invalid
            - modification_kind_unknown
            - modification_duplicate_singleton
            - launch_check_failed
        param:
          type:
            - string
            - 'null'
          description: |
            JSON path of the field the finding points at, for example
            `shared.price.markets[0].countries[2]`. Offending values appear
            in `message`, not here.
        message:
          type: string
  responses:
    Unauthorized:
      description: |
        `authentication_error`. Send a valid token as
        `Authorization: Bearer <token>`.

        - `missing_token`: no bearer token on the request.
        - `invalid_token`: the token is malformed, does not exist, or names a
          shop ABConvert can no longer act for.
        - `token_revoked`: someone revoked this token in the ABConvert admin.
          Create a new one.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: >
        `permission_error`. The shop or the token isn't allowed to do this, so

        nothing changed.


        | Code | What to do | Where it applies |

        |---|---|---|

        | `insufficient_scope` | Use a token with the scope named in
        `details.required_scope`. | Any request |

        | `api_access_disabled` | API access is turned off for this shop.
        Contact support. | Any request |

        | `subscription_inactive` | Renew the shop's ABConvert subscription,
        then retry. | `start` and `resume` |

        | `billing_cap_reached` | Raise the shop's usage cap for the period,
        then retry. | `start` and `resume` |

        | `feature_not_in_plan` | Upgrade to the plan named in `message`.
        `param` names the feature: `price`, `offer`, `multi_market`, or
        `checkout_blocks`. | Create, update, `preview`, `start`, `resume` |

        | `carrier_service_required` | Give the shop carrier-calculated
        shipping. A Shopify plan with the feature, an existing carrier service,
        or annual ABConvert billing each satisfy it. | Shipping tests |


        `pause`, `end`, and `archive` never return entitlement errors. See

        [Feature availability](/api-reference/overview#feature-availability).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: |
        `not_found_error`. No resource with that ID belongs to this shop. Check
        the ID, and check that the token belongs to the shop that owns the
        resource: a token reaches one shop only.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: |
        `rate_limit_error`, code `rate_limited`. You sent more requests than
        your budget allows. Wait the number of seconds in `Retry-After`, then
        retry.
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds until the window resets.
        X-RateLimit-Limit:
          $ref: '#/components/headers/XRateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/XRateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/XRateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    UnexpectedError:
      description: |
        A status this operation doesn't enumerate. The body is the standard
        error shape.

        - 413 `request_too_large`: the body is over 10 MB. Send less.
        - 500 `internal_error`: something went wrong on our side. Retry. If it
          keeps failing, send support the `request_id`.
        - 503: the request was refused before it ran, so nothing on the shop
          changed. Retry it, with the same `Idempotency-Key` if you sent one.
          The code names which safeguard was unavailable:
          `rate_limit_unavailable`, `audit_unavailable`,
          `idempotency_unavailable`, or `internal_error`.
        - 504 `internal_error`: the write timed out and nothing changed. Retry.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: |
        Bearer tokens created in the ABConvert admin under
        Settings → MCP & API Access. Scopes:
        `read_experiments` and `write_experiments` (write implies read; the
        default is read). See
        [Authentication](/api-reference/authentication) for which scope each
        request needs.

````