openapi: 3.1.0
info:
  title: ThaiForTravel Mini App API
  version: 0.2.0
  description: API каталога, заявок, избранного, планов отдыха и напоминаний.
servers:
  - url: https://api.thaifortravel.ru
security:
  - TelegramMiniApp: []
tags:
  - name: Catalog
  - name: Applications
  - name: Plans
  - name: Reminders
paths:
  /health/live:
    get:
      security: []
      responses:
        "200":
          description: Process is alive
  /health/ready:
    get:
      security: []
      responses:
        "200":
          description: Database is ready
        "503":
          $ref: "#/components/responses/Error"
  /v1/catalog/tours:
    get:
      security: []
      tags: [Catalog]
      parameters:
        - $ref: "#/components/parameters/City"
        - name: categories
          in: query
          schema:
            type: string
          description: Comma-separated category ids
        - name: search
          in: query
          schema:
            type: string
            maxLength: 120
        - name: sort
          in: query
          schema:
            enum: [popular, price_asc, price_desc, newest]
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
        - name: view
          in: query
          schema:
            enum: [full, card]
            default: full
          description: Use `card` for a compact catalog response; request the full tour by id when it is opened.
      responses:
        "200":
          description: Normalized catalog page
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TourList"
        "502":
          $ref: "#/components/responses/Error"
  /v1/catalog/tours/{id}:
    get:
      security: []
      tags: [Catalog]
      parameters:
        - $ref: "#/components/parameters/Id"
      responses:
        "200":
          description: Tour
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Tour"
        "404":
          $ref: "#/components/responses/Error"
  /v1/catalog/transfers:
    get:
      security: []
      tags: [Catalog]
      parameters:
        - name: city
          in: query
          schema:
            enum: [pattaya, phuket, thailand]
        - name: search
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Transfer offers
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Transfer"
  /v1/catalog/cities:
    get:
      security: []
      tags: [Catalog]
      responses:
        "200":
          description: Supported directions
  /v1/catalog/filters:
    get:
      security: []
      tags: [Catalog]
      parameters:
        - $ref: "#/components/parameters/City"
      responses:
        "200":
          description: Catalog filters with counts
  /v1/me:
    get:
      responses:
        "200":
          description: Authenticated Telegram user
        "401":
          $ref: "#/components/responses/Error"
  /v1/favorites:
    get:
      responses:
        "200":
          description: User favorites
    post:
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [serviceKind, serviceId]
              properties:
                serviceKind:
                  enum: [tour, transfer]
                serviceId:
                  type: string
                  description: ID экскурсии или трансфера из каталога.
      responses:
        "201":
          description: Favorite saved
  /v1/favorites/{serviceKind}/{serviceId}:
    delete:
      parameters:
        - name: serviceKind
          in: path
          required: true
          schema:
            enum: [tour, transfer]
        - name: serviceId
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Favorite removed
  /v1/applications:
    get:
      tags: [Applications]
      responses:
        "200":
          description: Current user's applications
    post:
      tags: [Applications]
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateApplication"
      responses:
        "201":
          description: Application and delivery outbox created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Application"
        "400":
          $ref: "#/components/responses/Error"
  /v1/applications/{id}:
    get:
      tags: [Applications]
      parameters:
        - $ref: "#/components/parameters/Id"
      responses:
        "200":
          description: Application with event history
        "404":
          $ref: "#/components/responses/Error"
  /v1/applications/{id}/send-to-chat:
    post:
      tags: [Applications]
      parameters:
        - $ref: "#/components/parameters/Id"
      responses:
        "200":
          description: Customer confirmation was already delivered to the bot chat
        "202":
          description: Customer confirmation queued for delivery to the bot chat
        "404":
          $ref: "#/components/responses/Error"
  /v1/plans:
    get:
      tags: [Plans]
      responses:
        "200":
          description: Current user's plans
    post:
      tags: [Plans]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePlan"
      responses:
        "201":
          description: Draft plan
  /v1/plans/{id}:
    get:
      tags: [Plans]
      parameters:
        - $ref: "#/components/parameters/Id"
      responses:
        "200":
          description: Plan with items and occupied days
    patch:
      tags: [Plans]
      parameters:
        - $ref: "#/components/parameters/Id"
      responses:
        "200":
          description: Updated plan
  /v1/plans/{id}/items:
    post:
      tags: [Plans]
      parameters:
        - $ref: "#/components/parameters/Id"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [serviceId, date]
              properties:
                serviceId:
                  type: string
                serviceVariantId:
                  type: string
                  description: Обязателен, если у экскурсии опубликовано несколько программ.
                date:
                  type: string
                  format: date
                startTime:
                  type: string
                  pattern: '^\d{2}:\d{2}$'
                note:
                  type: string
                  maxLength: 1000
      responses:
        "201":
          description: Service added; multi-day tours generate every occupied day.
  /v1/plans/{id}/recommendations:
    get:
      tags: [Plans]
      parameters:
        - $ref: "#/components/parameters/Id"
        - name: date
          in: query
          required: true
          schema:
            type: string
            format: date
      responses:
        "200":
          description: Ranked tours; first and last day also include one directional transfer recommendation.
  /v1/plans/{id}/submit:
    post:
      tags: [Plans]
      parameters:
        - $ref: "#/components/parameters/Id"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "201":
          description: Plan submitted as application
  /v1/reminders:
    get:
      tags: [Reminders]
      responses:
        "200":
          description: User reminders
    post:
      tags: [Reminders]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [title, message, triggerAt]
              properties:
                planId:
                  type: string
                title:
                  type: string
                message:
                  type: string
                triggerAt:
                  type: string
                  format: date-time
      responses:
        "201":
          description: Reminder scheduled
  /v1/chat/open:
    post:
      responses:
        "200":
          description: Deep link to the main Telegram bot
