openapi: 3.1.0
info:
  title: Qonversion REST API
  version: "3.1"
  description: |
    Qonversion REST API follows REST standards. It has predictable resource-oriented URLs,
    accepts JSON-encoded request bodies, returns JSON-encoded responses, and uses standard
    HTTP response codes, authentication, and verbs.

    ## Where to find your keys

    1. Sign in to the Qonversion dashboard at https://dash.qonversion.io/.
    2. Open your project and go to **Project Settings** in the sidebar.
    3. The **Project Keys** section exposes three values:
       - **Project Key** - bearer token for the v3 REST endpoints documented here.
         A 32-character URL-safe random string (`A-Z`, `a-z`, `0-9`, `_`, `-`) with no fixed prefix
         (e.g. `JFPATc4VaaWYsfurml3qZ4zsmNw0VfWH`).
       - **API Key** - used in the URL path of the analytics API
         (`https://api.qonversion.io/v1/analytics/{API_KEY}/...`); do NOT use it as a v3 bearer.
       - **Secret Key** - bearer for the entitlement grant/revoke endpoints. Always prefixed with `sk_`.

    ## Sandbox vs production

    Project Keys with the `test_` prefix (e.g. `test_PV77YHL7qnGvsdmpTs7gimsxUvY-Znl2`) target the
    **sandbox** environment. Production keys have no prefix. The same prefix scheme applies to Secret
    Keys. Sandbox users do not affect production analytics.

    ## Errors

    All endpoints share the error envelope shown in `Error`. The `meta.reference` URL is set
    automatically for the documented machine-readable codes (`invalid_data`, `invalid_request`,
    `invalid_entitlement_data`); other errors omit `meta`. There is no `meta.fields[]` array.
servers:
  - url: https://api.qonversion.io/v3
    description: Production

security:
  - ProjectKey: []

