openapi: 3.1.0
info:
  title: Adapty server-side API
  version: 1.0.0
servers:
  - url: https://api.adapty.io
    description: Production server
paths:
  # Profile Management
  /api/v2/server-side-api/profile/:
    post:
      summary: Create profile
      description: Creates a new end user of your app in Adapty.
      operationId: createProfile
      tags:
        - Profile
      security:
        - apikeyAuth: []
      parameters:
        - name: adapty-customer-user-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the customer in your system. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
        - name: adapty-profile-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the profile in your system. Your best option if you are working with anonymous profiles. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
        - name: adapty-platform
          in: header
          required: false
          schema:
            type: string
          description: The platform of the device where the user has your app installed
      responses:
        '200':
          description: Profile created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileResponse'
              example:
                data:
                  app_id: "14c3d623-2f3a-455a-aa86-ef83dff6913b"
                  profile_id: "3286abd3-48b0-4e9c-a5f6-ac0a006804a6"
                  customer_user_id: "jane.doe@example.com"
                  total_revenue_usd: 0.0
                  segment_hash: "8f45947bad31ab0c"
                  timestamp: 1736425645861
                  custom_attributes:
                    - key: "favourite_sport"
                      value: "yoga"
                  access_levels: []
                  subscriptions: []
                  non_subscriptions: []
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - source: "non_field_errors"
                    errors: ["Authentication credentials were not provided."]
                error_code: "not_authenticated"
                status_code: 401
        '500':
          description: Internal server error
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProfileRequest'
            example:
              first_name: "Jane"
              last_name: "Doe"
              gender: "f"
              email: "jane.doe@example.com"
              phone_number: "+1234567890"
              birthday: "2000-12-31"
              ip_country: "FR"
              store_country: "US"
              store: "app_store"
              analytics_disabled: true
              custom_attributes:
                - key: "favourite_sport"
                  value: "yoga"
              installation_meta:
                device_id: "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                device: "string"
                locale: "en"
                os: "string"
                platform: "iOS"
                timezone: "Europe/Rome"
                user_agent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Mobile/15E148 Safari/604.1"
                idfa: "EA7583CD-A667-48BC-B806-42ECB2B48333"
                idfv: "E9D48DA5-3930-4B41-8521-D953AECD2F33"
                advertising_id: ""
                android_id: ""
                android_app_set_id: ""
    get:
      summary: Get profile
      description: Retrieves the details of an existing end user of your app.
      operationId: getProfile
      tags:
        - Profile
      security:
        - apikeyAuth: []
      parameters:
        - name: adapty-customer-user-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the customer in your system. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
        - name: adapty-profile-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the profile in your system. Your best option if you are working with anonymous profiles. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
      responses:
        '200':
          description: Profile retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileResponse'
              example:
                data:
                  app_id: "14c3d333-2f3a-455a-aa86-ef83dff6913b"
                  profile_id: "d8533a10-bcce-4e33-8c9d-88b05ac56559"
                  customer_user_id: "77B14FB4-FD2A-4D38-AA3A-4C433F79863C"
                  total_revenue_usd: 9.99
                  segment_hash: "fdaeef7f8aaa33c9"
                  timestamp: 1733324566777
                  custom_attributes:
                    - key: "favourite_sport"
                      value: "yoga"
                  access_levels:
                    - access_level_id: "premium"
                      store: "app_store"
                      store_product_id: "unlimited.9999"
                      store_base_plan_id: null
                      store_transaction_id: "2000000335013007"
                      store_original_transaction_id: "2000000335013007"
                      offer: null
                      starts_at: null
                      purchased_at: "2024-12-24T10:50:23+00:00"
                      originally_purchased_at: "2024-12-24T10:50:23+00:00"
                      expires_at: null
                      renewal_cancelled_at: "2025-01-05T13:27:47.461425+00:00"
                      billing_issue_detected_at: null
                      is_in_grace_period: false
                      cancellation_reason: null
                  subscriptions:
                    - store: "app_store"
                      store_product_id: "unlimited.9999"
                      store_base_plan_id: null
                      store_transaction_id: "2000000815013007"
                      store_original_transaction_id: "2000000815013007"
                      offer: null
                      environment: "Sandbox"
                      purchased_at: "2024-12-24T10:50:23+00:00"
                      originally_purchased_at: "2024-12-24T10:50:23+00:00"
                      expires_at: null
                      renewal_cancelled_at: null
                      billing_issue_detected_at: null
                      is_in_grace_period: false
                      cancellation_reason: null
                  non_subscriptions:
                    - purchase_id: "7a5f9a7d-e236-33e6-96d8-53a3c59c5562"
                      store: "app_store"
                      store_product_id: "1year.premium"
                      store_base_plan_id: null
                      store_transaction_id: "30002109551456"
                      store_original_transaction_id: "30002109551456"
                      purchased_at: "2022-10-12T09:42:50+00:00"
                      environment: "Production"
                      is_refund: false
                      is_consumable: false
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - source: "non_field_errors"
                    errors: ["Authentication credentials were not provided."]
                error_code: "not_authenticated"
                status_code: 401
        '404':
          description: Profile not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - source: null
                    errors: ["Profile not found"]
                error_code: "profile_does_not_exist"
                status_code: 404
        '500':
          description: Internal server error
    patch:
      summary: Update profile
      description: Update user profile information
      operationId: updateProfile
      tags:
        - Profile
      security:
        - apikeyAuth: []
      parameters:
        - name: adapty-customer-user-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the customer in your system. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
        - name: adapty-profile-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the profile in your system. Your best option if you are working with anonymous profiles. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
      responses:
        '200':
          description: Profile updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Profile not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal server error
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProfileRequest'
    delete:
      summary: Delete profile
      description: Delete user profile
      operationId: deleteProfile
      tags:
        - Profile
      security:
        - apikeyAuth: []
      parameters:
        - name: adapty-customer-user-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the customer in your system. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
        - name: adapty-profile-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the profile in your system. Your best option if you are working with anonymous profiles. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
      responses:
        '204':
          description: Profile deleted successfully
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Profile not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal server error

  # Purchase Management
  /api/v2/server-side-api/purchase/set/transaction/:
    post:
      summary: Set transaction
      description: Creates a new transaction for an end user of your app in Adapty and provides access level. The transaction created by this method will appear in your analytics and Event Feed and will be sent to all integrations.
      operationId: setTransaction
      tags:
        - Purchase
      security:
        - apikeyAuth: []
      parameters:
        - name: adapty-customer-user-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the customer in your system. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
        - name: adapty-profile-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the profile in your system. Your best option if you are working with anonymous profiles. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
      responses:
        '200':
          description: Transaction recorded successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Profile not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal server error
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TransactionDTORequest'
            examples:
              one_time_purchase:
                summary: One-time purchase example
                value:
                  purchase_type: "one_time_purchase"
                  store: "app_store"
                  environment: "Production"
                  store_product_id: "premium_lifetime"
                  store_transaction_id: "1000000123456789"
                  store_original_transaction_id: "1000000123456789"
                  is_family_shared: false
                  price:
                    country: "US"
                    currency: "USD"
                    value: 9.99
                  purchased_at: "2024-01-15T10:30:00Z"
                  variation_id: "variation_123"
                  offer: null
                  refunded_at: null
                  cancellation_reason: null
              subscription:
                summary: Subscription example
                value:
                  purchase_type: "subscription"
                  store: "app_store"
                  environment: "Production"
                  store_product_id: "premium_monthly"
                  store_transaction_id: "1000000123456789"
                  store_original_transaction_id: "1000000123456789"
                  is_family_shared: false
                  price:
                    country: "US"
                    currency: "USD"
                    value: 4.99
                  purchased_at: "2024-01-15T10:30:00Z"
                  originally_purchased_at: "2024-01-15T10:30:00Z"
                  expires_at: "2024-02-15T10:30:00Z"
                  renew_status: true
                  renew_status_changed_at: null
                  billing_issue_detected_at: null
                  grace_period_expires_at: null
                  variation_id: "variation_123"
                  offer:
                    category: "introductory"
                    type: "free_trial"
                    id: "trial_offer_123"
                  refunded_at: null
                  cancellation_reason: null

  /api/v2/server-side-api/purchase/profile/grant/access-level/:
    post:
      summary: Grant access level
      description: Provides access level to your end-user without providing info on the transaction. This comes in handy if you have bonuses for referrals or other events related to your products. The access level provided by this method will not be reflected in your analytics. It will be sent to only webhook integration, and only in this case will appear in the Event Feed.
      operationId: grantAccessLevel
      tags:
        - Purchase
      security:
        - apikeyAuth: []
      parameters:
        - name: adapty-customer-user-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the customer in your system. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
        - name: adapty-profile-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the profile in your system. Your best option if you are working with anonymous profiles. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
      responses:
        '200':
          description: Access level granted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Profile not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal server error
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GrantAccessRequest'
            examples:
              immediate_grant:
                summary: Grant access immediately
                value:
                  access_level_id: "premium"
              scheduled_grant:
                summary: Grant access with start and end dates
                value:
                  access_level_id: "premium"
                  starts_at: "2024-01-01T00:00:00Z"
                  expires_at: "2024-12-31T23:59:59Z"
              lifetime_grant:
                summary: Grant lifetime access
                value:
                  access_level_id: "premium"
                  starts_at: "2024-01-01T00:00:00Z"

  /api/v2/server-side-api/purchase/profile/revoke/access-level/:
    post:
      summary: Revoke access level
      description: Removes an access level from an end user of your app in Adapty.
      operationId: revokeAccessLevel
      tags:
        - Purchase
      security:
        - apikeyAuth: []
      parameters:
        - name: adapty-customer-user-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the customer in your system. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
        - name: adapty-profile-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the profile in your system. Your best option if you are working with anonymous profiles. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
      responses:
        '200':
          description: Access level revoked successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Profile not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal server error
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RevokeAccessRequest'
            examples:
              immediate_revoke:
                summary: Revoke access immediately
                value:
                  access_level_id: "premium"
              scheduled_revoke:
                summary: Schedule access revocation
                value:
                  access_level_id: "premium"
                  revoke_at: "2024-12-31T23:59:59Z"

  # Refund Saver Management
  /api/v2/server-side-api/purchase/profile/refund-saver/settings/:
    get:
      summary: Get Refund Saver settings
      description: Get the Refund Saver preference for this user - whether Adapty should ask to decline or approve their refund request. Get the data sharing preference for this user – whether the user gave their consent to share their data with Apple.
      operationId: getRefundSaverSettings
      tags:
        - Refund Saver
      security:
        - apikeyAuth: []
      parameters:
        - name: adapty-customer-user-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the customer in your system. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
        - name: adapty-profile-id
          in: header
          required: false
          schema:
            type: string
            format: uuid
          description: The unique ID of the profile in your system. Your best option if you are working with anonymous profiles. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
      responses:
        '200':
          description: Settings retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RefundSaverSettingsResponse'
              example:
                profile_id: "e5aab402-b1bd-4039-b632-57a91ebc0779"
                settings:
                  consent: true
                  custom_preference: "no_preference"
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                profile_does_not_exist:
                  summary: Profile does not exist
                  value:
                    errors: ["Profile does not exist"]
                    error_code: "profile_does_not_exist"
                    status_code: 400
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors: ["Invalid API key"]
                error_code: "unauthorized"
                status_code: 401
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      summary: Set Refund Saver settings
      description: Set the refund preference individually for the user and record if the user gave their consent to share their data. By default, Refund Saver always asks Apple to decline a user's refund request. You can change this default behavior for all users in the Adapty Dashboard, or adjust it for a specific user using the Dashboard, the Adapty SDK, or the server-side API.
      operationId: setRefundSaverSettings
      tags:
        - Refund Saver
      security:
        - apikeyAuth: []
      parameters:
        - name: adapty-customer-user-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the customer in your system. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
        - name: adapty-profile-id
          in: header
          required: false
          schema:
            type: string
            format: uuid
          description: The unique ID of the profile in your system. Your best option if you are working with anonymous profiles. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefundSaverSettingsRequest'
            example:
              custom_preference: "grant"
              consent: true
      responses:
        '200':
          description: Settings updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RefundSaverSettingsResponse'
              example:
                profile_id: "e5aab402-b1bd-4039-b632-57a91ebc0779"
                settings:
                  consent: true
                  custom_preference: "grant"
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                profile_does_not_exist:
                  summary: Profile does not exist
                  value:
                    errors: ["Profile does not exist"]
                    error_code: "profile_does_not_exist"
                    status_code: 400
                validation_error:
                  summary: Validation error
                  value:
                    errors: ["Profile refund saver settings must have either consent or custom_preference or both"]
                    error_code: "validation_error"
                    status_code: 400
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors: ["Invalid API key"]
                error_code: "unauthorized"
                status_code: 401
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  # Stripe Integration
  /api/v1/sdk/purchase/stripe/token/validate/:
    post:
      summary: Validate Stripe purchase
      description: |
        Validates a purchase using the provided Stripe token using the credentials of Stripe in your App Settings inside Adapty Dashboard. 
        If the purchase is valid, the transaction history is imported from Stripe to the profile in Adapty with the specified customer_user_id. 
        If there was no profile with this customer_user_id before — it will be created.
        
        Profile events are generated along the way and imported transactions are counted towards MTR.
      operationId: validateStripePurchase
      tags:
        - Stripe
      security:
        - apikeyAuth: []
      requestBody:
        required: true
        content:
          application/vnd.api+json:
            schema:
              $ref: '#/components/schemas/StripeValidationRequest'
            example:
              data:
                type: "stripe_receipt_validation_result"
                attributes:
                  customer_user_id: "<YOUR_CUSTOMER_USER_ID>"
                  stripe_token: "<YOUR_STRIPE_TOKEN>"
      responses:
        '200':
          description: Purchase validated successfully
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/StripeValidationResponse'
              example:
                data: null
        '400':
          description: Bad request
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/StripeErrorResponse'
              example:
                errors:
                  - detail: "none is not an allowed value"
                    source:
                      pointer: "/data/attributes/stripe_token"
                    status: "400"
        '401':
          description: Unauthorized
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal server error
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  # Paddle Integration
  /api/v2/server-side-api/purchase/paddle/token/validate/:
    post:
      summary: Validate Paddle purchase
      description: |
        Validates a purchase using the provided Paddle token using the credentials of Paddle in your App Settings inside Adapty Dashboard. 
        If the purchase is valid, the transaction history is imported from Paddle to the profile in Adapty with the specified customer_user_id. 
        If there was no profile with this customer_user_id before — it will be created.
      operationId: validatePaddlePurchase
      tags:
        - Paddle
      security:
        - apikeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PaddleValidationRequest'
            example:
              customer_user_id: "<YOUR_CUSTOMER_USER_ID>"
              paddle_token: "live_7d279f61a3339fed520f7cd8c08"
      responses:
        '200':
          description: Purchase validated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileResponse'
              example:
                data:
                  app_id: "14c3d623-2f3a-455a-aa86-ef83dff6913b"
                  profile_id: "3286abd3-48b0-4e9c-a5f6-ac0a006804a6"
                  customer_user_id: "Jane.doe@example.com"
                  total_revenue_usd: 0.0
                  segment_hash: "8f45947bad31ab0c"
                  timestamp: 1736436751469
                  custom_attributes:
                    - key: "favourite_sport"
                      value: "yoga"
                  access_levels: []
                  subscriptions:
                    - purchase_id: "5a7ab471-2299-45f7-ad69-1d395c1256e3"
                      store: "app_store"
                      store_product_id: "1year.premium"
                      store_base_plan_id: null
                      store_transaction_id: "30002109551456"
                      store_original_transaction_id: "30002109551456"
                      purchased_at: "2022-10-12T09:42:50+00:00"
                      environment: "Production"
                      is_refund: false
                      is_consumable: false
                  non_subscriptions: []
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                no_products_found:
                  summary: No products found
                  value:
                    errors: ["No products found"]
                    error_code: "no_products_found"
                    status_code: 400
                paddle_api_key_not_found:
                  summary: Paddle API key not found
                  value:
                    errors: ["Paddle API key not found"]
                    error_code: "paddle_api_key_not_found"
                    status_code: 400
                invalid_paddle_credentials_or_purchase_not_found:
                  summary: Invalid Paddle credentials or purchase not found
                  value:
                    errors: ["Invalid Paddle credentials or purchase not found"]
                    error_code: "invalid_paddle_credentials_or_purchase_not_found"
                    status_code: 400
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors: ["Invalid API key"]
                error_code: "unauthorized"
                status_code: 401
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  # Integration Management
  /api/v2/server-side-api/integration/profile/set/integration-identifiers/:
    get:
      summary: Get integration identifiers
      description: Retrieves integration identifiers of a profile.
      operationId: getIntegrationIdentifiers
      tags:
        - Integration
      security:
        - apikeyAuth: []
      parameters:
        - name: adapty-customer-user-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the customer in your system. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
        - name: adapty-profile-id
          in: header
          required: false
          schema:
            type: string
            format: uuid
          description: The unique ID of the profile in your system. Your best option if you are working with anonymous profiles. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
      responses:
        '200':
          description: Integration identifiers retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntegrationIdentifiersResponse'
              example:
                facebook_anonymous_id: "XZ7EF7D15E-8FA1-49D8-B180-918EB333E42A"
                amplitude_user_id: null
                amplitude_device_id: null
                mixpanel_user_id: "33w6yv5DPqVlyMVbjW31xvzJLtJ3"
                appmetrica_profile_id: null
                appmetrica_device_id: null
                one_signal_player_id: null
                one_signal_subscription_id: "333ed338-757d-466a-a672-ab92db196a1f"
                pushwoosh_hwid: null
                firebase_app_instance_id: "C333B35DF1DB418E99F7B815E9F5C549"
                airbridge_device_id: null
                appsflyer_id: "1741933337626-3179568"
                branch_id: null
                adjust_device_id: null
                tenjin_analytics_installation_id: null
                posthog_distinct_user_id: null
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors: ["Invalid API key"]
                error_code: "unauthorized"
                status_code: 401
        '404':
          description: Profile not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors: ["Profile not found"]
                error_code: "profile_not_found"
                status_code: 404
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      summary: Set integration identifiers
      description: Adds integration identifiers to a profile.
      operationId: setIntegrationIdentifiers
      tags:
        - Integration
      security:
        - apikeyAuth: []
      parameters:
        - name: adapty-customer-user-id
          in: header
          required: false
          schema:
            type: string
          description: The unique ID of the customer in your system. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
        - name: adapty-profile-id
          in: header
          required: false
          schema:
            type: string
            format: uuid
          description: The unique ID of the profile in your system. Your best option if you are working with anonymous profiles. Either `adapty-customer-user-id` or `adapty-profile-id` is required.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IntegrationIdentifiersRequest'
            example:
              pushwoosh_hwid: "example_pushwoosh_hwid"
              mixpanel_user_id: "example_mixpanel_user_id"
              facebook_anonymous_id: "example_facebook_anonymous_id"
              firebase_app_instance_id: "example_firebase_app_instance_id"
              amplitude_user_id: "example_amplitude_user_id"
              amplitude_device_id: "example_amplitude_device_id"
              appmetrica_device_id: "example_appmetrica_device_id"
              appmetrica_profile_id: "example_appmetrica_profile_id"
              one_signal_subscription_id: "example_one_signal_subscription_id"
              one_signal_player_id: "example_one_signal_player_id"
              branch_id: "example_branch_id"
              appsflyer_id: "example_appsflyer_id"
              adjust_device_id: "example_adjust_device_id"
              airbridge_device_id: "example_airbridge_device_id"
              tenjin_analytics_installation_id: "example_tenjin_analytics_installation_id"
              posthog_distinct_user_id: "example_posthog_distinct_user_id"
      responses:
        '200':
          description: Integration identifiers set successfully. The response body is blank.
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors: ["At least one integration identifier must be provided."]
                error_code: "validation_error"
                status_code: 400
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors: ["Invalid API key"]
                error_code: "unauthorized"
                status_code: 401
        '404':
          description: Profile not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors: ["Profile not found"]
                error_code: "profile_not_found"
                status_code: 404
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  # Paywall Management
  /api/v2/server-side-api/paywalls/:
    get:
      summary: List paywalls
      description: Retrieves a list of paywalls in your app.
      operationId: listPaywalls
      tags:
        - Paywalls
      security:
        - apikeyAuth: []
      parameters:
        - name: page[number]
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
          description: Page number for pagination
        - name: page[size]
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          description: Number of items per page
      responses:
        '200':
          description: Paywalls retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaywallListResponse'
              example:
                data:
                  - title: "LlkTlizT"
                    paywall_id: "fd891d4f-5906-45b9-97c1-13cc3dc665df"
                    use_paywall_builder: false
                    use_paywall_builder_legacy: false
                    updated_at: "2025-07-08T07:27:06.754527+00:00"
                    created_at: "2025-07-08T07:27:06.754541+00:00"
                    state: "live"
                    is_deleted: false
                    web_purchase_url: null
                    products:
                      - product_id: "b95e9e51-a056-4eb6-9cf7-b75d139e7c3c"
                        title: "mFUQPcJQ"
                        product_set: "uncategorised"
                        offer: null
                  - title: "Premium Subscription"
                    paywall_id: "a1cf7850-1bb8-4151-8336-a4e588730c55"
                    use_paywall_builder: true
                    use_paywall_builder_legacy: false
                    updated_at: "2025-07-28T08:15:13.722680+00:00"
                    created_at: "2025-07-25T13:40:01.789853+00:00"
                    state: "live"
                    is_deleted: false
                    web_purchase_url: "https://example.com/purchase"
                    products:
                      - product_id: "b136422f-8153-402a-afbb-986929c68f6a"
                        title: "Premium Monthly"
                        product_set: "uncategorised"
                        offer:
                          product_offer_id: "e31a4296-f250-4faf-ac80-3cc93c2da8f5"
                          title: "Free Trial"
                meta:
                  pagination:
                    count: 365
                    page: 1
                    pages: 183
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - source: "page_size"
                    errors: ["Invalid page_size parameter. Must be between 1 and 100."]
                error_code: "validation_error"
                status_code: 400
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors: ["Invalid API key"]
                error_code: "unauthorized"
                status_code: 401
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v2/server-side-api/paywalls/{paywall_id}/:
    get:
      summary: Get paywall
      description: Retrieves a specific paywall in your app.
      operationId: getPaywall
      tags:
        - Paywalls
      security:
        - apikeyAuth: []
      parameters:
        - name: paywall_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the paywall to retrieve
      responses:
        '200':
          description: Paywall retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaywallResponse'
              example:
                title: "Premium Subscription"
                paywall_id: "fd891d4f-5906-45b9-97c1-13cc3dc665df"
                use_paywall_builder: true
                use_paywall_builder_legacy: false
                updated_at: "2025-07-30T11:13:58.798Z"
                created_at: "2025-07-30T11:13:58.798Z"
                state: "live"
                is_deleted: false
                web_purchase_url: "https://example.com/purchase"
                products:
                  - product_id: "b95e9e51-a056-4eb6-9cf7-b75d139e7c3c"
                    title: "Premium Monthly"
                    product_set: "uncategorised"
                    offer:
                      product_offer_id: "e31a4296-f250-4faf-ac80-3cc93c2da8f5"
                      title: "Free Trial"
                remote_configs:
                  - locale: "en"
                    data: "{\"title\":\"Premium Features\",\"subtitle\":\"Unlock all premium content\"}"
                main_screenshot:
                  image_id: 123456
                  url: "https://public-media.adapty.io/public/screenshot.jpg"
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors: ["Invalid API key"]
                error_code: "unauthorized"
                status_code: 401
        '404':
          description: Paywall not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - source: null
                    errors: ["Paywall not found"]
                error_code: "paywall_does_not_exist"
                status_code: 404
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    put:
      summary: Update paywall
      description: |
        Updates the remote config of a specific paywall. This endpoint allows you to modify the remote config values that help you to customize the paywall's appearance and behavior.
        
        **Important:** If you update a remote config, it will overwrite all the existing remote configs! If you need to preserve the existing remote configs, first, get the paywall. Then, copy the remote_configs from there and modify the objects you need in the update request.
      operationId: updatePaywall
      tags:
        - Paywalls
      security:
        - apikeyAuth: []
      parameters:
        - name: paywall_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier of the paywall to update
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PaywallUpdateRequest'
            example:
              remote_configs:
                - locale: "en"
                  data: "{\"title\":\"Premium Features\",\"subtitle\":\"Unlock all premium content\"}"
      responses:
        '200':
          description: Paywall updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaywallResponse'
              example:
                title: "Premium Subscription"
                paywall_id: "fd891d4f-5906-45b9-97c1-13cc3dc665df"
                use_paywall_builder: true
                use_paywall_builder_legacy: false
                updated_at: "2025-07-30T11:13:58.798Z"
                created_at: "2025-07-30T11:13:58.798Z"
                state: "live"
                is_deleted: false
                web_purchase_url: "https://example.com/purchase"
                products:
                  - product_id: "b95e9e51-a056-4eb6-9cf7-b75d139e7c3c"
                    title: "Premium Monthly"
                    product_set: "uncategorised"
                    offer:
                      product_offer_id: "e31a4296-f250-4faf-ac80-3cc93c2da8f5"
                      title: "Free Trial"
                remote_configs:
                  - locale: "en"
                    data: "{\"title\":\"Premium Features\",\"subtitle\":\"Unlock all premium content\"}"
                main_screenshot:
                  image_id: 123456
                  url: "https://public-media.adapty.io/public/screenshot.jpg"
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - source: "remote_configs"
                    errors: ["At least one field must be provided"]
                error_code: "validation_error"
                status_code: 400
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors: ["Invalid API key"]
                error_code: "unauthorized"
                status_code: 401
        '404':
          description: Paywall not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                  - source: null
                    errors: ["Paywall not found"]
                error_code: "paywall_does_not_exist"
                status_code: 404
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  securitySchemes:
    apikeyAuth:
      type: apiKey
      name: Authorization
      in: header
      default: Api-Key {Your secret API key}
      description: |
        API requests must be authenticated by your secret API key as the **Authorization** 
        header with the value `Api-Key {your_secret_api_key}`, for example, 
        `Api-Key secret_live_...`. Find this key in the Adapty Dashboard -> 
        **App Settings** -> **General** tab -> **API keys** section.
  requestBodies:
    DefaultJson:
      required: true
      content:
        application/json:
          schema:
            type: object
  schemas:
    ProfileRequest:
      type: object
      properties:
        first_name:
          type: string
          nullable: true
          description: Your end user's first name
        last_name:
          type: string
          nullable: true
          description: Your end user's last name
        gender:
          type: string
          nullable: true
          enum: [f, m, o]
          description: Your end user's gender
        email:
          type: string
          nullable: true
          format: email
          description: Your end user's email
        phone_number:
          type: string
          nullable: true
          description: Your end user's phone number
        birthday:
          type: string
          format: date
          nullable: true
          description: Your end user's birthday
        ip_country:
          type: string
          nullable: true
          description: Country of the end user in ISO 3166-2 format
        ip_v4_address:
          type: string
          nullable: true
          description: IPv4 address of the end user
        store_country:
          type: string
          nullable: true
          description: Country of the end user's app store
        store:
          type: string
          nullable: true
          enum: [app_store, play_store, stripe, adapty, paddle]
          description: The platform the user uses to make purchases in your app
        store_account_token:
          type: string
          format: uuid
          nullable: true
          description: Store account token
        att_status:
          type: integer
          nullable: true
          enum: [0, 1, 2, 3]
          description: Apple App Tracking Transparency status (0=not determined, 1=restricted, 2=denied, 3=authorized)
        analytics_disabled:
          type: boolean
          nullable: true
          description: Option to opt out of external analytics
        custom_attributes:
          type: array
          items:
            $ref: '#/components/schemas/CustomAttribute'
          description: Allows setting up to 30 custom attributes for the profile
        installation_meta:
          $ref: '#/components/schemas/InstallationMeta'
      required:
        - installation_meta
      example:
        first_name: "Jane"
        last_name: "Doe"
        gender: "f"
        email: "jane.doe@example.com"
        phone_number: "+1234567890"
        birthday: "2000-12-31"
        ip_country: "FR"
        ip_v4_address: "192.168.1.1"
        store_country: "US"
        store: "app_store"
        store_account_token: "3fa85f64-5717-4562-b3fc-2c963f66afa6"
        att_status: 3
        analytics_disabled: true
        custom_attributes:
          - key: "favourite_sport"
            value: "yoga"
        installation_meta:
          device_id: "3fa85f64-5717-4562-b3fc-2c963f66afa6"
          device: "iPhone 15 Pro"
          locale: "en"
          os: "iOS 17.1"
          platform: "iOS"
          timezone: "Europe/Rome"
          user_agent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Mobile/15E148 Safari/604.1"
          app_build: "1.0.0"
          app_version: "1.0.0"
          adapty_sdk_version: "2.0.0"
          idfa: "EA7583CD-A667-48BC-B806-42ECB2B48333"
          idfv: "E9D48DA5-3930-4B41-8521-D953AECD2F33"
          advertising_id: ""
          android_id: ""
          android_app_set_id: ""
    
    ProfileResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/Profile'
      required:
        - data
    
    Profile:
      type: object
      properties:
        app_id:
          type: string
          format: uuid
          description: The internal ID of your app
        profile_id:
          type: string
          format: uuid
          description: Adapty profile ID
        customer_user_id:
          type: string
          nullable: true
          description: The ID of your user in your system
        total_revenue_usd:
          type: number
          format: float
          description: A float value representing the total revenue in USD earned in the profile
        segment_hash:
          type: string
          description: Internal parameter
        timestamp:
          type: integer
          format: int64
          description: Response time in milliseconds, needs for resolve a race condition
        custom_attributes:
          type: array
          items:
            $ref: '#/components/schemas/CustomAttribute'
          description: A maximum of 30 custom attributes to the profile are allowed to be set
        access_levels:
          type: array
          items:
            $ref: '#/components/schemas/AccessLevel'
          description: Array of Access level objects. Empty array if the customer has no access levels
        subscriptions:
          type: array
          items:
            $ref: '#/components/schemas/Subscription'
          description: Array of Subscription objects. Empty array if the customer has no subscriptions
        non_subscriptions:
          type: array
          items:
            $ref: '#/components/schemas/NonSubscription'
          description: Array of Non-Subscription objects. Empty array if the customer has no purchases
      required:
        - app_id
        - profile_id
        - customer_user_id
        - total_revenue_usd
        - segment_hash
        - timestamp
        - custom_attributes
        - access_levels
        - subscriptions
        - non_subscriptions
    
    CustomAttribute:
      type: object
      properties:
        key:
          type: string
          maxLength: 30
          description: The key must be a string with no more than 30 characters. Only letters, numbers, dashes, points, and underscores allowed
        value:
          oneOf:
            - type: string
            - type: number
          description: The attribute value must be no more than 50 characters. Only strings and floats are allowed as values
      required:
        - key
        - value
    
    InstallationMeta:
      type: object
      properties:
        device_id:
          type: string
          format: uuid
          description: Unique device identifier
        device:
          type: string
          nullable: true
          description: Device information
        locale:
          type: string
          nullable: true
          description: Device locale
        os:
          type: string
          nullable: true
          description: Operating system information
        platform:
          type: string
          nullable: true
          enum: [iOS, macOS, iPadOS, Android, visionOS, web]
          description: Platform (iOS, Android, etc.)
        timezone:
          type: string
          nullable: true
          description: Device timezone
        user_agent:
          type: string
          nullable: true
          description: User agent string
        app_build:
          type: string
          nullable: true
          description: App build version
        app_version:
          type: string
          nullable: true
          description: App version
        adapty_sdk_version:
          type: string
          nullable: true
          description: Adapty SDK version
        idfa:
          type: string
          nullable: true
          description: iOS Identifier for Advertisers
        idfv:
          type: string
          nullable: true
          description: iOS Identifier for Vendor
        advertising_id:
          type: string
          nullable: true
          description: Android advertising ID
        android_id:
          type: string
          nullable: true
          description: Android device ID
        android_app_set_id:
          type: string
          nullable: true
          description: Android app set ID
      required:
        - device_id
    
    AccessLevel:
      type: object
      properties:
        access_level_id:
          type: string
          description: Access level identifier
        store:
          type: string
          description: Store where the access level was purchased
        store_product_id:
          type: string
          description: Product ID in the store
        store_base_plan_id:
          type: string
          nullable: true
          description: Base plan ID in the store
        store_transaction_id:
          type: string
          description: Transaction ID in the store
        store_original_transaction_id:
          type: string
          description: Original transaction ID in the store
        offer:
          allOf:
            - $ref: '#/components/schemas/OfferDTO'
          nullable: true
          description: Offer details, if a promotional or introductory offer was applied
        starts_at:
          type: string
          format: date-time
          nullable: true
          description: When the access level starts
        purchased_at:
          type: string
          format: date-time
          description: When the access level was purchased
        originally_purchased_at:
          type: string
          format: date-time
          description: When the access level was originally purchased
        expires_at:
          type: string
          format: date-time
          nullable: true
          description: When the access level expires
        renewal_cancelled_at:
          type: string
          format: date-time
          nullable: true
          description: When renewal was cancelled
        billing_issue_detected_at:
          type: string
          format: date-time
          nullable: true
          description: When billing issue was detected
        is_in_grace_period:
          type: boolean
          description: Whether the access level is in grace period
        cancellation_reason:
          type: string
          nullable: true
          description: Reason for cancellation
    
    Subscription:
      type: object
      properties:
        store:
          type: string
          description: Store where the subscription was purchased
        store_product_id:
          type: string
          description: Product ID in the store
        store_base_plan_id:
          type: string
          nullable: true
          description: Base plan ID in the store
        store_transaction_id:
          type: string
          description: Transaction ID in the store
        store_original_transaction_id:
          type: string
          description: Original transaction ID in the store
        offer:
          allOf:
            - $ref: '#/components/schemas/OfferDTO'
          nullable: true
          description: Offer details, if a promotional or introductory offer was applied
        environment:
          type: string
          description: Environment (Sandbox, Production)
        purchased_at:
          type: string
          format: date-time
          description: When the subscription was purchased
        originally_purchased_at:
          type: string
          format: date-time
          description: When the subscription was originally purchased
        expires_at:
          type: string
          format: date-time
          nullable: true
          description: When the subscription expires
        renewal_cancelled_at:
          type: string
          format: date-time
          nullable: true
          description: When renewal was cancelled
        billing_issue_detected_at:
          type: string
          format: date-time
          nullable: true
          description: When billing issue was detected
        is_in_grace_period:
          type: boolean
          description: Whether the subscription is in grace period
        cancellation_reason:
          type: string
          nullable: true
          description: Reason for cancellation
    
    NonSubscription:
      type: object
      properties:
        purchase_id:
          type: string
          format: uuid
          description: Unique purchase identifier
        store:
          type: string
          description: Store where the purchase was made
        store_product_id:
          type: string
          description: Product ID in the store
        store_base_plan_id:
          type: string
          nullable: true
          description: Base plan ID in the store
        store_transaction_id:
          type: string
          description: Transaction ID in the store
        store_original_transaction_id:
          type: string
          description: Original transaction ID in the store
        purchased_at:
          type: string
          format: date-time
          description: When the purchase was made
        environment:
          type: string
          description: Environment (Sandbox, Production)
        is_refund:
          type: boolean
          description: Whether this is a refund
        is_consumable:
          type: boolean
          description: Whether this is a consumable purchase
    
    ErrorResponse:
      type: object
      properties:
        errors:
          type: array
          items:
            type: object
            properties:
              source:
                type: string
                nullable: true
                description: Source of the error
              errors:
                type: array
                items:
                  type: string
                description: Array of error messages
        error_code:
          type: string
          description: Short error name
        status_code:
          type: integer
          description: HTTP status code
      required:
        - errors
        - error_code
        - status_code
    
    TransactionDTORequest:
      oneOf:
        - $ref: '#/components/schemas/OneTimePurchaseDTO'
        - $ref: '#/components/schemas/SubscriptionDTO'
      discriminator:
        propertyName: purchase_type

    OneTimePurchaseDTO:
      type: object
      title: One-time Purchase
      description: Transaction data for a one-time purchase
      properties:
        purchase_type:
          type: string
          enum: [one_time_purchase]
          description: Type of purchase
        store:
          type: string
          description: Store where the purchase was made. Common values include `app_store`, `play_store`, `stripe`, `paddle`, or any custom store identifier
        environment:
          type: string
          enum: [Production, Sandbox]
          default: Production
          description: Environment where the purchase was made
        store_product_id:
          type: string
          description: Product ID in the store
        store_transaction_id:
          type: string
          description: Transaction ID in the store
        store_original_transaction_id:
          type: string
          description: Original transaction ID in the store
        is_family_shared:
          type: boolean
          default: false
          description: Whether the purchase is family shared
        price:
          $ref: '#/components/schemas/PriceDTO'
        purchased_at:
          type: string
          format: date-time
          description: When the purchase was made
        variation_id:
          type: string
          nullable: true
          description: Variation ID for A/B testing
        offer:
          $ref: '#/components/schemas/OfferDTO'
          nullable: true
        refunded_at:
          type: string
          format: date-time
          nullable: true
          description: When the purchase was refunded
        cancellation_reason:
          type: string
          enum: [billing_error, cancelled_by_developer, new_subscription_replace, price_increase, product_was_not_available, refund, unknown, upgraded, voluntarily_cancelled, adapty_revoked]
          nullable: true
          description: Reason for cancellation
      required:
        - purchase_type
        - store
        - store_product_id
        - store_transaction_id
        - store_original_transaction_id
        - price
        - purchased_at

    SubscriptionDTO:
      type: object
      title: Subscription
      description: Transaction data for a subscription purchase
      properties:
        purchase_type:
          type: string
          enum: [subscription]
          description: Type of purchase
        store:
          type: string
          description: Store where the purchase was made. Common values include `app_store`, `play_store`, `stripe`, `paddle`, or any custom store identifier
        environment:
          type: string
          enum: [Production, Sandbox]
          default: Production
          description: Environment where the purchase was made
        store_product_id:
          type: string
          description: Product ID in the store
        store_transaction_id:
          type: string
          description: Transaction ID in the store
        store_original_transaction_id:
          type: string
          description: Original transaction ID in the store
        is_family_shared:
          type: boolean
          default: false
          description: Whether the purchase is family shared
        price:
          $ref: '#/components/schemas/PriceDTO'
        purchased_at:
          type: string
          format: date-time
          description: When the purchase was made
        variation_id:
          type: string
          nullable: true
          description: Variation ID for A/B testing
        offer:
          $ref: '#/components/schemas/OfferDTO'
          nullable: true
        refunded_at:
          type: string
          format: date-time
          nullable: true
          description: When the purchase was refunded
        cancellation_reason:
          type: string
          enum: [billing_error, cancelled_by_developer, new_subscription_replace, price_increase, product_was_not_available, refund, unknown, upgraded, voluntarily_cancelled, adapty_revoked]
          nullable: true
          description: Reason for cancellation
        # Subscription-specific fields
        originally_purchased_at:
          type: string
          format: date-time
          description: When the subscription was originally purchased
        expires_at:
          type: string
          format: date-time
          description: When the subscription expires
        renew_status:
          type: boolean
          description: Whether the subscription will renew
        renew_status_changed_at:
          type: string
          format: date-time
          nullable: true
          description: When the renewal status was changed
        billing_issue_detected_at:
          type: string
          format: date-time
          nullable: true
          description: When billing issue was detected
        grace_period_expires_at:
          type: string
          format: date-time
          nullable: true
          description: When grace period expires
      required:
        - purchase_type
        - store
        - store_product_id
        - store_transaction_id
        - store_original_transaction_id
        - price
        - purchased_at
        - originally_purchased_at
        - expires_at
        - renew_status

    PriceDTO:
      type: object
      properties:
        country:
          type: string
          description: Country code
        currency:
          type: string
          description: Currency code
        value:
          type: number
          format: float
          description: Price value
      required:
        - country
        - currency
        - value

    OfferDTO:
      type: object
      properties:
        category:
          type: string
          enum: [introductory, promotional, offer_code, win_back]
          description: Offer category
        type:
          type: string
          enum: [free_trial, pay_as_you_go, pay_up_front]
          description: Offer type
        id:
          type: string
          nullable: true
          description: Offer ID
      required:
        - category
        - type
    
    GrantAccessRequest:
      type: object
      properties:
        access_level_id:
          type: string
          description: Paid access level ID configured in the Access Levels page
        starts_at:
          type: string
          format: date-time
          nullable: true
          description: The datetime when the access level will be active. Maybe in the future. The default value is null.
        expires_at:
          type: string
          format: date-time
          nullable: true
          description: The datetime when the access level will expire. It may be in the past and may be null for lifetime access. The default value is null.
      required:
        - access_level_id
    
    RevokeAccessRequest:
      type: object
      properties:
        access_level_id:
          type: string
          description: Paid access level ID configured in the Access Levels page
        revoke_at:
          type: string
          format: date-time
          nullable: true
          description: Specifies when the access level will expire. To revoke access immediately, either omit this field or set it to null. The default value is null.
      required:
        - access_level_id
    
    RefundSaverSettingsRequest:
      type: object
      properties:
        custom_preference:
          type: string
          enum: [grant, no_preference, decline]
          nullable: true
          description: |
            Set the refund preference individually for the user.
            - `grant`: approve each refund request
            - `no_preference`: do not provide any recommendations
            - `decline`: decline each refund request
        consent:
          type: boolean
          nullable: true
          description: |
            Record if the user gave their consent to share their data.
            - `true` means that if you receive an in-app refund request, you may provide Apple with information about the user
      example:
        custom_preference: "grant"
        consent: true
    
    RefundSaverSettingsResponse:
      type: object
      properties:
        profile_id:
          type: string
          format: uuid
          description: Customer profile ID
        settings:
          type: object
          properties:
            consent:
              type: boolean
              description: Defines whether the user consented to share their data
            custom_preference:
              type: string
              enum: [grant, no_preference, decline]
              description: The refund preference
          description: Refund saver settings for the user
      required:
        - profile_id
        - settings
    
    StripeValidationRequest:
      type: object
      properties:
        data:
          type: object
          properties:
            type:
              type: string
              enum: [stripe_receipt_validation_result]
              description: The type of the resource
            attributes:
              type: object
              properties:
                customer_user_id:
                  type: string
                  description: The ID of your user in your system
                stripe_token:
                  type: string
                  description: |
                    Token of a Stripe object that represents a unique purchase. 
                    Could either be a token of Stripe's Subscription (sub_XXX) or Payment Intent (pi_XXX)
              required:
                - customer_user_id
                - stripe_token
          required:
            - type
            - attributes
      required:
        - data
    
    StripeValidationResponse:
      type: object
      properties:
        data:
          type: object
          nullable: true
          description: Response data (null for successful validation)
      required:
        - data
    
    StripeErrorResponse:
      type: object
      properties:
        errors:
          type: array
          items:
            type: object
            properties:
              detail:
                type: string
                description: Descriptive information about the error
              source:
                type: object
                properties:
                  pointer:
                    type: string
                    description: References the exact location in the request document causing the issue
                required:
                  - pointer
              status:
                type: string
                description: HTTP status code
            required:
              - detail
              - source
              - status
      required:
        - errors
    
    PaddleValidationRequest:
      type: object
      properties:
        customer_user_id:
          type: string
          description: The ID of your user in your system
        paddle_token:
          type: string
          description: |
            Token of a Paddle object that represents a unique purchase. 
            Could be either a transaction id (txn_...) or a subscription id (sub_...)
      required:
        - customer_user_id
        - paddle_token
    
    IntegrationIdentifiersRequest:
      type: object
      properties:
        adjust_device_id:
          type: string
          nullable: true
          description: The network user's ID in the Adjust integration
        airbridge_device_id:
          type: string
          nullable: true
          description: The ID of the user's device in Airbridge integration
        amplitude_device_id:
          type: string
          nullable: true
          description: The ID of the user's device in Amplitude integration
        amplitude_user_id:
          type: string
          nullable: true
          description: The ID of the user in Amplitude integration
        appmetrica_device_id:
          type: string
          nullable: true
          description: The ID of the user's device in AppMetrica integration
        appmetrica_profile_id:
          type: string
          nullable: true
          description: The ID of the user in AppMetrica integration
        appsflyer_id:
          type: string
          nullable: true
          description: The network user's ID in the AppsFlyer integration
        branch_id:
          type: string
          nullable: true
          description: The Branch Key of the user's app in the Branch integration
        facebook_anonymous_id:
          type: string
          nullable: true
          description: The ID of the user in Facebook Ads integration
        firebase_app_instance_id:
          type: string
          nullable: true
          description: The ID of the user in Firebase integration
        mixpanel_user_id:
          type: string
          nullable: true
          description: The ID of the user in Mixpanel integration
        one_signal_player_id:
          type: string
          nullable: true
          description: The ID of the user in OneSignal integration (Legacy identifier)
        one_signal_subscription_id:
          type: string
          nullable: true
          description: The ID of the user in OneSignal integration (Recommended identifier)
        posthog_distinct_user_id:
          type: string
          nullable: true
          description: The ID of the user in PostHog integration
        pushwoosh_hwid:
          type: string
          nullable: true
          description: The ID of the user's device in Pushwoosh integration
        tenjin_analytics_installation_id:
          type: string
          nullable: true
          description: The ID of the user's device in Tenjin integration
      description: At least one integration identifier must be provided
    
    IntegrationIdentifiersResponse:
      type: object
      properties:
        adjust_device_id:
          type: string
          nullable: true
          description: The network user's ID in the Adjust integration
        airbridge_device_id:
          type: string
          nullable: true
          description: The ID of the user's device in Airbridge integration
        amplitude_device_id:
          type: string
          nullable: true
          description: The ID of the user's device in Amplitude integration
        amplitude_user_id:
          type: string
          nullable: true
          description: The ID of the user in Amplitude integration
        appmetrica_device_id:
          type: string
          nullable: true
          description: The ID of the user's device in AppMetrica integration
        appmetrica_profile_id:
          type: string
          nullable: true
          description: The ID of the user in AppMetrica integration
        appsflyer_id:
          type: string
          nullable: true
          description: The network user's ID in the AppsFlyer integration
        branch_id:
          type: string
          nullable: true
          description: The Branch Key of the user's app in the Branch integration
        facebook_anonymous_id:
          type: string
          nullable: true
          description: The ID of the user in Facebook Ads integration
        firebase_app_instance_id:
          type: string
          nullable: true
          description: The ID of the user in Firebase integration
        mixpanel_user_id:
          type: string
          nullable: true
          description: The ID of the user in Mixpanel integration
        one_signal_player_id:
          type: string
          nullable: true
          description: The ID of the user in OneSignal integration (Legacy identifier)
        one_signal_subscription_id:
          type: string
          nullable: true
          description: The ID of the user in OneSignal integration (Recommended identifier)
        posthog_distinct_user_id:
          type: string
          nullable: true
          description: The ID of the user in PostHog integration
        pushwoosh_hwid:
          type: string
          nullable: true
          description: The ID of the user's device in Pushwoosh integration
        tenjin_analytics_installation_id:
          type: string
          nullable: true
          description: The ID of the user's device in Tenjin integration
    
    PaywallUpdateRequest:
      type: object
      properties:
        remote_configs:
          type: array
          items:
            type: object
            properties:
              locale:
                type: string
                description: The locale for the remote config (e.g., "en", "es", "fr")
              data:
                type: string
                description: JSON string containing the remote config data
            required:
              - locale
              - data
          description: Array of RemoteConfig objects to update
      required:
        - remote_configs
      description: At least one field must be provided
    
    PaywallResponse:
      type: object
      properties:
        title:
          type: string
          description: The name of the paywall, as defined in your Adapty Dashboard
        paywall_id:
          type: string
          format: uuid
          description: The unique identifier of the paywall
        use_paywall_builder:
          type: boolean
          description: Whether the paywall uses the paywall builder
        use_paywall_builder_legacy:
          type: boolean
          description: Whether the paywall uses the legacy paywall builder
        updated_at:
          type: string
          format: date-time
          description: Timestamp when the paywall was last updated
        created_at:
          type: string
          format: date-time
          description: Timestamp when the paywall was created
        state:
          type: string
          enum: [draft, live, inactive, archived]
          description: The current state of the paywall
        is_deleted:
          type: boolean
          description: Whether the paywall is marked as deleted
        web_purchase_url:
          type: string
          format: uri
          nullable: true
          description: URL for web purchases, if applicable
        products:
          type: array
          items:
            type: object
            properties:
              product_id:
                type: string
                format: uuid
                description: The unique identifier of the product
              title:
                type: string
                description: The title of the product
              product_set:
                type: string
                enum: [weekly, monthly, trimonthly, semiannual, annual, lifetime, uncategorised, nonsubscriptions, two_months, consumable]
                description: The product set category
              offer:
                type: object
                nullable: true
                properties:
                  product_offer_id:
                    type: string
                    format: uuid
                    description: The unique identifier of the product offer
                  title:
                    type: string
                    description: The title of the offer
                required:
                  - product_offer_id
                  - title
            required:
              - product_id
              - title
              - product_set
              - offer
          description: Array of Product objects containing product information
        remote_configs:
          type: array
          items:
            type: object
            properties:
              locale:
                type: string
                description: The locale for the remote config
              data:
                type: string
                description: JSON string containing the remote config data
            required:
              - locale
              - data
          description: Array of RemoteConfig objects with locale and data
        main_screenshot:
          type: object
          nullable: true
          properties:
            image_id:
              type: integer
              description: The unique identifier of the image
            url:
              type: string
              format: uri
              description: The URL of the image
          required:
            - image_id
            - url
          description: Main screenshot object with image_id and url
      required:
        - title
        - paywall_id
        - use_paywall_builder
        - use_paywall_builder_legacy
        - updated_at
        - created_at
        - state
        - is_deleted
        - products
    
    PaywallListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/PaywallListItem'
          description: Array of paywall objects
        meta:
          type: object
          properties:
            pagination:
              type: object
              properties:
                count:
                  type: integer
                  description: Total number of paywalls
                page:
                  type: integer
                  description: Current page number
                pages:
                  type: integer
                  description: Total number of pages
              required:
                - count
                - page
                - pages
          required:
            - pagination
      required:
        - data
        - meta
    
    PaywallListItem:
      type: object
      properties:
        title:
          type: string
          description: The name of the paywall, as defined in your Adapty Dashboard
        paywall_id:
          type: string
          format: uuid
          description: The unique identifier of the paywall
        use_paywall_builder:
          type: boolean
          description: Whether the paywall uses the paywall builder
        use_paywall_builder_legacy:
          type: boolean
          description: Whether the paywall uses the legacy paywall builder
        updated_at:
          type: string
          format: date-time
          description: Timestamp when the paywall was last updated
        created_at:
          type: string
          format: date-time
          description: Timestamp when the paywall was created
        state:
          type: string
          enum: [draft, live, inactive, archived]
          description: The current state of the paywall
        is_deleted:
          type: boolean
          description: Whether the paywall is marked as deleted
        web_purchase_url:
          type: string
          format: uri
          nullable: true
          description: URL for web purchases, if applicable
        products:
          type: array
          items:
            type: object
            properties:
              product_id:
                type: string
                format: uuid
                description: The unique identifier of the product
              title:
                type: string
                description: The title of the product
              product_set:
                type: string
                enum: [weekly, monthly, trimonthly, semiannual, annual, lifetime, uncategorised, nonsubscriptions, two_months, consumable]
                description: The product set category
              offer:
                type: object
                nullable: true
                properties:
                  product_offer_id:
                    type: string
                    format: uuid
                    description: The unique identifier of the product offer
                  title:
                    type: string
                    description: The title of the offer
                required:
                  - product_offer_id
                  - title
            required:
              - product_id
              - title
              - product_set
              - offer
          description: Array of product objects containing product information for the paywall
      required:
        - title
        - paywall_id
        - use_paywall_builder
        - use_paywall_builder_legacy
        - updated_at
        - created_at
        - state
        - is_deleted
        - products
    
    
    # ========================================
    # API OBJECTS REFERENCE
    # ========================================
    # This section contains all the core data structures used throughout the API
    
    # Profile Objects
    ProfileObject:
      type: object
      description: |
        A Profile represents an end user in your app. It contains all the user's subscription data, 
        purchase history, and custom attributes. Profiles are identified by either a customer_user_id 
        (your internal user ID) or a profile_id (Adapty's internal ID).
      properties:
        app_id:
          type: string
          format: uuid
          description: The unique identifier of your app in Adapty
        profile_id:
          type: string
          format: uuid
          description: The unique identifier of the profile in Adapty
        customer_user_id:
          type: string
          nullable: true
          description: The ID of your user in your system
        total_revenue_usd:
          type: number
          format: float
          description: Total revenue generated by this user in USD
        segment_hash:
          type: string
          description: Hash of the user's segment for analytics
        timestamp:
          type: integer
          format: int64
          description: Unix timestamp of the last profile update
        custom_attributes:
          type: array
          items:
            type: object
            properties:
              key:
                type: string
                description: The attribute key
              value:
                type: string
                description: The attribute value
            required: [key, value]
          description: Array of custom attributes for the user
        access_levels:
          type: array
          items:
            $ref: '#/components/schemas/AccessLevelObject'
          description: Array of access levels the user has
        subscriptions:
          type: array
          items:
            $ref: '#/components/schemas/SubscriptionObject'
          description: Array of active subscriptions
        non_subscriptions:
          type: array
          items:
            $ref: '#/components/schemas/NonSubscriptionObject'
          description: Array of one-time purchases
      required:
        - app_id
        - profile_id
        - customer_user_id
        - total_revenue_usd
        - segment_hash
        - timestamp
        - custom_attributes
        - access_levels
        - subscriptions
        - non_subscriptions
    
    AccessLevelObject:
      type: object
      description: |
        An Access Level represents a paid feature or content tier in your app. 
        Users can have multiple access levels, and each level can have different expiration dates.
      properties:
        id:
          type: string
          description: The unique identifier of the access level
        is_active:
          type: boolean
          description: Whether the access level is currently active
        vendor_product_id:
          type: string
          description: The product ID in the app store
        store:
          type: string
          description: The store where the access level was purchased. Common values include app_store, play_store, stripe, paddle, or any custom store identifier
        activated_at:
          type: string
          format: date-time
          description: When the access level was activated
        renewed_at:
          type: string
          format: date-time
          nullable: true
          description: When the access level was last renewed
        expires_at:
          type: string
          format: date-time
          nullable: true
          description: When the access level expires (null for lifetime access)
        is_lifetime:
          type: boolean
          description: Whether this is a lifetime access level
        active_introductory_offer_type:
          type: string
          nullable: true
          description: Type of active introductory offer
        active_promotional_offer_type:
          type: string
          nullable: true
          description: Type of active promotional offer
        will_renew:
          type: boolean
          description: Whether the access level will auto-renew
        is_in_grace_period:
          type: boolean
          description: Whether the access level is in a grace period
        unsubscribed_at:
          type: string
          format: date-time
          nullable: true
          description: When the user unsubscribed
        billing_issue_detected_at:
          type: string
          format: date-time
          nullable: true
          description: When a billing issue was detected
      required:
        - id
        - is_active
        - vendor_product_id
        - store
        - activated_at
        - renewed_at
        - expires_at
        - is_lifetime
        - active_introductory_offer_type
        - active_promotional_offer_type
        - will_renew
        - is_in_grace_period
        - unsubscribed_at
        - billing_issue_detected_at
    
    SubscriptionObject:
      type: object
      description: |
        A Subscription represents an active subscription purchase. It contains information about 
        the subscription product, purchase details, and renewal status.
      properties:
        purchase_id:
          type: string
          format: uuid
          description: The unique identifier of the purchase
        store:
          type: string
          description: The store where the subscription was purchased. Common values include app_store, play_store, stripe, paddle, or any custom store identifier
        store_product_id:
          type: string
          description: The product ID in the store
        store_base_plan_id:
          type: string
          nullable: true
          description: The base plan ID in the store
        store_transaction_id:
          type: string
          description: The transaction ID in the store
        store_original_transaction_id:
          type: string
          description: The original transaction ID in the store
        purchased_at:
          type: string
          format: date-time
          description: When the subscription was purchased
        environment:
          type: string
          enum: [Production, Sandbox]
          description: The environment where the purchase was made
        is_refund:
          type: boolean
          description: Whether this is a refunded purchase
        is_consumable:
          type: boolean
          description: Whether this is a consumable purchase
      required:
        - purchase_id
        - store
        - store_product_id
        - store_base_plan_id
        - store_transaction_id
        - store_original_transaction_id
        - purchased_at
        - environment
        - is_refund
        - is_consumable
    
    NonSubscriptionObject:
      type: object
      description: |
        A Non-Subscription represents a one-time purchase. It contains information about 
        the product and purchase details.
      properties:
        purchase_id:
          type: string
          format: uuid
          description: The unique identifier of the purchase
        store:
          type: string
          description: The store where the purchase was made. Common values include app_store, play_store, stripe, paddle, or any custom store identifier
        store_product_id:
          type: string
          description: The product ID in the store
        store_transaction_id:
          type: string
          description: The transaction ID in the store
        purchased_at:
          type: string
          format: date-time
          description: When the purchase was made
        environment:
          type: string
          enum: [Production, Sandbox]
          description: The environment where the purchase was made
        is_refund:
          type: boolean
          description: Whether this is a refunded purchase
        is_consumable:
          type: boolean
          description: Whether this is a consumable purchase
      required:
        - purchase_id
        - store
        - store_product_id
        - store_transaction_id
        - purchased_at
        - environment
        - is_refund
        - is_consumable
    
    # Paywall Objects
    PaywallObject:
      type: object
      description: |
        A Paywall represents a monetization screen in your app. It contains information about 
        the paywall configuration, products, and remote configs.
      properties:
        title:
          type: string
          description: The name of the paywall, as defined in your Adapty Dashboard
        paywall_id:
          type: string
          format: uuid
          description: The unique identifier of the paywall
        use_paywall_builder:
          type: boolean
          description: Whether the paywall uses the paywall builder
        use_paywall_builder_legacy:
          type: boolean
          description: Whether the paywall uses the legacy paywall builder
        updated_at:
          type: string
          format: date-time
          description: Timestamp when the paywall was last updated
        created_at:
          type: string
          format: date-time
          description: Timestamp when the paywall was created
        state:
          type: string
          enum: [draft, live, inactive, archived]
          description: The current state of the paywall
        is_deleted:
          type: boolean
          description: Whether the paywall is marked as deleted
        web_purchase_url:
          type: string
          format: uri
          nullable: true
          description: URL for web purchases, if applicable
        products:
          type: array
          items:
            $ref: '#/components/schemas/ProductObject'
          description: Array of products available in this paywall
        remote_configs:
          type: array
          items:
            $ref: '#/components/schemas/RemoteConfigObject'
          description: Array of remote configs for different locales
        main_screenshot:
          type: object
          nullable: true
          properties:
            image_id:
              type: integer
              description: The unique identifier of the image
            url:
              type: string
              format: uri
              description: The URL of the image
          required: [image_id, url]
          description: Main screenshot object with image_id and url
      required:
        - title
        - paywall_id
        - use_paywall_builder
        - use_paywall_builder_legacy
        - updated_at
        - created_at
        - state
        - is_deleted
        - products
    
    ProductObject:
      type: object
      description: |
        A Product represents a purchasable item in your app. It can be a subscription or 
        a one-time purchase product.
      properties:
        product_id:
          type: string
          format: uuid
          description: The unique identifier of the product
        title:
          type: string
          description: The title of the product
        product_set:
          type: string
          description: The product set category
        offer:
          type: object
          nullable: true
          properties:
            product_offer_id:
              type: string
              format: uuid
              description: The unique identifier of the product offer
            title:
              type: string
              description: The title of the offer
          required: [product_offer_id, title]
          description: Product offer information
      required:
        - product_id
        - title
        - product_set
        - offer
    
    RemoteConfigObject:
      type: object
      description: |
        A Remote Config contains locale-specific configuration data for a paywall. 
        This allows you to customize paywall appearance and behavior for different languages.
      properties:
        locale:
          type: string
          description: The locale for the remote config (e.g., "en", "es", "fr")
        data:
          type: string
          description: JSON string containing the remote config data
      required:
        - locale
        - data
    
    # Integration Objects
    IntegrationIdentifiersObject:
      type: object
      description: |
        Integration Identifiers contain user IDs from various third-party services. 
        These are used to sync user data across different analytics and marketing platforms.
      properties:
        adjust_device_id:
          type: string
          nullable: true
          description: The network user's ID in the Adjust integration
        airbridge_device_id:
          type: string
          nullable: true
          description: The ID of the user's device in Airbridge integration
        amplitude_device_id:
          type: string
          nullable: true
          description: The ID of the user's device in Amplitude integration
        amplitude_user_id:
          type: string
          nullable: true
          description: The ID of the user in Amplitude integration
        appmetrica_device_id:
          type: string
          nullable: true
          description: The ID of the user's device in AppMetrica integration
        appmetrica_profile_id:
          type: string
          nullable: true
          description: The ID of the user in AppMetrica integration
        appsflyer_id:
          type: string
          nullable: true
          description: The network user's ID in the AppsFlyer integration
        branch_id:
          type: string
          nullable: true
          description: The Branch Key of the user's app in the Branch integration
        facebook_anonymous_id:
          type: string
          nullable: true
          description: The ID of the user in Facebook Ads integration
        firebase_app_instance_id:
          type: string
          nullable: true
          description: The ID of the user in Firebase integration
        mixpanel_user_id:
          type: string
          nullable: true
          description: The ID of the user in Mixpanel integration
        one_signal_player_id:
          type: string
          nullable: true
          description: The ID of the user in OneSignal integration (Legacy identifier)
        one_signal_subscription_id:
          type: string
          nullable: true
          description: The ID of the user in OneSignal integration (Recommended identifier)
        posthog_distinct_user_id:
          type: string
          nullable: true
          description: The ID of the user in PostHog integration
        pushwoosh_hwid:
          type: string
          nullable: true
          description: The ID of the user's device in Pushwoosh integration
        tenjin_analytics_installation_id:
          type: string
          nullable: true
          description: The ID of the user's device in Tenjin integration
    
    # Refund Saver Objects
    RefundSaverSettingsObject:
      type: object
      description: |
        Refund Saver Settings contain user preferences for handling refund requests. 
        This includes consent for data sharing and custom refund preferences.
      properties:
        consent:
          type: boolean
          description: Defines whether the user consented to share their data
        custom_preference:
          type: string
          enum: [grant, no_preference, decline]
          description: The refund preference
      required:
        - consent
        - custom_preference
    
    # Error Objects
    ErrorObject:
      type: object
      description: |
        An Error represents an API error response. It contains information about 
        what went wrong and how to fix it.
      properties:
        errors:
          type: array
          items:
            type: object
            properties:
              source:
                type: string
                nullable: true
                description: The field or parameter that caused the error
              errors:
                type: array
                items:
                  type: string
                description: Array of error messages
            required: [errors]
          description: Array of error details
        error_code:
          type: string
          description: A machine-readable error code
        status_code:
          type: integer
          description: The HTTP status code
      required:
        - errors
        - error_code
        - status_code
    
    # Pagination Objects
    PaginationObject:
      type: object
      description: |
        Pagination metadata for list endpoints. Contains information about 
        the current page and total results.
      properties:
        count:
          type: integer
          description: Total number of items
        page:
          type: integer
          description: Current page number
        pages:
          type: integer
          description: Total number of pages
      required:
        - count
        - page
        - pages
