openapi: 3.0.3
info:
  title: Keeta Promotion API Reference
  version: v1.0.0
  contact:
    name: Keeta Developer Website
    url: https://developers.mykeeta.com/
  description: >
    This API provides comprehensive promotion management capabilities for Keeta
    platform integration, including promotion management, promotion status query
    and notification.
servers:
  - url: https://open.mykeeta.com/api/open/grocery
paths:
  /v1/promotion/chains/{chainId}:
    put:
      operationId: managePromotion
      summary: Manage Promotion
      description: >
        This endpoint allows a third-party system to **create, update, or
        deactivate SKU-level promotions** on the Keeta platform.


        **Notes:**

        - The request is processed **asynchronously**, and the processing result
        will be notified via webhook.

        - Keeta identifies a promotion by the combination of **sku,
        conditions.startTime, and conditions.endTime**.

        - When conditions is omitted, the promotion **becomes effective after
        creation and remains effective** until it is updated or deactivated.

        - Set discount[].active to false to **remove the specified SKUs** from
        the promotion.

        - Only **STRIKETHROUGH** promotions are currently supported.

        - **Only one promotion** can be active for the same SKU at any given
        time.

        - Only **SKU-level daily inventory limits** and **per-order purchase
        limits** are supported.
      x-codeSamples:
        - lang: cURL
          source: |-
            curl -i -X PUT \
              'https://open.mykeeta.com/api/open/grocery/v1/promotion/chains/{chainId}' \
              -H 'Authorization: Bearer <YOUR_TOKE_HERE>' \
              -H 'Content-Type: application/json' \
              -H 'Signature: YOUR_API_KEY_HERE' \
              -d '{
                    "vendors": ["12345", "67890"],
                    "type": "STRIKETHROUGH",
                    "promotionName": "Energy drinks 0.5l on sale",
                    "conditions": {
                      "startTime": "2024-03-05T15:59:59Z",
                      "endTime": "2024-03-05T16:59:59Z"
                    },
                    "discount": [
                      {
                        "discountSubtype": "PERCENTAGE",
                        "discountValue": 10,
                        "sku": [
                          {"skuId": "23927", "originalPrice": 100},
                          {"skuId": "84665"}
                        ],
                        "active": true,
                        "orderLimit": 2,
                        "dailyLimit": 100
                      }
                    ]
                  }'
      security:
        - OAuth2ClientCredentials: []
          Signature: []
      parameters:
        - $ref: '#/components/parameters/ChainId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PromotionRequest'
            example:
              vendors:
                - '12345'
                - '67890'
              type: STRIKETHROUGH
              promotionName: Energy drinks 0.5l on sale
              conditions:
                startTime: '2024-03-05T15:59:59Z'
                endTime: '2024-03-05T16:59:59Z'
              discount:
                - discountSubtype: PERCENTAGE
                  discountValue: 10
                  sku:
                    - skuId: '23927'
                      originalPrice: 100
                    - skuId: '84665'
                  active: true
                  orderLimit: 2
                  dailyLimit: 100
      responses:
        '202':
          description: >-
            The asynchronous promotion job for the specified vendors was created
            successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobSubmissionResponse'
              example:
                jobId: id_123456
                jobStatus: QUEUED
        '400':
          description: The request parameters were invalid, missing, or malformed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                code: BAD_REQUEST
                message: Invalid Parameter format
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/InvalidSignature'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  securitySchemes:
    OAuth2ClientCredentials:
      type: oauth2
      description: OAuth 2.0 client credentials used by partner applications.
      flows:
        clientCredentials:
          tokenUrl: >-
            https://api-docs.mykeeta.com/apis/yhsu3hu21ch23yabx4xuuuq7/basic/endpoints/generateaccesstoken
          scopes: {}
    Signature:
      type: apiKey
      description: >-
        To enhance overall security, Keeta requires a signature in the Header
        when making requests to Keeta. Please refer to [Authorization
        Guide](https://api-docs.mykeeta.com/apis/yhsu3hu21ch23yabx4xuuuq7/docs/auth)
        for details.
      in: header
      name: Signature
    WebhookKeyAuth:
      type: apiKey
      in: header
      name: X-Webhook-Token
      description: Optional static webhook token configured in the Developer Portal.
  parameters:
    ChainId:
      name: chainId
      in: path
      required: true
      description: Unique identifier of the chain.
      schema:
        type: string
        example: '5508400'
    JobId:
      name: jobId
      in: path
      required: true
      description: Job ID from the response of the Manage Promotion endpoint.
      schema:
        type: integer
        format: int64
        example: 123456
  schemas:
    PromotionRequest:
      type: object
      required:
        - vendors
        - type
        - discount
      properties:
        vendors:
          type: array
          minItems: 1
          items:
            type: string
          example:
            - '12345'
            - '67890'
          description: Identifiers of the vendors that participate in the promotion.
        type:
          type: string
          enum:
            - STRIKETHROUGH
          example: STRIKETHROUGH
          description: >-
            This field represents the promotion type. Only STRIKETHROUGH is
            currently supported.
        promotionName:
          type: string
          example: Energy drinks 0.5l on sale
          description: >-
            Unique name used to identify and manage the promotion on the Keeta
            platform.
        conditions:
          $ref: '#/components/schemas/PromotionConditions'
        discount:
          type: array
          minItems: 1
          maxItems: 20000
          items:
            $ref: '#/components/schemas/DiscountItem'
          description: >-
            A list of SKU-level discount configurations. A single request can
            contain up to 20,000 discount items.
    PromotionConditions:
      type: object
      description: >-
        Optional promotion validity period. When this object is omitted, the
        promotion becomes effective after creation and remains effective until
        it is updated or deactivated. The start time and end time need to be
        either both provided or both omitted.
      properties:
        startTime:
          type: string
          format: date-time
          example: '2024-09-20T09:23:06Z'
          description: >-
            Optional promotion start time in UTC using ISO 8601 format. When
            both times are provided, this value must be earlier than endTime.
        endTime:
          type: string
          format: date-time
          example: '2024-09-22T09:23:06Z'
          description: >-
            Optional promotion end time in UTC using ISO 8601 format. When both
            times are provided, this value must be later than startTime.
    DiscountItem:
      type: object
      required:
        - sku
      properties:
        sku:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/PromotionSku'
          example:
            - skuId: '23927'
              originalPrice: 100
            - skuId: '84665'
          description: >-
            List of SKU objects. The specified SKUs are added to the promotion
            when active is true and removed when active is false.
        active:
          type: boolean
          default: true
          example: true
          description: >-
            Indicates whether the specified SKUs participate in the promotion.
            The default value is true.
        discountSubtype:
          type: string
          enum:
            - PERCENTAGE
            - FINAL_PRICE
          example: FINAL_PRICE
          description: >
            This field represents the discount calculation method and is
            **required when active is true**.


            - `PERCENTAGE`: Specifies a percentage reduction.

            - `FINAL_PRICE`: Specifies the final selling price.
        discountValue:
          type: number
          format: double
          exclusiveMinimum: true
          minimum: 0
          example: 10.98
          description: >
            This field represents the discount value and is **required when
            active is true**.


            - `PERCENTAGE`: Provide a value from 1 through 100 with no more than
            two decimal places. For example, 10 represents a 10 percent
            reduction.

            - `FINAL_PRICE`: Provide a positive amount in local currency with no
            more than three decimal places. For weighted products, should be
            sent for the **base_weight**.
        orderLimit:
          type: integer
          format: int32
          nullable: true
          minimum: 0
          maximum: 100000
          default: null
          example: 2
          description: >-
            Maximum number of times the promotion can be applied to each SKU in
            a single order. The accepted configuration range is 0 to 100000.
            Null means there is no limit.
        dailyLimit:
          type: integer
          format: int32
          nullable: true
          minimum: 0
          maximum: 100000
          default: null
          example: 100
          description: >-
            Maximum number of times the promotion can be applied to each SKU in
            one day. The accepted configuration range is 0 to 100000. Null means
            there is no limit.
    PromotionSku:
      type: object
      required:
        - skuId
      properties:
        skuId:
          type: string
          example: '2756'
          description: Unique identifier of the SKU.
        originalPrice:
          type: number
          format: double
          nullable: true
          example: 99.43
          description: >-
            Original product price in local currency. Up to two decimal places
            are supported.
    JobSubmissionResponse:
      type: object
      required:
        - jobId
        - jobStatus
      properties:
        jobId:
          type: string
          example: id_123456
          description: >-
            Job identifier. Can be used as a query parameter to get the job
            status
        jobStatus:
          type: string
          enum:
            - QUEUED
          example: QUEUED
          description: >-
            Job status. Always equal to QUEUED due to the asynchronous nature of
            this endpoint.
    PromotionJobResponse:
      type: object
      required:
        - jobId
        - jobStatus
        - createdAt
        - updatedAt
        - result
      properties:
        jobId:
          type: string
          example: id_123456
          description: >-
            Job identifier. Can be used as a query parameter to get the job
            status
        jobStatus:
          type: string
          enum:
            - QUEUED
            - INPROGRESS
            - COMPLETED
            - FAILED
          example: COMPLETED
          description: This field represents the current processing status of the job.
        createdAt:
          type: string
          format: date-time
          example: '2024-09-30T10:00:36Z'
          description: Time when the job was created, in UTC using ISO 8601 format.
        updatedAt:
          type: string
          format: date-time
          example: '2024-09-30T10:05:36Z'
          description: >-
            Time when the job status was last updated, in UTC using ISO 8601
            format.
        result:
          $ref: '#/components/schemas/PromotionJobResult'
    PromotionJobResult:
      type: object
      description: >-
        Detailed processing result for vendors and SKUs that could not be
        included in the promotion.
      properties:
        invalidVendors:
          type: array
          items:
            type: integer
            format: int64
          description: >-
            List of invalid vendor identifiers, including vendors that do not
            exist or do not belong to the specified chain.
        missingSkus:
          allOf:
            - $ref: '#/components/schemas/VendorSkuMap'
          description: >-
            Map whose key is a vendor identifier and whose value is a list of
            SKU identifiers that were not found in that vendor's catalog.
        invalidDiscountedSkus:
          allOf:
            - $ref: '#/components/schemas/VendorSkuMap'
          description: >-
            Map whose key is a vendor identifier and whose value is a list of
            SKU identifiers for which the discounted price is higher than the
            listed product price.
        invalidPromotionTime:
          allOf:
            - $ref: '#/components/schemas/VendorSkuMap'
          description: >-
            Map whose key is a vendor identifier and whose value is a list of
            SKU identifiers with an invalid promotion time.
        others:
          allOf:
            - $ref: '#/components/schemas/VendorSkuMap'
          description: >-
            Map whose key is a vendor identifier and whose value is a list of
            SKU identifiers that failed for other reasons.
    VendorSkuMap:
      type: object
      additionalProperties:
        type: array
        items:
          type: string
      example:
        '11221':
          - '2355662'
          - '1278976'
      description: >-
        Map whose key is a vendor identifier and whose value is a list of SKU
        identifiers that could not be processed for that vendor.
    PromotionEventRequest:
      type: object
      required:
        - jobId
        - jobStatus
      properties:
        jobId:
          type: string
          example: id_123456
          description: Job identifier from the response of the Manage Promotion API.
        jobStatus:
          type: string
          enum:
            - QUEUED
            - INPROGRESS
            - COMPLETED
            - FAILED
          description: Job status of the asynchronous promotion task.
          example: COMPLETED
        downloadUrl:
          type: string
          format: uri
          example: https://example.com/promotion-results.csv
          description: >-
            URL of an Excel file containing the promotion execution result for
            each SKU. **Only when the `jobStatus=COMPLETED` then the downloadUrl
            is available.**
        reason:
          type: string
          example: The job failed to execute.
          description: >-
            The specific reason why the asynchronous promotion job failed. This
            field is available only when `jobStatus=FAILED`.
    ErrorResponse:
      type: object
      properties:
        code:
          type: string
          example: BAD_REQUEST
          description: Machine-readable error title.
        message:
          type: string
          example: Invalid Parameter format
          description: Human-readable explanation of the error.
    CallbackErrorResponse:
      type: object
      properties:
        code:
          type: string
          example: Unexpected error
          description: Human-readable error title.
        message:
          type: integer
          format: int32
          example: 500
          description: Numeric error status supplied by the callback service.
  responses:
    Unauthorized:
      description: The request was not authenticated or the credentials were invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            missingAuthorizationHeader:
              summary: Authorization header is missing
              value:
                code: invalid_request
                message: Authorization header missing
            invalidToken:
              summary: Access token or signature is invalid
              value:
                code: invalid_token
                message: Invalid access token or signature
            expiredToken:
              summary: Access token has expired
              value:
                code: invalid_token
                message: The access token expired
    InvalidSignature:
      description: The application signature was invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            code: invalid_signature
            message: The signature is invalid
    TooManyRequests:
      description: Too many requests. The rate limit was exceeded.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            code: TOO_MANY_REQUESTS
            message: Too many requests. Please try later
    InternalServerError:
      description: An unexpected error occurred on the server.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            code: INTERNAL_ERROR
            message: Internal Server Error
    CallbackBadRequest:
      description: The callback request was invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/CallbackErrorResponse'
          example:
            code: Unexpected error
            message: 500
    CallbackForbidden:
      description: The authenticated request violated an authorization rule.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/CallbackErrorResponse'
          example:
            code: Unexpected error
            message: 500
    CallbackNotFound:
      description: The requested callback resource was not found.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/CallbackErrorResponse'
          example:
            code: Unexpected error
            message: 500
    CallbackUnavailable:
      description: >-
        The callback service is unavailable, under maintenance, or outside its
        operating window.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/CallbackErrorResponse'
          example:
            code: Unexpected error
            message: 500
