openapi: 3.1.0
info:
  title: Zarfora e-İhale API
  version: 1.0.0
  description: |
    Zarfora e-ihale platformunun web ve mobil istemcilerinin kullandığı REST + WebSocket sözleşmesi.
    Tüm para alanları KURUŞ cinsinden tam sayıdır (125000 = 1.250,00 ₺). Zamanlar ISO 8601 (Europe/Istanbul).
    Demo notu: bu yayında sunucu yoktur; aynı işlemler tarayıcıdaki MockAdapter ile çalışır. Kurulumda Briefikir, kurumun altyapısında bu sözleşmeyi uygular.
servers:
  - url: https://api.ornek-kurum.com
    description: Kurulum örneği (demo — gerçek uç değildir)
security:
  - bearer: []
tags:
  - name: İhaleler
    description: İlan listesi, detay, dokümanlar, zeyilnameler
  - name: Soru-cevap
    description: Anonim istekli soruları ve kurum cevapları
  - name: Teklifler
    description: Kapalı zarf (istemcide şifreli) teklif, makbuz, geri çekme
  - name: Canlı oturumlar
    description: e-Eksiltme ve açık artırma; gerçek zamanlı kanal wss://…/v1/canli
  - name: Firmalar
    description: İstekli kaydı, belge doğrulama, yetkililer
  - name: Bildirimler
    description: Uygulama içi bildirim ve tercihler
  - name: Oturum
    description: Kimlik doğrulama (kurulumda SSO / e-imza ile)