components:
  securitySchemes:
    TelegramMiniApp:
      type: apiKey
      in: header
      name: Authorization
      description: 'Raw Telegram.WebApp.initData with prefix "tma "'
  parameters:
    City:
      name: city
      in: query
      schema:
        enum: [pattaya, phuket]
    Page:
      name: page
      in: query
      schema:
        type: integer
        minimum: 1
        default: 1
    PerPage:
      name: per_page
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    Id:
      name: id
      in: path
      required: true
      schema:
        type: string
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        type: string
        minLength: 8
        maxLength: 120
  responses:
    Error:
      description: API error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
            message:
              type: string
            details: {}
    Money:
      type: object
      required: [amount, currency]
      properties:
        amount:
          type: [number, "null"]
        currency:
          const: THB
        raw:
          type: string
    Tour:
      type: object
      required:
        - id
        - wpId
        - kind
        - city
        - title
        - categories
        - prices
        - durationDays
        - sourceUrl
      properties:
        id:
          type: string
          example: pattaya:11175
        wpId:
          type: integer
        kind:
          enum: [tour, yacht, zoo]
        city:
          enum: [pattaya, phuket]
        title:
          type: string
        summary:
          type: string
        descriptionHtml:
          type: string
        categories:
          type: array
          items:
            type: string
        badges:
          type: array
          items:
            type: string
        featured:
          type: boolean
        durationDays:
          type: integer
          minimum: 1
        prices:
          type: array
          items:
            type: object
            properties:
              variant:
                type: string
              adult:
                $ref: "#/components/schemas/Money"
              child:
                $ref: "#/components/schemas/Money"
        variants:
          type: array
          items:
            $ref: "#/components/schemas/TourVariant"
        sourceUrl:
          type: string
          format: uri
    TourVariant:
      type: object
      required: [id, name, adult, child, durationDays, durationLabel, program, conditions]
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
        adult:
          $ref: "#/components/schemas/Money"
        child:
          $ref: "#/components/schemas/Money"
        infant:
          $ref: "#/components/schemas/Money"
        durationDays:
          type: integer
          minimum: 1
        durationLabel:
          type: string
        program:
          type: array
          items:
            type: object
        included:
          type: array
          items:
            type: string
        conditions:
          type: array
          items:
            type: string
        whatToTake:
          type: array
          items:
            type: string
    TourList:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Tour"
        meta:
          type: object
          required: [page, perPage, total, totalPages]
          properties:
            page:
              type: integer
            perPage:
              type: integer
            total:
              type: integer
            totalPages:
              type: integer
    CreateApplication:
      type: object
      required: [kind, adults, children, consent]
      properties:
        kind:
          enum: [TOUR, TRANSFER, QUICK_PICK]
        serviceKind:
          enum: [tour, transfer]
        serviceId:
          type: string
        serviceVariantId:
          type: string
          description: Обязателен, если у экскурсии опубликовано несколько программ.
        requestedDate:
          type: string
          format: date
        adults:
          type: integer
          minimum: 1
        children:
          type: integer
          minimum: 0
        childAges:
          type: array
          description: Возраст каждого ребёнка; количество элементов должно совпадать с children.
          items:
            type: integer
            minimum: 0
            maximum: 17
        customerName:
          type: string
        phone:
          type: string
        whatsapp:
          type: string
        hotelOrArea:
          type: string
        comment:
          type: string
        consent:
          const: true
        consentVersion:
          type: string
    Application:
      type: object
      properties:
        publicId:
          type: string
        kind:
          enum: [TOUR, TRANSFER, QUICK_PICK, TRAVEL_PLAN]
        status:
          enum:
            - NEW
            - MANAGER_REVIEW
            - WAITING_CUSTOMER
            - CONFIRMED
            - COMPLETED
            - CANCELLED
            - REJECTED
        serviceVariantId:
          type: [string, "null"]
        serviceVariantTitle:
          type: [string, "null"]
        childAges:
          type: array
          items:
            type: integer
        customerDelivery:
          type: object
          properties:
            status:
              enum: [MISSING, PENDING, PROCESSING, SENT, FAILED, DEAD_LETTER]
            sent:
              type: boolean
            sentAt:
              type: [string, "null"]
              format: date-time
            canRetry:
              type: boolean
    Transfer:
      type: object
      required: [id, city, title, price, vehicles]
      properties:
        id:
          type: string
        city:
          enum: [pattaya, phuket, thailand]
        title:
          type: string
        description:
          type: string
        from:
          type: string
        to:
          type: string
        price:
          $ref: "#/components/schemas/Money"
        vehicles:
          type: array
          items:
            $ref: "#/components/schemas/TransferVehicle"
        sourceUrl:
          type: string
          format: uri
    TransferVehicle:
      type: object
      required: [id, name, price]
      properties:
        id:
          type: string
        name:
          type: string
        maxPassengers:
          type: [integer, "null"]
        price:
          $ref: "#/components/schemas/Money"
    CreatePlan:
      type: object
      required: [city, arrivalDate, departureDate, adults, children, categories]
      properties:
        city:
          enum: [pattaya, phuket]
        arrivalDate:
          type: string
          format: date
        departureDate:
          type: string
          format: date
        adults:
          type: integer
          minimum: 1
        children:
          type: integer
          minimum: 0
        categories:
          type: array
          uniqueItems: true
          items:
            type: string
        comment:
          type: string
