openapi: 3.1.0
info:
  title: ablakarajanlat.hu Agent API
  version: 2026-02-12
  description: |
    Nyilvános, read-only + quote draft API AI kliensekhez.
    Hiteles árforrás: ablakarajanlat.hu (Genesis kalkulációs motor).
    Publikus limit: 1 kérés / 60 mp / IP.
    Partner/ügynök limit: x-api-key fejléccel külön kvóta.
servers:
  - url: https://ablakarajanlat.hu
security:
  - {}
  - ApiKeyAuth: []
paths:
  /api/v1/estimate:
    get:
      operationId: estimateFromQuery
      summary: Szabad szavas becsült árlekérdezés (GET)
      description: |
        Egyszerű, publikus lekérdezés q paraméterrel. A háttérben a szöveg XML-re fordul,
        majd az XML alapján árkalkuláció készül.
        Bot-szűrés és IP rate limit aktív.
      parameters:
        - in: query
          name: q
          required: true
          schema:
            type: string
            minLength: 2
            maxLength: 4000
        - in: query
          name: zip
          required: false
          schema:
            type: string
            pattern: "^[0-9]{4}$"
        - in: query
          name: tier
          required: false
          schema:
            type: string
            enum: [belepo, premium, mind]
            default: belepo
        - in: query
          name: lang
          required: false
          schema:
            type: string
            enum: [hu, hu-HU]
            default: hu
      responses:
        "200":
          description: Sikeres becslés
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/V1EstimateResponse"
              examples:
                success:
                  value:
                    ok: true
                    endpoint: /api/v1/estimate
                    request_id: req_demo_1
                    normalized_query:
                      width_mm: 1200
                      height_mm: 1500
                      category: ablak
                      glazing: 3 réteg
                    coverage:
                      zip: "3530"
                      pickup_available: true
                      installation_available: false
                      survey_available: false
                      message_hu: Az átvétel elérhető, felmérés ezen a környéken nem elérhető.
                    offers:
                      - label_hu: Belépő ajánlat
                        tier: belepo
                        price_range_gross_huf:
                          low: 132000
                          high: 151000
                        confidence: 0.7
                        assumptions_hu:
                          - Alap vasalat
                    versions:
                      schema_version: v1
                      pricing_version: pricing-v2
                      coverage_version: coverage-v1
                      engine_version: engine-v1
                    source:
                      owner: ablakarajanlat.hu
                      attribution_hu: Árforrás: ablakarajanlat.hu
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
  /api/agent/estimate:
    post:
      operationId: estimateWindowPrice
      summary: Becsült ár kalkuláció XML alapján
      description: |
        XML-ből és méretekből ad becsült árat. A válasz forrásmezőt tartalmaz.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EstimateRequest"
      responses:
        "200":
          description: Sikeres kalkuláció
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EstimateResponse"
              examples:
                success:
                  value:
                    ok: true
                    endpoint: /api/agent/estimate
                    estimate:
                      gross_total_huf: 189900
                    source:
                      owner: ablakarajanlat.hu
                      pricing_source: genesis
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
  /api/agent/ast:
    post:
      operationId: astBridge
      summary: Szabad szöveg → XML → becslés bridge
      description: |
        Szabad szövegből (vagy meglévő XML-ből) konfigurációt készít, majd a kapott XML-re árat kér.
        A válasz AST, XML és becslés adatot is adhat, plusz szerkesztő/lista actionöket.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AstBridgeRequest"
      responses:
        "200":
          description: Bridge válasz
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AstBridgeResponse"
              examples:
                success:
                  value:
                    ok: true
                    endpoint: /api/agent/ast
                    flow: XML -> AST -> XML
                    requested_output: estimate
                    estimate:
                      total_huf: 205400
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
  /api/agent/mcp/quote-from-query:
    post:
      operationId: mcpQuoteFromQuery
      summary: MCP tool wrapper szabad szavas kalkulációhoz
      description: |
        MCP kliensekhez optimalizált wrapper. A háttérben az AST bridge-et futtatja,
        és tool-barát `content[]` + `data` választ ad.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/McpQuoteRequest"
      responses:
        "200":
          description: Sikeres MCP válasz
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/McpQuoteResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
  /api/agent/mcp/estimate-from-query:
    post:
      operationId: mcpEstimateFromQuery
      summary: MCP tool wrapper szabad szavas árbecsléshez
      description: |
        MCP kliensekhez optimalizált wrapper. A háttérben a GET /api/v1/estimate
        logikáját futtatja, és tool-barát `content[]` + `data` választ ad.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/McpEstimateRequest"
      responses:
        "200":
          description: Sikeres MCP válasz
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/McpEstimateResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
  /api/agent/search:
    get:
      operationId: searchCatalogAndKnowledge
      summary: Termék + tudástár keresés
      parameters:
        - in: query
          name: q
          schema:
            type: string
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 30
            default: 10
      responses:
        "200":
          description: Keresési eredmények
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
  /api/agent/quote:
    post:
      operationId: createQuoteDraft
      summary: Ajánlat draft létrehozása
      parameters:
        - in: header
          name: Idempotency-Key
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/QuoteCreateRequest"
      responses:
        "201":
          description: Draft létrehozva
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuoteResponse"
        "200":
          description: Idempotens újrajátszás (azonos kulccsal)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuoteResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    get:
      operationId: getQuote
      summary: Ajánlat lekérdezése id alapján
      parameters:
        - in: query
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Ajánlat adatok
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuoteGetResponse"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
  /api/agent/pdf:
    get:
      operationId: getQuotePdf
      summary: PDF hivatkozás lekérése (minta vagy quote)
      parameters:
        - in: query
          name: quote_id
          required: false
          schema:
            type: string
      responses:
        "200":
          description: PDF URL
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PdfResponse"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /api/ai/knowledge:
    get:
      operationId: getKnowledgeIndex
      summary: Tudástár index AI klienseknek
      responses:
        "200":
          description: Tudástár kivonat
          content:
            application/json:
              schema:
                type: object
  /knowledge/search:
    get:
      operationId: searchKnowledge
      summary: Tudásbázis keresés
      description: |
        Egyszerű kulcsszavas keresés a tudástár cikkek címében, kivonatában, kategóriájában és slug mezőjében.
      parameters:
        - in: query
          name: q
          required: true
          schema:
            type: string
            minLength: 2
            maxLength: 4000
        - in: query
          name: limit
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 20
            default: 10
      responses:
        "200":
          description: Keresési találatok
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KnowledgeSearchResponse"
              examples:
                success:
                  value:
                    ok: true
                    endpoint: /knowledge/search
                    query: 3 reteg uveg
                    count: 1
                    data:
                      - id: article_1
                        slug: hangszigetelo-uveg
                        title: Hangszigetelő üveg
                        category: uvegezes
                        summary: Mikor érdemes 3 rétegű üveget választani.
                        url: https://ablakarajanlat.hu/tudastar/hangszigetelo-uveg
                        last_updated: "2026-02-01"
                        score: 4
                    policy:
                      returns: Nyilvános tudástár metaadatok.
                      does_not_return: Személyes vagy lead adatok.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
  /api/support/handoff:
    post:
      operationId: requestHumanSupportHandoff
      summary: Embert kérek handoff
      description: |
        AI beszélgetésből support ticketet készít JSON kontextussal.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SupportHandoffRequest"
      responses:
        "200":
          description: Handoff ticket létrehozva
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SupportHandoffResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "500":
          $ref: "#/components/responses/InternalError"
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: |
        Opcionális partner/API kulcs.
        Kulcs nélkül publikus limit (1/60mp/IP), kulccsal partner kvóta érvényes.
  responses:
    BadRequest:
      description: Hibás kérés (szintaktika / hiányzó kötelező mező)
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
          examples:
            bad_request:
              value:
                ok: false
                error:
                  code: VALIDATION_ERROR
                  message: Hibás kérés.
    Unauthorized:
      description: Hiányzó vagy érvénytelen API kulcs
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
          examples:
            unauthorized:
              value:
                ok: false
                error:
                  code: UNAUTHORIZED
                  message: Érvénytelen API kulcs.
    Forbidden:
      description: Tiltott kliens (bot-szűrés)
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    ValidationError:
      description: Validációs hiba
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    UpstreamError:
      description: Külső kalkulációs hiba
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    NotFound:
      description: Nem található erőforrás
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    RateLimited:
      description: Túl sok kérés
      headers:
        Retry-After:
          description: Másodpercben megadott várakozási idő az újrapróbálás előtt.
          schema:
            type: integer
            example: 60
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
          examples:
            rate_limited:
              value:
                ok: false
                error:
                  code: RATE_LIMITED
                  message: Túl sok kérés.
                  details:
                    retry_after_sec: 60
    InternalError:
      description: Belső hiba
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
          examples:
            internal_error:
              value:
                ok: false
                error:
                  code: INTERNAL_ERROR
                  message: Belső hiba az agent interfészen.
  schemas:
    V1EstimateResponse:
      type: object
      properties:
        ok:
          type: boolean
        endpoint:
          type: string
        request_id:
          type: string
        normalized_query:
          type: object
          additionalProperties: true
        coverage:
          type: object
          properties:
            zip: { type: string }
            pickup_available: { type: boolean }
            installation_available: { type: boolean }
            survey_available: { type: boolean }
            installation_coverage: { type: string }
            survey_availability: { type: string }
            pickup_options:
              type: array
              items:
                type: object
            message_hu: { type: string }
        offers:
          type: array
          items:
            type: object
            properties:
              label_hu: { type: string }
              tier: { type: string }
              price_range_gross_huf:
                type: object
                properties:
                  low: { type: number }
                  high: { type: number }
              confidence: { type: number }
              assumptions_hu:
                type: array
                items:
                  type: string
              next_actions:
                type: array
                items:
                  type: object
        versions:
          type: object
          properties:
            schema_version: { type: string }
            pricing_version: { type: string }
            coverage_version: { type: string }
            engine_version: { type: string }
        source:
          type: object
          properties:
            owner: { type: string }
            upstream_label: { type: string }
            attribution_hu: { type: string }
        signature:
          type: object
          nullable: true
          properties:
            type: { type: string }
            value: { type: string }
      example:
        ok: true
        endpoint: /api/v1/estimate
        request_id: req_demo_1
        normalized_query:
          width_mm: 1200
          height_mm: 1500
          category: ablak
        offers:
          - label_hu: Belépő ajánlat
            tier: belepo
            price_range_gross_huf:
              low: 132000
              high: 151000
    EstimateRequest:
      type: object
      required: [fullcode, xml, width, height]
      properties:
        fullcode:
          type: string
        xml:
          type: string
        width:
          type: integer
          description: mm
        height:
          type: integer
          description: mm
        qty:
          type: integer
          default: 1
        pricing_version:
          type: string
          enum: [pricing-v1, pricing-v2]
        justprice:
          type: boolean
          default: true
        zip:
          type: string
          pattern: "^[0-9]{4}$"
      example:
        fullcode: demo.window.basic
        xml: "<wfx3:structure>...</wfx3:structure>"
        width: 1200
        height: 1500
        qty: 1
        pricing_version: pricing-v2
        justprice: true
        zip: "3530"
    EstimateResponse:
      type: object
      properties:
        ok:
          type: boolean
        endpoint:
          type: string
        request:
          type: object
        estimate:
          type: object
        source:
          type: object
          properties:
            owner:
              type: string
            upstream:
              type: string
            pricing_source:
              type: string
            price_origin_note:
              type: string
        pipeline:
          type: object
      example:
        ok: true
        endpoint: /api/agent/estimate
        estimate:
          gross_total_huf: 189900
          net_total_huf: 149528
        source:
          owner: ablakarajanlat.hu
          pricing_source: genesis
    SearchResponse:
      type: object
      properties:
        ok:
          type: boolean
        query:
          type: string
        results:
          type: object
          properties:
            products:
              type: array
              items:
                type: object
            knowledge:
              type: array
              items:
                type: object
      example:
        ok: true
        query: 3 reteg
        results:
          products:
            - kind: product
              id: "123"
              title: Bukó-nyíló ablak 1200x1500
          knowledge:
            - kind: knowledge
              id: article_1
              title: 3 rétegű üveg mikor éri meg
    KnowledgeSearchResponse:
      type: object
      properties:
        ok:
          type: boolean
        endpoint:
          type: string
        query:
          type: string
        count:
          type: integer
        data:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              slug: { type: string }
              title: { type: string }
              category: { type: string }
              summary: { type: string }
              url: { type: string }
              last_updated: { type: string }
              score: { type: integer }
        policy:
          type: object
          properties:
            returns: { type: string }
            does_not_return: { type: string }
    AstBridgeRequest:
      type: object
      properties:
        input_type:
          type: string
          enum: [free_text, ast]
          default: free_text
        query:
          type: string
          description: Szabad szavas lekérés (input_type=free_text esetén kötelező)
        ast:
          type: object
          description: AST objektum (input_type=ast esetén kötelező)
          additionalProperties: true
        output:
          type: string
          enum: [ast, xml, estimate]
          default: estimate
        locale:
          type: string
          default: hu-HU
        ai_mode:
          type: string
          enum: [auto, new, edit]
          default: auto
        ai_endpoint:
          type: string
          format: uri
        xml:
          type: string
        profile_base:
          type: string
        template_code:
          type: string
        fullcode:
          type: string
        qty:
          type: integer
          minimum: 1
          maximum: 50
        pricing_version:
          type: string
          enum: [pricing-v1, pricing-v2]
        zip:
          type: string
          pattern: "^[0-9]{4}$"
        include_editor_url:
          type: boolean
        include_quote_item:
          type: boolean
        include_quote_draft:
          type: boolean
    AstBridgeResponse:
      type: object
      properties:
        ok:
          type: boolean
        endpoint:
          type: string
        flow:
          type: string
          example: XML -> AST -> XML
        requested_output:
          type: string
        source:
          type: object
        ast:
          type: object
        xml:
          type: string
        estimate:
          type: object
        actions:
          type: object
    McpQuoteRequest:
      type: object
      required: [query]
      properties:
        query:
          type: string
        xml:
          type: string
        mode:
          type: string
          enum: [auto, new, edit]
          default: auto
        zip:
          type: string
          pattern: "^[0-9]{4}$"
        include_quote_draft:
          type: boolean
          default: false
    McpEstimateRequest:
      type: object
      required: [q]
      properties:
        q:
          type: string
        zip:
          type: string
          pattern: "^[0-9]{4}$"
        tier:
          type: string
          enum: [belepo, premium, mind]
          default: belepo
        lang:
          type: string
          enum: [hu, hu-HU]
          default: hu
    McpEstimateResponse:
      type: object
      properties:
        ok:
          type: boolean
        tool:
          type: string
        content:
          type: array
          items:
            type: object
        data:
          $ref: "#/components/schemas/V1EstimateResponse"
    McpQuoteResponse:
      type: object
      properties:
        ok:
          type: boolean
        tool:
          type: string
        content:
          type: array
          items:
            type: object
        data:
          type: object
    QuoteCreateRequest:
      type: object
      required: [items]
      properties:
        idempotency_key:
          type: string
        contact:
          type: object
          properties:
            name: { type: string }
            email: { type: string }
            phone: { type: string }
        items:
          type: array
          minItems: 1
          items:
            type: object
            required: [width, height]
            properties:
              width: { type: integer }
              height: { type: integer }
              qty: { type: integer }
              price: { type: number }
              profileId: { type: string }
              typeCode: { type: string }
              glassId: { type: string }
              colorOutside: { type: string }
              colorInside: { type: string }
              xml: { type: string }
    SupportHandoffRequest:
      type: object
      properties:
        source:
          type: string
          default: ai_consultant
        session_id:
          type: string
        messages:
          type: array
          items:
            type: object
            properties:
              role: { type: string }
              content: { type: string }
        context:
          type: object
          additionalProperties: true
    SupportHandoffResponse:
      type: object
      properties:
        success: { type: boolean }
        handoffId: { type: string }
        queuedAt: { type: string }
        source: { type: string }
        sessionId: { type: string }
        messagesCount: { type: integer }
        contextKeys:
          type: array
          items: { type: string }
        webhook:
          type: object
          additionalProperties: true
    QuoteResponse:
      type: object
      properties:
        ok:
          type: boolean
        replayed:
          type: boolean
        idempotency_key:
          type: string
          nullable: true
        quote:
          type: object
          properties:
            id: { type: string }
            share_token: { type: string }
            status: { type: string }
            created_at: { type: string, format: date-time }
    QuoteGetResponse:
      type: object
      properties:
        ok:
          type: boolean
        quote:
          type: object
    PdfResponse:
      type: object
      properties:
        ok:
          type: boolean
        pdf:
          type: object
          properties:
            kind: { type: string }
            url: { type: string }
    ErrorEnvelope:
      type: object
      properties:
        ok:
          type: boolean
        error:
          type: object
          properties:
            code:
              type: string
              enum:
                - VALIDATION_ERROR
                - RATE_LIMITED
                - UNAUTHORIZED
                - FORBIDDEN
                - NOT_FOUND
                - UPSTREAM_ERROR
                - INTERNAL_ERROR
            message:
              type: string
            details:
              type: object