components:
  securitySchemes:
    ProjectKey:
      type: http
      scheme: bearer
      description: |
        Use your **Project Key** from the Qonversion dashboard
        (Project Settings -> Project Keys -> Project Key).

        Production keys have no prefix; keys prefixed with `test_` target the sandbox environment.

        Example (sandbox): `Bearer test_PV77YHL7qnGvsdmpTs7gimsxUvY-Znl2`
        Example (production): `Bearer JFPATc4VaaWYsfurml3qZ4zsmNw0VfWH`
    SecretKey:
      type: http
      scheme: bearer
      description: |
        Use your **Secret Key** (prefixed with `sk_`) from the Qonversion dashboard
        (Project Settings -> Project Keys -> Secret Key). Sandbox secret keys are prefixed
        with `test_sk_`.

        **Never expose the secret key in client-side code.**

  schemas:
    User:
      type: object
      properties:
        id:
          type: string
          description: Qonversion User ID
          example: QON_7791a27fc4e747412381842b36a
        identity_id:
          type: string
          description: User Identity ID from your auth system
          example: awesome_user
        created:
          type: integer
          description: User creation time (Unix epoch seconds)
          example: 1658489733
        environment:
          type: string
          enum: [prod, sandbox]
          description: "`prod` or `sandbox`"
          example: prod

    UserProperty:
      type: object
      properties:
        key:
          type: string
          description: "Property identifier. Qonversion-defined keys start with `_q_` prefix."
          example: _q_email
        value:
          type: string
          description: Property value
          example: test@email.com

    Identity:
      type: object
      properties:
        id:
          type: string
          description: User Identity ID
          example: awesome_user
        user_id:
          type: string
          description: Qonversion User ID
          example: QON_7791a27fc4e747412381842b36a

    Subscription:
      type: object
      properties:
        current_period_type:
          type: string
          enum: [normal, trial, intro]
          description: Current subscription period type
          example: normal
        renew_state:
          type: string
          enum: [will_renew, canceled, billing_issue]
          description: Subscription renewal state
          example: will_renew

    Product:
      type: object
      properties:
        product_id:
          type: string
          description: Qonversion product identifier
          example: main
        subscription:
          $ref: "#/components/schemas/Subscription"

    Entitlement:
      type: object
      properties:
        id:
          type: string
          description: Entitlement identifier
          example: plus
        active:
          type: boolean
          description: Whether the user currently has the entitlement
          example: true
        started:
          type: integer
          description: Unix epoch seconds when entitlement was activated
          example: 1652438020
        expires:
          type: integer
          description: Unix epoch seconds when entitlement expires
          example: 1654215637
        source:
          type: string
          enum: [appstore, playstore, stripe, paddle, manual, unknown]
          description: Purchase origin
          example: appstore
        product:
          $ref: "#/components/schemas/Product"

    StripeStoreData:
      type: object
      required: [subscription_id, product_id]
      properties:
        subscription_id:
          type: string
          description: Stripe subscription identifier
          example: sub_123e4567
        product_id:
          type: string
          description: Stripe product identifier
          example: prod_123e4567

    PaddleStoreData:
      type: object
      required: [transaction_id, product_id, type]
      properties:
        type:
          type: string
          enum: [subscription, non_recurring]
          description: Purchase type
          example: subscription
        transaction_id:
          type: string
          description: Paddle transaction identifier. Always present.
          example: txn_01hv4rrk
        subscription_id:
          type: string
          description: Paddle subscription identifier. Required when `type` is `subscription`; omitted for `non_recurring` purchases.
          example: sub_01hv4rrk
        product_id:
          type: string
          description: Paddle product identifier
          example: pro_01hv4rrk

    Purchase:
      type: object
      description: |
        The response echoes whichever store-data variant was provided in the request:
        exactly one of `stripe_store_data`, `paddle_store_data`, `app_store_data`, or `play_store_data` will be present.
      required: [user_id]
      properties:
        user_id:
          type: string
          description: Qonversion User ID
          example: QON_7791a27fc4e747412381842b36a
        currency:
          type: string
          description: Three-letter ISO 4217 currency code (uppercase)
          example: USD
        price:
          type: string
          description: Monetary amount (decimal string)
          example: "12.99"
        purchased:
          type: integer
          format: int64
          description: Unix epoch seconds of the transaction
          example: 1653458781
        stripe_store_data:
          $ref: "#/components/schemas/StripeStoreData"
        paddle_store_data:
          $ref: "#/components/schemas/PaddleStoreData"
        app_store_data:
          type: object
          description: App Store transaction data. Echoed only when the purchase was created with `app_store_data`.
          properties:
            transaction_id:
              type: string
            original_transaction_id:
              type: string
            product_id:
              type: string
            receipt:
              type: string
              description: Base64-encoded App Store receipt
        play_store_data:
          type: object
          description: Google Play transaction data. Echoed only when the purchase was created with `play_store_data`.
          properties:
            order_id:
              type: string
            purchase_token:
              type: string
            product_id:
              type: string
            type:
              type: string
              enum: [subscription, non_recurring]

    DashboardProduct:
      type: object
      properties:
        uid:
          type: string
          description: Unique identifier of the Qonversion product
          example: premium_monthly
        project_id:
          type: integer
          description: Numeric ID of the Qonversion project owning this product
          example: 12345
        type:
          type: integer
          enum: [0, 1, 2, 3]
          description: |
            Product type:
            - `0` = Subscription With Promo Period
            - `1` = Regular Subscription
            - `2` = Non-Recurring (Consumable)
            - `3` = Non-Consumable (Lifetime)
          example: 1
        apple_product_id:
          type: string
          description: Product identifier in App Store Connect
          example: com.app.premium_monthly
        google_product_id:
          type: string
          description: Product identifier in Google Play Console
          example: com.app.premium_monthly
        google_base_plan_id:
          type: string
          description: Base plan identifier in Google Play Console
          example: monthly-base
        stripe_product_id:
          type: string
          description: Product identifier in Stripe
          example: prod_abc123
        duration:
          type: integer
          enum: [0, 1, 2, 3, 4]
          description: "Duration: `0` = Weekly, `1` = Monthly, `2` = Three Monthly, `3` = Six Monthly, `4` = Annual"
          example: 1
        subscription_duration:
          type: string
          enum: [P1W, P1M, P3M, P6M, P1Y]
          description: |
            ISO 8601 subscription duration string derived from `duration`. Server-computed:
            `P1W` for Weekly, `P1M` for Monthly, `P3M` for Three Monthly, `P6M` for Six Monthly,
            `P1Y` for Annual. Present only for subscription products (`type` 0 or 1).
          example: P1M
        created_at:
          type: integer
          description: Creation timestamp (Unix seconds)
          example: 1665669916
        updated_at:
          type: integer
          description: Last-update timestamp (Unix seconds)
          example: 1665669916

    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, type]
          properties:
            type:
              type: string
              enum: [request, resource, logical, internal]
              description: Error category
            code:
              type: string
              description: Machine-readable error code
            message:
              type: string
              description: Human-readable error message
        meta:
          type: object
          description: |
            Optional metadata. Currently only contains a `reference` URL for a small subset
            of error codes (`invalid_data`, `invalid_request`, `invalid_entitlement_data`).
          properties:
            reference:
              type: string
              format: uri
              example: https://documentation.qonversion.io/reference/handling-api-errors

