openapi: 3.1.0
info:
  title: Avirqo Public API
  version: 1.0.0
  description: Purchase and retrieve vouchers using customer-scoped API credentials.
servers:
  - url: https://api.avirqo.com/v1
security:
  - bearerAuth: []
paths:
  /oauth/token:
    post:
      security: []
      summary: Exchange a client ID and secret for a short-lived access token
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [grant_type, client_id, client_secret, scope]
              properties:
                grant_type: { type: string, enum: [client_credentials] }
                client_id: { type: string, example: 019fe2d0-8629-73f6-8074-193774fafeca }
                client_secret: { type: string, format: password }
                scope: { type: string, example: 'products.list orders.create' }
      responses:
        '200':
          description: Access token issued
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TokenResponse' }
        '401': { $ref: '#/components/responses/Error' }
  /products:
    get:
      summary: List products available to the authenticated customer
      parameters:
        - { name: search, in: query, schema: { type: string } }
        - { name: country_code, in: query, schema: { type: string } }
        - { name: page, in: query, schema: { type: integer, minimum: 1 } }
      responses:
        '200':
          description: Paginated product catalog
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ProductPage' }
        '403': { $ref: '#/components/responses/Error' }
  /balances:
    get:
      summary: Get wallet balances by currency and bucket
      responses:
        '200':
          description: Balance summary
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BalanceResponse' }
  /balance-history:
    get:
      summary: Get cursor-paginated wallet transactions
      parameters:
        - { name: currency_code, in: query, schema: { type: string } }
        - { name: type, in: query, schema: { type: string, enum: [credit, debit] } }
        - { name: cursor, in: query, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100 } }
      responses:
        '200':
          description: Balance transactions
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BalanceHistoryResponse' }
  /orders:
    post:
      summary: Place an order for exactly one voucher
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [items]
              properties:
                items:
                  type: array
                  minItems: 1
                  maxItems: 1
                  items: { $ref: '#/components/schemas/OrderItem' }
      responses:
        '201':
          description: Order completed
          content:
            application/json:
              schema: { $ref: '#/components/schemas/OrderResponse' }
        '409': { $ref: '#/components/responses/Error' }
        '422': { $ref: '#/components/responses/Error' }
  /bulk-orders:
    post:
      summary: Place a multi-product voucher order
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [items]
              properties:
                items:
                  type: array
                  minItems: 1
                  items: { $ref: '#/components/schemas/OrderItem' }
      responses:
        '201':
          description: Bulk order completed atomically
          content:
            application/json:
              schema: { $ref: '#/components/schemas/OrderResponse' }
        '409': { $ref: '#/components/responses/Error' }
        '422': { $ref: '#/components/responses/Error' }
  /orders/{order_number}/vouchers:
    get:
      summary: Retrieve vouchers for a current or historical customer order
      parameters:
        - { name: order_number, in: path, required: true, schema: { type: string } }
      responses:
        '200':
          description: Voucher codes; treat the response as sensitive
          content:
            application/json:
              schema: { $ref: '#/components/schemas/VoucherResponse' }
        '404': { $ref: '#/components/responses/Error' }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
    clientSecretBasic:
      type: http
      scheme: basic
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema: { type: string, maxLength: 100 }
  schemas:
    OrderItem:
      type: object
      required: [product_id, denomination]
      properties:
        product_id: { type: string, pattern: '^AVQ-PROD-[A-Z0-9-]+-[0-9]{5}$', example: AVQ-PROD-AMZ-IN-00123 }
        currency_code: { type: string, example: INR }
        denomination: { type: number, minimum: 0.01, example: 1000 }
        quantity: { type: integer, minimum: 1, default: 1 }
    TokenResponse:
      type: object
      required: [access_token, token_type, expires_in]
      properties:
        access_token: { type: string }
        token_type: { type: string, enum: [Bearer] }
        expires_in: { type: integer, example: 900 }
        scope: { type: string, example: 'products.list orders.create' }
    ProductPage:
      type: object
      required: [data, current_page, per_page, total]
      properties:
        data:
          type: array
          items: { $ref: '#/components/schemas/Product' }
        current_page: { type: integer }
        per_page: { type: integer }
        total: { type: integer }
        last_page: { type: integer }
        from: { type: integer, nullable: true }
        to: { type: integer, nullable: true }
        first_page_url: { type: string, nullable: true }
        last_page_url: { type: string, nullable: true }
        next_page_url: { type: string, nullable: true }
        prev_page_url: { type: string, nullable: true }
    Product:
      type: object
      required: [avirqo_id, product_id, name, product_images, brand_data, categories,
        show_discount_percent, allowed_currency_codes, countries, value_type,
        allowed_denominations, fee, discount, exchange_rate]
      properties:
        avirqo_id: { type: string }
        product_id: { type: string, example: AVQ-PROD-AMZ-IN-00123 }
        name: { type: string }
        brand: { type: string, nullable: true }
        description: { type: string, nullable: true }
        image_url: { type: string, nullable: true }
        product_images: { type: array, items: { type: string } }
        brand_data: { type: object, additionalProperties: true }
        categories: { type: array, items: { type: string } }
        show_discount_percent: { type: boolean }
        allowed_currency_codes: { type: array, items: { type: string } }
        country_code: { type: string, nullable: true }
        country_name: { type: string, nullable: true }
        countries: { type: array, items: { type: string } }
        value_type: { type: string, enum: [fixed, open] }
        min_value: { type: string, nullable: true }
        max_value: { type: string, nullable: true }
        allowed_denominations: { type: array, items: { type: number } }
        fee: { type: string }
        discount: { type: string }
        exchange_rate: { type: string }
        terms_and_conditions: { type: string, nullable: true }
        redemption_instructions: { type: string, nullable: true }
        expiry_and_validity: { type: string, nullable: true }
        usage_type: { type: string, nullable: true }
        delivery_type: { type: string, nullable: true }
        tat_in_days: { type: integer, nullable: true }
    BalanceResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items: { $ref: '#/components/schemas/Balance' }
    Balance:
      type: object
      required: [currency_code, voucher_balance, service_charge_balance, available_balance]
      properties:
        currency_code: { type: string, example: INR }
        voucher_balance: { type: string, example: '10000.00' }
        service_charge_balance: { type: string, example: '500.00' }
        available_balance: { type: string, example: '10500.00' }
    BalanceHistoryResponse:
      type: object
      required: [data, next_cursor]
      properties:
        data:
          type: array
          items: { $ref: '#/components/schemas/BalanceTransaction' }
        next_cursor: { type: string, nullable: true }
    BalanceTransaction:
      type: object
      required: [id, currency_code, bucket, type, amount, balance_after, description, reference, created_at]
      properties:
        id: { type: string }
        currency_code: { type: string, example: INR }
        bucket: { type: string, example: voucher_balance }
        type: { type: string, enum: [credit, debit] }
        amount: { type: string, example: '1000.00' }
        balance_after: { type: string, example: '9000.00' }
        description: { type: string, nullable: true }
        reference:
          type: object
          properties:
            type: { type: string, nullable: true }
            id: { type: string, nullable: true }
        created_at: { type: string, format: date-time, nullable: true }
    OrderResponse:
      type: object
      required: [data, request_id]
      properties:
        data: { $ref: '#/components/schemas/Order' }
        request_id: { type: string }
    Order:
      type: object
      required: [id, order_number, status, total_amount, items]
      properties:
        id: { type: string }
        order_number: { type: string }
        status: { type: string }
        total_amount: { type: string, example: '1000.00' }
        created_at: { type: string, format: date-time, nullable: true }
        items:
          type: array
          items: { $ref: '#/components/schemas/OrderResultItem' }
    OrderResultItem:
      type: object
      required: [product_id, currency_code, denomination, quantity, total]
      properties:
        product_id: { type: string, example: AVQ-PROD-AMZ-IN-00123 }
        product_name: { type: string, nullable: true }
        currency_code: { type: string, example: INR }
        denomination: { type: string, example: '1000.00' }
        quantity: { type: integer }
        total: { type: string, example: '1000.00' }
    VoucherResponse:
      type: object
      required: [order, data]
      properties:
        order: { $ref: '#/components/schemas/Order' }
        data:
          type: array
          items: { $ref: '#/components/schemas/Voucher' }
    Voucher:
      type: object
      required: [product_id, currency_code, denomination, code, pin]
      properties:
        product_id: { type: string, example: AVQ-PROD-AMZ-IN-00123 }
        product_name: { type: string, nullable: true }
        currency_code: { type: string, example: INR }
        denomination: { type: string, example: '1000.00' }
        code: { type: string }
        pin: { type: string, nullable: true }
        expiry_date: { type: string, format: date, nullable: true }
  responses:
    Error:
      description: Error response
      content:
        application/json:
          schema:
            oneOf:
              - type: object
                required: [error]
                properties:
                  error:
                    type: object
                    required: [code, message]
                    properties:
                      code: { type: string }
                      message: { type: string }
                      retry_after: { type: integer, minimum: 0 }
                      refresh_products: { type: boolean }
                      changes:
                        type: array
                        description: Current product, currency, or denomination rules that conflict with the submitted order.
                        items:
                          type: object
                          required: [product_id, type, message]
                          properties:
                            product_id: { type: string, example: AVQ-PROD-AMZ-IN-00123 }
                            type: { type: string, enum: [product, currency, denomination] }
                            message: { type: string }
                      currentStatus:
                        type: object
                        properties:
                          customer_status: { type: string, nullable: true }
                          kyb_status: { type: string, nullable: true }
              - type: object
                required: [error]
                properties:
                  error: { type: string }
                  error_description: { type: string }