paths:
  /v1/oturum:
    post:
      tags: [Oturum]
      summary: Erişim jetonu al
      security: []
      description: E-posta + parola ya da kurumsal SSO dönüşü ile 15 dakikalık erişim ve 30 günlük yenileme jetonu verir. İki adımlı doğrulama açıksa `ikiAdim` alanı istenir.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OturumIstek'
      responses:
        '200':
          description: Jetonlar
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Jeton'
        '401':
          description: Kimlik bilgisi hatalı
  /v1/ihaleler:
    get:
      tags: [İhaleler]
      summary: İhale ilanlarını listele
      security: []
      description: Yayındaki ilanlar. Süzgeçler birleştirilebilir; sonuç sayfalıdır (`imlec`).
      parameters:
        - name: yon
          in: query
          schema:
            type: string
            enum: [alim, satis]
        - name: usul
          in: query
          schema:
            type: string
            enum: [acik, belli, pazarlik, teklif-toplama, e-eksiltme, acik-artirma, kapali-zarf]
        - name: kategori
          in: query
          schema:
            type: string
        - name: il
          in: query
          schema:
            type: string
        - name: durum
          in: query
          schema:
            type: string
            enum: [ilanda, soru-cevap, teklif-acik, degerlendirmede, canli, sonuclandi, iptal]
        - name: q
          in: query
          schema:
            type: string
        - name: imlec
          in: query
          schema:
            type: string
      responses:
        '200':
          description: İlan özetleri
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IhaleSayfa'
  /v1/ihaleler/{ihaleId}:
    get:
      tags: [İhaleler]
      summary: İhale detayı
      security: []
      description: Kalemler, takvim, teminat, yeterlik kriterleri, zeyilnameler. Yaklaşık maliyet gizli ise `yaklasikMaliyet` alanı dönmez.
      parameters:
        - name: ihaleId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: İhale
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Ihale'
        '404':
          description: İlan bulunamadı ya da yayından kaldırıldı
  /v1/ihaleler/{ihaleId}/dokumanlar/{dokumanId}:
    get:
      tags: [İhaleler]
      summary: Doküman indir (imzalı geçici bağlantı)
      description: İndirme denetim izine yazılır (kim, ne zaman). Yanıt 5 dakika geçerli ön imzalı adrestir.
      parameters:
        - name: ihaleId
          in: path
          required: true
          schema:
            type: string
        - name: dokumanId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: İndirme adresi
          content:
            application/json:
              schema:
                type: object
                properties:
                  url:
                    type: string
                    example: https://dosya.ornek-kurum.com/d/teknik-sartname.pdf?imza=…
                  gecerlilik:
                    type: string
                    format: date-time
  /v1/ihaleler/{ihaleId}/takvim.ics:
    get:
      tags: [İhaleler]
      summary: Takvim dosyası (.ics)
      security: []
      parameters:
        - name: ihaleId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Soru son tarihi, teklif son tarihi, açılış ve oturum etkinlikleri
          content:
            text/calendar:
              schema:
                type: string
  /v1/ihaleler/{ihaleId}/sorular:
    get:
      tags: [Soru-cevap]
      summary: Yayımlanmış soru ve cevaplar
      security: []
      parameters:
        - name: ihaleId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Soru listesi (soran firma gizli)
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Soru'
    post:
      tags: [Soru-cevap]
      summary: Soru sor
      description: Soru, soru son tarihine kadar sorulabilir. Kurum cevabı yayımlarken soran firmanın kimliği gizlenir.
      parameters:
        - name: ihaleId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [metin]
              properties:
                metin:
                  type: string
                  minLength: 20
                  maxLength: 1200
                  example: Raf dikmeleri için statik hesapta hangi zemin emniyet gerilmesi esas alınmalı?
      responses:
        '201':
          description: Soru alındı (durum bekliyor)
        '409':
          description: Soru süresi doldu
  /v1/ihaleler/{ihaleId}/teklifler:
    post:
      tags: [Teklifler]
      summary: Kapalı zarf teklif gönder
      description: |
        Teklif içeriği İSTEMCİDE AES-GCM 256 ile şifrelenir; sunucu düz fiyatı görmez. Anahtar, komisyon üyelerinin pay anahtarlarıyla
        (eşik şifreleme / HSM) korunur ve yalnız teklif son tarihinden sonra, yeterli sayıda komisyon onayıyla çözülür.
        Aynı firmanın yeni gönderimi önceki sürümün yerini alır (`surum` artar). Yanıttaki `makbuz` SHA-256 özetidir.
      parameters:
        - name: ihaleId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ZarfIstek'
      responses:
        '201':
          description: Zarf mühürlendi
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Makbuz'
        '409':
          description: Teklif süresi doldu ya da firma doğrulanmamış
  /v1/ihaleler/{ihaleId}/teklifler/{teklifId}:
    delete:
      tags: [Teklifler]
      summary: Teklifi geri çek
      description: Teklif son tarihine kadar geri çekilebilir; denetim izine yazılır.
      parameters:
        - name: ihaleId
          in: path
          required: true
          schema:
            type: string
        - name: teklifId
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Geri çekildi
  /v1/ihaleler/{ihaleId}/acilis:
    post:
      tags: [Teklifler]
      summary: Zarf açılışı (komisyon)
      description: Teklif son tarihi geçmiş olmalı ve kurum ayarındaki sayıda komisyon üyesi onay vermiş olmalıdır. Açılış tutanağı üretilir.
      parameters:
        - name: ihaleId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Açılan teklifler ve tutanak
        '423':
          description: Onay sayısı yetersiz ya da süre dolmadı
  /v1/oturumlar/{oturumId}:
    get:
      tags: [Canlı oturumlar]
      summary: Oturum durumu
      description: Kalan süre, sıranız, en iyi teklif (ihale ayarı izin veriyorsa), uzatmalar. Rakip unvanları dönmez.
      parameters:
        - name: oturumId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Oturum özeti
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OturumDurum'
  /v1/oturumlar/{oturumId}/teklifler:
    post:
      tags: [Canlı oturumlar]
      summary: Canlı teklif / pey ver
      description: |
        Eksiltmede teklif, en iyi tekliften en az adım kadar düşük; artırmada en yüksek peyden en az adım kadar yüksek olmalıdır.
        Bitişe `uzatmaEsikSn` saniyeden az kala gelen geçerli teklif süreyi `uzatmaSn` saniyeye çıkarır (son dakika uzatması).
        Gerçek zamanlı güncellemeler wss://api.ornek-kurum.com/v1/canli?oturum={oturumId} kanalından `teklif`, `uzatma`, `bitis` olaylarıyla gelir.
      parameters:
        - name: oturumId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CanliTeklif'
      responses:
        '201':
          description: Kabul edildi
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OturumDurum'
        '422':
          description: Adım kuralını karşılamıyor
        '423':
          description: Oturum duraklatıldı ya da bitti
  /v1/firmalar:
    post:
      tags: [Firmalar]
      summary: İstekli firma kaydı
      security: []
      description: Kayıt sonrası belgeler incelenir; doğrulama tamamlanana kadar teklif verilemez. VKN ve MERSİS biçim kontrolünden geçer.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FirmaKayit'
      responses:
        '201':
          description: Kayıt alındı (dogrulama bekliyor)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Firma'
  /v1/firmalar/{firmaId}/belgeler:
    post:
      tags: [Firmalar]
      summary: Belge yükle
      description: İmza sirküleri, vergi levhası, faaliyet belgesi, iş deneyim belgesi… Ön imzalı yükleme adresi döner; geçerlilik tarihi yaklaşınca bildirim gönderilir.
      parameters:
        - name: firmaId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                tur:
                  type: string
                  enum: [imza-sirkuleri, vergi-levhasi, faaliyet-belgesi, is-deneyim, iso-9001, sgk-borcu-yok, vergi-borcu-yok, ticaret-sicil]
                gecerlilik:
                  type: string
                  format: date
      responses:
        '201':
          description: Yükleme adresi
  /v1/bildirimler:
    get:
      tags: [Bildirimler]
      summary: Bildirimlerim
      responses:
        '200':
          description: Bildirim listesi
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Bildirim'
  /v1/bildirimler/tercihler:
    put:
      tags: [Bildirimler]
      summary: Kanal tercihlerini kaydet
      requestBody:
        content:
          application/json:
            schema:
              type: object
              additionalProperties:
                type: object
                properties:
                  eposta:
                    type: boolean
                  sms:
                    type: boolean
                  push:
                    type: boolean
      responses:
        '204':
          description: Kaydedildi