paths:
  /users/{id}:
    get:
      operationId: retrieveUser
      summary: Retrieve a user
      description: Retrieves a Qonversion user by their ID.
      tags:
        - Users
      x-mint:
        content: |
          <Info>User IDs are generated by the Qonversion SDK (prefixed with `QON_`) or set manually via the API.</Info>
      security:
        - ProjectKey: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Qonversion User ID
          example: QON_7791a27fc4e747412381842b36a
      responses:
        "200":
          description: User retrieved successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
              examples:
                production_user:
                  summary: Production user
                  value:
                    id: QON_7791a27fc4e747412381842b36a
                    identity_id: custom_identity_id
                    created: 1658489733
                    environment: prod
                sandbox_user:
                  summary: Sandbox user
                  value:
                    id: QON_5e8df1ce8b3242c0a1c4df7e95
                    identity_id: test_user_42
                    created: 1700000000
                    environment: sandbox
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: control_unauthorized
                  message: Bad authorization header
                  type: request
        "404":
          description: User not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: not_found
                  message: User not found
                  type: resource

    post:
      operationId: createUser
      summary: Create a user
      description: Creates a new Qonversion user with the given ID.
      tags:
        - Users
      x-mint:
        content: |
          <Warning>Use `sandbox` environment for testing. Sandbox users do not affect production analytics.</Warning>
      security:
        - ProjectKey: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Qonversion User ID. By default generated with the Qonversion SDK.
          example: QON_MU1yZnydlsoqCM
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [environment]
              properties:
                environment:
                  type: string
                  enum: [prod, sandbox]
                  description: "`prod` or `sandbox`. Use sandbox for testing."
            example:
              environment: prod
      responses:
        "200":
          description: User created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
              examples:
                production:
                  summary: Production user created
                  value:
                    id: QON_MU1yZnydlsoqCM
                    created: 1665669916
                    environment: prod
                sandbox:
                  summary: Sandbox user created
                  value:
                    id: QON_MU1yZnydlsoqCM
                    created: 1665669916
                    environment: sandbox
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: invalid_data
                  message: unknown environment type
                  type: request
        "422":
          description: User already exists
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: already_exists
                  message: User with given uid already exists
                  type: resource

  /users/{id}/properties:
    get:
      operationId: retrieveUserProperties
      summary: Retrieve user properties
      description: |
        Retrieves all properties for a given user. Up to 100 properties per user.

        **Rate limit:** Properties endpoints are rate-limited per project (default 30 requests
        per second; configurable on the server). Exceeding the limit returns 429.
      tags:
        - User Properties
      x-mint:
        content: |
          <Note>Qonversion-defined properties are prefixed with `_q_` (e.g. `_q_email`). Custom properties use your own keys.</Note>
      security:
        - ProjectKey: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Qonversion User ID
      responses:
        "200":
          description: Properties retrieved
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/UserProperty"
              examples:
                with_properties:
                  summary: User with properties
                  value:
                    - key: _q_email
                      value: test@email.com
                    - key: _q_name
                      value: John Doe
                    - key: client_source
                      value: google_ads
                empty:
                  summary: User with no properties
                  value: []
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: User not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

    post:
      operationId: createUserProperties
      summary: Create user properties
      description: |
        Sets properties for a user. Supports partial success - returns both saved properties and errors.

        **Rate limit:** Properties endpoints are rate-limited per project (default 30 requests
        per second; configurable on the server). Exceeding the limit returns 429.
      tags:
        - User Properties
      security:
        - ProjectKey: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Qonversion User ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: "#/components/schemas/UserProperty"
            example:
              - key: valid_key
                value: valid_value
              - key: "invalid key"
                value: value
      responses:
        "200":
          description: Properties processed (partial success possible)
          content:
            application/json:
              schema:
                type: object
                properties:
                  savedProperties:
                    type: array
                    items:
                      $ref: "#/components/schemas/UserProperty"
                  propertyErrors:
                    type: array
                    items:
                      type: object
                      properties:
                        key:
                          type: string
                        error:
                          type: string
              example:
                savedProperties:
                  - key: valid_key
                    value: valid_value
                propertyErrors:
                  - key: "invalid key"
                    error: "Property key: invalid key. Error: invalid key format."
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /identities/{identity_id}:
    get:
      operationId: retrieveIdentity
      summary: Retrieve an identity
      description: Retrieves an identity by its ID.
      tags:
        - Identity
      x-mint:
        content: |
          <Info>Identities map your internal user IDs to Qonversion User IDs, enabling cross-platform user tracking.</Info>
      security:
        - ProjectKey: []
      parameters:
        - name: identity_id
          in: path
          required: true
          schema:
            type: string
          description: User Identity ID
          example: awesome_user
      responses:
        "200":
          description: Identity retrieved
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Identity"
              example:
                id: new_identity
                user_id: QON_7791ae4e888bdeb81842b36a
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Identity not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

    post:
      operationId: createIdentity
      summary: Create an identity
      description: |
        Creates an identity. If `user_id` is not provided, a new Qonversion user is created automatically.
      tags:
        - Identity
      security:
        - ProjectKey: []
      parameters:
        - name: identity_id
          in: path
          required: true
          schema:
            type: string
          description: User Identity ID
          example: new_identities_user
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                user_id:
                  type: string
                  description: Qonversion User ID. If omitted, a new user is created.
            example:
              user_id: QON_8968d347c4a24168b4885ee087fbd635
      responses:
        "201":
          description: Identity created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Identity"
              example:
                id: new_identities_user
                user_id: QON_8968d347c4a24168b4885ee087fbd635
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Identity already linked to another user
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: invalid_request
                  message: "identity already exists: it's linked to another user"
                  type: request

  /users/{user_id}/entitlements:
    get:
      operationId: retrieveEntitlements
      summary: Retrieve entitlements
      description: Retrieves all entitlements for a user.
      tags:
        - Entitlements
      security:
        - ProjectKey: []
      parameters:
        - name: user_id
          in: path
          required: true
          schema:
            type: string
          description: Qonversion User ID
      responses:
        "200":
          description: Entitlements retrieved
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Entitlement"
              examples:
                active_subscription:
                  summary: Active subscription
                  value:
                    data:
                      - id: plus
                        active: true
                        started: 1652438020
                        expires: 1654215637
                        source: stripe
                        product:
                          product_id: main
                          subscription:
                            current_period_type: normal
                            renew_state: will_renew
                no_entitlements:
                  summary: No active entitlements
                  value:
                    data: []
                manual_grant:
                  summary: Manually granted entitlement
                  value:
                    data:
                      - id: premium
                        active: true
                        started: 1678894940
                        expires: 1704067200
                        source: manual
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: User not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

    post:
      operationId: grantEntitlement
      summary: Grant an entitlement
      description: |
        Manually grants an entitlement to a user. **Requires Secret Key authentication.**
      tags:
        - Entitlements
      x-mint:
        content: |
          <Warning>This endpoint requires a **Secret Key** (`sk_` prefix). Never expose it in client-side code.</Warning>
      security:
        - SecretKey: []
      parameters:
        - name: user_id
          in: path
          required: true
          schema:
            type: string
          description: Qonversion User ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [id, expires]
              properties:
                id:
                  type: string
                  description: Entitlement ID
                expires:
                  type: integer
                  format: int64
                  description: Unix timestamp (seconds) when entitlement expires. Must be in the future.
            example:
              id: plus
              expires: 1704067200
      responses:
        "200":
          description: Entitlement granted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Entitlement"
              example:
                id: plus
                active: true
                started: 1678894940
                expires: 1704067200
                source: manual
        "400":
          description: Invalid entitlement data
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: invalid_entitlement_data
                  message: "Invalid expires at value has been provided, should be in unix timestamp format in seconds in future"
                  type: request

  /users/{user_id}/entitlements/{id}:
    delete:
      operationId: revokeEntitlement
      summary: Revoke an entitlement
      description: |
        Revokes a manually granted entitlement from a user. **Requires Secret Key authentication.**

        You can only revoke entitlements with source `manual`.
      tags:
        - Entitlements
      x-mint:
        content: |
          <Warning>This endpoint requires a **Secret Key** (`sk_` prefix). Only entitlements with source `manual` can be revoked.</Warning>
      security:
        - SecretKey: []
      parameters:
        - name: user_id
          in: path
          required: true
          schema:
            type: string
          description: Qonversion User ID
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Entitlement ID
      responses:
        "200":
          description: Entitlement revoked. No response body.
        "400":
          description: Invalid entitlement
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error:
                  code: invalid_entitlement_data
                  message: "Invalid entitlement uid, no such entitlement found"
                  type: request
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: |
            Either the user or the entitlement could not be found, or the entitlement was not
            revokable (only manually granted entitlements with source `manual` can be revoked).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /users/{user_id}/purchases:
    post:
      operationId: createPurchase
      summary: Create a purchase
      description: |
        Records a purchase for a user. Stripe and Paddle are the recommended paths when calling this
        API directly; App Store and Play Store purchases normally flow through the Qonversion SDK and
        rarely need to be POSTed manually.

        Provide exactly one of `stripe_store_data`, `paddle_store_data`, `app_store_data`, or `play_store_data`.
      tags:
        - Purchases
      x-mint:
        content: |
          <Note>Stripe and Paddle are the typical use cases. `app_store_data` and `play_store_data` are
          accepted for completeness but App Store and Play Store purchases are normally handled
          automatically by the Qonversion SDK.</Note>
      security:
        - ProjectKey: []
      parameters:
        - name: user_id
          in: path
          required: true
          schema:
            type: string
          description: Qonversion User ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [currency, price, purchased]
              properties:
                currency:
                  type: string
                  description: Three-letter ISO 4217 currency code (case-insensitive; normalised to uppercase server-side)
                price:
                  type: string
                  description: Monetary amount (decimal string, e.g. "12.99")
                purchased:
                  type: integer
                  format: int64
                  description: Unix epoch seconds of the transaction
                stripe_store_data:
                  $ref: "#/components/schemas/StripeStoreData"
                paddle_store_data:
                  $ref: "#/components/schemas/PaddleStoreData"
                app_store_data:
                  type: object
                  description: |
                    App Store transaction data. Use only when posting App Store purchases manually
                    instead of via the SDK.
                  required: [transaction_id, original_transaction_id, product_id]
                  properties:
                    transaction_id:
                      type: string
                    original_transaction_id:
                      type: string
                    product_id:
                      type: string
                    receipt:
                      type: string
                      description: Base64-encoded App Store receipt
                play_store_data:
                  type: object
                  description: |
                    Google Play transaction data. Use only when posting Play purchases manually
                    instead of via the SDK.
                  required: [order_id, purchase_token, product_id, type]
                  properties:
                    order_id:
                      type: string
                    purchase_token:
                      type: string
                    product_id:
                      type: string
                    type:
                      type: string
                      enum: [subscription, non_recurring]
            example:
              currency: USD
              price: "12.99"
              purchased: 1653458781
              stripe_store_data:
                subscription_id: sub_123e4567
                product_id: prod_123e4567
      responses:
        "201":
          description: Purchase created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Purchase"
              example:
                user_id: QON_7791a27fc4e747412381842b36a
                currency: USD
                price: "12.99"
                purchased: 1653458781
                stripe_store_data:
                  subscription_id: sub_123e4567
                  product_id: prod_123e4567
        "400":
          description: Invalid data
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: |
            Returned when the user is not found (`User not found`) or when the referenced product
            cannot be located (`Qonversion product with the specified ID not found`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /products/{id}:
    get:
      operationId: getProduct
      summary: Get products
      description: |
        Retrieve a single product by ID, or omit the ID to get all dashboard products.
      tags:
        - Products
      security:
        - ProjectKey: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Qonversion product UID
          example: premium_monthly
      responses:
        "200":
          description: Product retrieved
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DashboardProduct"
              examples:
                subscription:
                  summary: Monthly subscription
                  value:
                    uid: premium_monthly
                    project_id: 12345
                    type: 1
                    apple_product_id: com.app.premium_monthly
                    google_product_id: com.app.premium_monthly
                    stripe_product_id: prod_abc123
                    duration: 1
                    subscription_duration: P1M
                    created_at: 1665669916
                    updated_at: 1665669916
                one_time:
                  summary: One-time purchase
                  value:
                    uid: lifetime_access
                    project_id: 12345
                    type: 3
                    apple_product_id: com.app.lifetime
                    google_product_id: com.app.lifetime
                    created_at: 1665669916
                    updated_at: 1665669916
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Product not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /products:
    get:
      operationId: listProducts
      summary: List products
      description: Returns all dashboard products configured for the authenticated project.
      tags:
        - Products
      security:
        - ProjectKey: []
      responses:
        "200":
          description: List of dashboard products
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/DashboardProduct"
              example:
                - uid: premium_monthly
                  project_id: 12345
                  type: 1
                  apple_product_id: com.app.premium_monthly
                  google_product_id: com.app.premium_monthly
                  duration: 1
                  created_at: 1665669916
                  updated_at: 1665669916
                - uid: lifetime_access
                  project_id: 12345
                  type: 2
                  apple_product_id: com.app.lifetime
                  created_at: 1665669916
                  updated_at: 1665669916
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      operationId: createProduct
      summary: Create a product
      description: Creates a new Qonversion dashboard product.
      tags:
        - Products
      security:
        - ProjectKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [uid]
              properties:
                uid:
                  type: string
                  description: Unique product identifier
                type:
                  type: integer
                  enum: [0, 1, 2, 3]
                  description: "Product type: `0` = Subscription With Promo Period, `1` = Regular Subscription, `2` = Non-Recurring (Consumable), `3` = Non-Consumable (Lifetime)"
                apple_product_id:
                  type: string
                  description: App Store Connect product ID
                google_product_id:
                  type: string
                  description: Google Play Console product ID
                google_base_plan_id:
                  type: string
                  description: Google Play base plan ID
                stripe_product_id:
                  type: string
                  description: Stripe product ID
                duration:
                  type: integer
                  enum: [0, 1, 2, 3, 4]
                  description: "Duration: `0` = Weekly, `1` = Monthly, `2` = 3 Months, `3` = 6 Months, `4` = Annual"
            example:
              uid: premium_monthly
              type: 1
              apple_product_id: com.app.premium_monthly
              google_product_id: com.app.premium_monthly
              duration: 1
      responses:
        "200":
          description: Product created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DashboardProduct"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /products/{uid}:
    put:
      operationId: updateProduct
      summary: Update a product
      description: |
        Replaces all product fields. Unlike PATCH, all fields must be provided — omitted fields are reset to defaults.
      tags:
        - Products
      security:
        - ProjectKey: []
      parameters:
        - name: uid
          in: path
          required: true
          schema:
            type: string
          description: Qonversion product UID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                type:
                  type: integer
                  enum: [0, 1, 2, 3]
                  description: "Product type: `0` = Subscription With Promo Period, `1` = Regular Subscription, `2` = Non-Recurring (Consumable), `3` = Non-Consumable (Lifetime)"
                apple_product_id:
                  type: string
                google_product_id:
                  type: string
                google_base_plan_id:
                  type: string
                stripe_product_id:
                  type: string
                duration:
                  type: integer
                  enum: [0, 1, 2, 3, 4]
            example:
              type: 1
              apple_product_id: com.app.premium_monthly
              google_product_id: com.app.premium_monthly
              duration: 1
      responses:
        "200":
          description: Product updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DashboardProduct"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Product not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

    patch:
      operationId: patchProduct
      summary: Patch a product
      description: |
        Partially updates a product. Only the submitted fields are updated — omitted fields remain unchanged.
      tags:
        - Products
      security:
        - ProjectKey: []
      parameters:
        - name: uid
          in: path
          required: true
          schema:
            type: string
          description: Qonversion product UID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                type:
                  type: integer
                  enum: [0, 1, 2, 3]
                  description: "Product type: `0` = Subscription With Promo Period, `1` = Regular Subscription, `2` = Non-Recurring (Consumable), `3` = Non-Consumable (Lifetime)"
                apple_product_id:
                  type: string
                google_product_id:
                  type: string
                google_base_plan_id:
                  type: string
                stripe_product_id:
                  type: string
                duration:
                  type: integer
                  enum: [0, 1, 2, 3, 4]
            example:
              apple_product_id: com.app.premium_yearly
              duration: 4
      responses:
        "200":
          description: Product patched
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DashboardProduct"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Product not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

    delete:
      operationId: deleteProduct
      summary: Delete a product
      description: Deletes a Qonversion dashboard product.
      tags:
        - Products
      security:
        - ProjectKey: []
      parameters:
        - name: uid
          in: path
          required: true
          schema:
            type: string
          description: Qonversion product UID
      responses:
        "204":
          description: Product deleted. No response body.
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Product not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
