openapi: 3.0.3
info:
  title: ProjectK API
  version: "1.0"
  description: |
    Nekastodialnyy servis podtverzhdeniya kriptoplatezhey (BTC + LTC).
    Dengi idut napryamuyu na koshelek klienta (xpub-derivaciya) - my tolko nablyudaem
    blockchain i shlem webhook. Vozvratov net (nekastodial).
    Autentifikaciya: X-Api-Key (server-to-server) ili Bearer session_token (LK).
servers:
  - url: https://api.example.com
security:
  - ApiKeyAuth: []
paths:
  /v1/auth/register:
    post:
      summary: Registraciya (otvet odinakov dlya novogo i zanyatogo email)
      security: []
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email: { type: string }
                password: { type: string, minLength: 8 }
      responses:
        "201": { description: "ok (dostup podtverzhdaetsya loginom)" }
        "422": { description: validation_error }
  /v1/auth/login:
    post:
      summary: Login -> session_token
      security: []
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email: { type: string }
                password: { type: string }
      responses:
        "200": { description: "session_token + expires_at" }
        "401": { description: invalid_credentials }
        "429": { description: rate_limited (po IP i po email) }
  /v1/auth/logout:
    post: { summary: Vyhod (tekushaya sessiya), security: [{ BearerAuth: [] }], responses: { "200": { description: ok } } }
  /v1/auth/logout-all:
    post: { summary: Vyhod so vseh ustroystv, security: [{ BearerAuth: [] }], responses: { "200": { description: ok } } }
  /v1/auth/change-password:
    post:
      summary: Smena parolya (vse sessii invalidiruyutsya)
      security: [{ BearerAuth: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [current_password, new_password]
              properties:
                current_password: { type: string }
                new_password: { type: string, minLength: 8 }
      responses:
        "200": { description: ok }
        "401": { description: nevernyy tekushiy parol }
  /v1/me:
    get:
      summary: Tekushiy akkaunt + proekt (donate/overlay tokeny, koshelki s next_index)
      security: [{ BearerAuth: [] }]
      responses: { "200": { description: ok } }
  /v1/api-keys:
    post:
      summary: Sozdat API-klyuch (token pokazyvaetsya odin raz)
      security: [{ BearerAuth: [] }]
      responses: { "201": { description: "key_public + token" } }
    get:
      summary: Spisok klyuchey proekta
      security: [{ BearerAuth: [] }]
      responses: { "200": { description: ok } }
  /v1/api-keys/{keyPublic}:
    delete:
      summary: Otozvat klyuch
      security: [{ BearerAuth: [] }]
      parameters: [{ name: keyPublic, in: path, required: true, schema: { type: string } }]
      responses:
        "200": { description: ok }
        "404": { description: key_not_found }
  /v1/wallets:
    post:
      summary: Dobavit koshelek (xpub/ypub/zpub/Ltub/Mtub, normalizuetsya)
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [currency, xpub]
              properties:
                currency: { type: string, enum: [BTC, LTC] }
                xpub: { type: string }
                script_type: { type: string, enum: [p2pkh, p2sh_p2wpkh, p2wpkh] }
                derivation_path: { type: string }
      responses:
        "201": { description: "verification_address (index 0) dlya sverki" }
  /v1/wallets/{currency}/verify:
    post:
      summary: Podtverdit vladenie (adres iz svoego koshelka)
      parameters: [{ name: currency, in: path, required: true, schema: { type: string, enum: [BTC, LTC] } }]
      requestBody:
        content:
          application/json:
            schema: { type: object, required: [address], properties: { address: { type: string } } }
      responses: { "200": { description: verified } }
  /v1/webhooks:
    post:
      summary: Zadat webhook-endpoint (sekret otdaetsya ODIN raz; https-only)
      requestBody:
        content:
          application/json:
            schema: { type: object, required: [url], properties: { url: { type: string, format: uri } } }
      responses: { "201": { description: "url + secret (whsec_...)" } }
  /v1/invoices:
    post:
      summary: Sozdat invoys
      description: |
        Idempotentnost: povtor s tem zhe order_id ili zagolovkom Idempotency-Key vozvrashaet
        sushestvuyushiy invoys (200 vmesto 201). Trebuetsya aktivnaya podpiska (402 inache).
      parameters:
        - { name: Idempotency-Key, in: header, required: false, schema: { type: string, maxLength: 200 } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [currency, type]
              properties:
                currency: { type: string, enum: [BTC, LTC] }
                type: { type: string, enum: [donation, order] }
                expected_amount: { type: string, description: "min. edinicy (satoshi); obyazatelen dlya order" }
                order_id: { type: string, maxLength: 200 }
                ttl_seconds: { type: integer, minimum: 60, maximum: 86400 }
      responses:
        "201": { description: sozdan }
        "200": { description: "idempotentnyy povtor - sushestvuyushiy invoys" }
        "402": { description: subscription_inactive }
        "422": { description: validation_error / wallet_not_verified }
        "503": { description: address_pool_busy (povtorit cherez minutu) }
    get:
      summary: Spisok invoysov proekta
      responses: { "200": { description: ok } }
  /v1/invoices/{publicId}:
    get:
      summary: Status invoysa (confirmations, confirmations_required, donor-polya)
      parameters: [{ name: publicId, in: path, required: true, schema: { type: string } }]
      responses: { "200": { description: ok }, "404": { description: invoice_not_found } }
  /v1/invoices/{publicId}/extend:
    post:
      summary: Prodlit TTL odin raz (+15 min, tolko new/pending)
      parameters: [{ name: publicId, in: path, required: true, schema: { type: string } }]
      responses: { "200": { description: ok }, "409": { description: already_extended } }
  /v1/stats:
    get:
      summary: Statistika proekta (prinyato po valyutam, statusy, tipy, aktivnost 30d)
      responses: { "200": { description: ok } }
  /v1/public/invoices/{publicId}:
    get:
      summary: Publichnyy status invoysa (dlya stranicy oplaty)
      security: []
      parameters: [{ name: publicId, in: path, required: true, schema: { type: string } }]
      responses: { "200": { description: ok }, "404": { description: not found } }
  /v1/public/invoices/{publicId}/events:
    get:
      summary: SSE-potok statusa invoysa
      security: []
      parameters: [{ name: publicId, in: path, required: true, schema: { type: string } }]
      responses: { "200": { description: text/event-stream } }
  /v1/public/donate/{token}:
    get:
      summary: Info donat-ssylki (dostupnye valyuty)
      security: []
      parameters: [{ name: token, in: path, required: true, schema: { type: string } }]
      responses: { "200": { description: ok }, "404": { description: not found } }
    post:
      summary: Sozdat donat (adres vydaetsya; rate-limit po IP)
      security: []
      parameters: [{ name: token, in: path, required: true, schema: { type: string } }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [currency]
              properties:
                currency: { type: string, enum: [BTC, LTC] }
                amount: { type: string, description: "min. edinicy, opcionalno" }
                donor_name: { type: string, maxLength: 50 }
                donor_message: { type: string, maxLength: 200 }
      responses: { "201": { description: "public_id + pay_url" } }
  /v1/public/fee/{currency}:
    get:
      summary: Ocenka setevoy komissii
      security: []
      parameters: [{ name: currency, in: path, required: true, schema: { type: string, enum: [BTC, LTC] } }]
      responses: { "200": { description: ok } }
  /v1/public/overlay/{token}/events:
    get:
      summary: SSE podtverzhdennyh donatov proekta (OBS-overlay)
      security: []
      parameters: [{ name: token, in: path, required: true, schema: { type: string } }]
      responses: { "200": { description: text/event-stream } }
components:
  securitySchemes:
    ApiKeyAuth: { type: apiKey, in: header, name: X-Api-Key }
    BearerAuth: { type: http, scheme: bearer }
  x-webhooks:
    invoice-events:
      description: |
        Sobytiya: invoice.confirmed, invoice.expired, invoice.underpaid, invoice.late_payment.
        Podpis: zagolovok X-Signature = "t=<unix>,v1=<hex>", gde v1 = HMAC-SHA256(secret, "<t>.<body>").
        Primer proverki - examples/webhook-receiver.mjs. Retrai: srazu, 5m, 30m, 2h, 5h, 10h, dalee kazhdye 12h.