webhooks:
  ihale.yayimlandi:
    post:
      summary: Yeni ilan yayımlandı
      description: 'İmza başlığı: X-Zarfora-Imza = HMAC-SHA256(gizli anahtar, ham gövde), hex.'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OlayYeniIlan'
      responses:
        '200':
          description: Alındı
  teklif.alindi:
    post:
      summary: Teklif zarfı mühürlendi (tutar içermez)
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OlayTeklif'
      responses:
        '200':
          description: Alındı
  oturum.sonuclandi:
    post:
      summary: Canlı oturum sona erdi
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OlayOturumSonuc'
      responses:
        '200':
          description: Alındı
components:
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    OturumIstek:
      type: object
      required: [eposta, parola]
      properties:
        eposta:
          type: string
          example: teklif@example.com
        parola:
          type: string
        ikiAdim:
          type: string
          example: '482913'
    Jeton:
      type: object
      properties:
        erisim:
          type: string
        yenileme:
          type: string
        sure:
          type: integer
          example: 900
    Kurus:
      type: integer
      description: Tutar, kuruş cinsinden (125000 = 1.250,00 ₺)
      example: 431250000
    IhaleOzet:
      type: object
      properties:
        id:
          type: string
          example: i-akis
        ikn:
          type: string
          example: 2026/ZR-004199
        baslik:
          type: string
          example: Gebze Bölge Dağıtım Deposu Raf Sistemi ve Elleçleme Ekipmanı Alımı
        yon:
          type: string
          enum: [alim, satis]
        usul:
          type: string
          example: e-eksiltme
        kategori:
          type: string
          example: ZK-32
        il:
          type: string
          example: Kocaeli
        durum:
          type: string
          example: teklif-acik
        teklifSon:
          type: string
          format: date-time
        yaklasikMaliyet:
          $ref: '#/components/schemas/Kurus'
    IhaleSayfa:
      type: object
      properties:
        kayitlar:
          type: array
          items:
            $ref: '#/components/schemas/IhaleOzet'
        sonraki:
          type: string
          description: Sonraki sayfa imleci
    Kalem:
      type: object
      properties:
        id:
          type: string
          example: i-akis-k1
        sira:
          type: integer
          example: 1
        ad:
          type: string
          example: Palet raf dikme çerçevesi (6.000 mm)
        birim:
          type: string
          example: adet
        miktar:
          type: number
          example: 180
        kdv:
          type: integer
          example: 20
    Ihale:
      type: object
      properties:
        ozet:
          $ref: '#/components/schemas/IhaleOzet'
        aciklama:
          type: array
          items:
            type: string
        kalemler:
          type: array
          items:
            $ref: '#/components/schemas/Kalem'
        teminat:
          type: object
          properties:
            geciciOran:
              type: number
              example: 3
            kesinOran:
              type: number
              example: 6
            turler:
              type: array
              items:
                type: string
              example: [mektup, nakit, e-teminat]
        yeterlik:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              ad:
                type: string
              zorunlu:
                type: boolean
        eksiltme:
          type: object
          properties:
            adimOran:
              type: number
              example: 0.5
            uzatmaEsikSn:
              type: integer
              example: 120
            uzatmaSn:
              type: integer
              example: 120
            gorunurluk:
              type: string
              enum: [sira, sira-en-iyi]
    Soru:
      type: object
      properties:
        id:
          type: string
        metin:
          type: string
        cevap:
          type: string
        zaman:
          type: string
          format: date-time
    ZarfIstek:
      type: object
      required: [sifreli, surum]
      properties:
        sifreli:
          type: object
          properties:
            alg:
              type: string
              const: AES-GCM-256
            iv:
              type: string
              description: base64, 12 bayt
            veri:
              type: string
              description: base64 şifreli içerik (birim fiyatlar, toplamlar)
            parmakIzi:
              type: string
              description: Anahtar parmak izi (ilk 16 hex)
        anahtarPayi:
          type: string
          description: Komisyon açık anahtarlarıyla sarılmış zarf anahtarı
        surum:
          type: integer
          example: 1
        teminat:
          type: object
          properties:
            sekil:
              type: string
              enum: [mektup, nakit, e-teminat]
            referans:
              type: string
              example: GT-4F21A9
        imza:
          type: object
          description: Nitelikli elektronik imza (kurulumda e-imza sağlayıcısı üzerinden)
          properties:
            sertifika:
              type: string
            imzaDegeri:
              type: string
    Makbuz:
      type: object
      properties:
        teklifId:
          type: string
        makbuz:
          type: string
          description: SHA-256(ihaleId|firmaId|zaman|surum|şifreli veri), hex
          example: 9f2c41b8d07e6a35c1f4b2e8a90d7c6153e2b48f0a1c9d7e6b5a4f3e2d1c0b9a
        zaman:
          type: string
          format: date-time
        surum:
          type: integer
    CanliTeklif:
      type: object
      properties:
        tutar:
          $ref: '#/components/schemas/Kurus'
        vekilUst:
          $ref: '#/components/schemas/Kurus'
    OturumDurum:
      type: object
      properties:
        durum:
          type: string
          enum: [planli, canli, duraklatildi, bitti]
        kalanMs:
          type: integer
          example: 312000
        siraniz:
          type: integer
          example: 2
        katilimci:
          type: integer
          example: 5
        enIyi:
          $ref: '#/components/schemas/Kurus'
        uzatma:
          type: integer
          example: 1
    FirmaKayit:
      type: object
      required: [unvan, vkn, il, yetkiliAd, eposta, telefon, kategoriler]
      properties:
        unvan:
          type: string
          example: Örnek Endüstriyel Ekipman A.Ş.
        vkn:
          type: string
          example: '1234567890'
        il:
          type: string
          example: Kocaeli
        yetkiliAd:
          type: string
        eposta:
          type: string
          example: teklif@example.com
        telefon:
          type: string
          example: 0123 456 78 90
        kategoriler:
          type: array
          items:
            type: string
          example: [ZK-32, ZK-31]
    Firma:
      type: object
      properties:
        id:
          type: string
        unvan:
          type: string
        dogrulama:
          type: string
          enum: [onayli, bekliyor, reddedildi]
    Bildirim:
      type: object
      properties:
        id:
          type: string
        tur:
          type: string
          enum: [yeni-ilan, zeyilname, soru-cevap, sure, davet, sonuc, belge, teminat, sistem]
        baslik:
          type: string
        okundu:
          type: boolean
    OlayYeniIlan:
      type: object
      properties:
        olay:
          type: string
          const: ihale.yayimlandi
        ihaleId:
          type: string
          example: i-akis
        ikn:
          type: string
          example: 2026/ZR-004199
        kategori:
          type: string
          example: ZK-32
        teklifSon:
          type: string
          format: date-time
    OlayTeklif:
      type: object
      properties:
        olay:
          type: string
          const: teklif.alindi
        ihaleId:
          type: string
        firmaId:
          type: string
        surum:
          type: integer
        makbuz:
          type: string
    OlayOturumSonuc:
      type: object
      properties:
        olay:
          type: string
          const: oturum.sonuclandi
        oturumId:
          type: string
        ihaleId:
          type: string
        enIyi:
          $ref: '#/components/schemas/Kurus'
        teklifSayisi:
          type: integer
          example: 37
        uzatma:
          type: integer
          example: 3
