openapi: 3.0.3
info:
  title: CardOS Tenant API
  version: "1.0-sandbox"
  description: >
    CardOS-as-API для тенантов. Авторизация — Bearer API-ключ (cms_sk_test_… = sandbox, cms_sk_live_… = live).
    Sandbox (test-ключ) работает на заглушке — реальных денег не двигает. Live включается отдельно.
    Денежные операции требуют заголовок Idempotency-Key. Провайдер карт скрыт (наружу только «CardOS»).
servers:
  - url: https://cardos.dev/api/v1
security:
  - ApiKey: []
components:
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer
      description: "Authorization: Bearer cms_sk_test_…"
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema: { type: string, minLength: 8, maxLength: 200 }
      description: Ключ идемпотентности денежной операции (8–200 симв). Повтор с тем же телом → тот же результат.
  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties: { code: { type: string }, message: { type: string } }
paths:
  /balance:
    get:
      summary: Баланс конечника
      parameters:
        - { name: external_user_ref, in: query, required: true, schema: { type: string } }
      responses:
        "200":
          description: OK
          content: { application/json: { schema: { type: object, properties: { external_user_ref: {type: string}, currency: {type: string}, balance_micro: {type: string}, mode: {type: string} } } } }
        "401": { description: Invalid API key, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
  /transactions:
    get:
      summary: Движения баланса
      parameters:
        - { name: external_user_ref, in: query, required: true, schema: { type: string } }
        - { name: limit, in: query, required: false, schema: { type: integer, default: 50, maximum: 100 } }
      responses:
        "200": { description: OK }
  /cards:
    post:
      summary: Выпуск карты конечнику
      parameters: [ { $ref: "#/components/parameters/IdempotencyKey" } ]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [external_user_ref]
              properties:
                external_user_ref: { type: string, description: "tg id / внешний ref (без PII)" }
                product: { type: string }
      responses:
        "201": { description: Card issued }
        "409": { description: Tenant pricing not configured }
  /cards/{id}:
    get:
      summary: Детали карты
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses:
        "200": { description: OK }
        "404": { description: Not found }
  /cards/{id}/freeze:
    post:
      summary: Заморозить карту
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses: { "200": { description: Frozen }, "404": { description: Not found } }
  /cards/{id}/unfreeze:
    post:
      summary: Разморозить карту
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses: { "200": { description: Active }, "404": { description: Not found } }
  /cards/{id}/set-pin:
    post:
      summary: Установить PIN
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      requestBody:
        required: true
        content: { application/json: { schema: { type: object, required: [pin], properties: { pin: { type: string, pattern: "^\\d{4}$" } } } } }
      responses: { "200": { description: OK }, "404": { description: Not found } }
  /cards/{id}/transactions:
    get:
      summary: Транзакции по карте
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
        - { name: limit, in: query, required: false, schema: { type: integer, default: 50, maximum: 100 } }
      responses: { "200": { description: OK }, "404": { description: Not found } }
  /cards/{id}/kill:
    post:
      summary: Закрыть карту
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses:
        "200": { description: Closed }
        "404": { description: Not found }
        "501": { description: Not available in live yet }
  /deposits:
    post:
      summary: Пополнить баланс
      parameters: [ { $ref: "#/components/parameters/IdempotencyKey" } ]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [external_user_ref, amount, method]
              properties:
                external_user_ref: { type: string }
                amount: { type: string, description: "USD decimal-строка, напр. \"100.50\" (не float)" }
                method: { type: string, enum: [usdt_trc20, usdt_ton] }
      responses:
        "201":
          description: Invoice
          content: { application/json: { schema: { type: object, properties: { deposit_id: {type: string}, pay_address: {type: string}, pay_amount_micro: {type: string}, expires_at: {type: string}, status: {type: string} } } } }
  /sandbox/deposits/{id}/confirm:
    post:
      summary: "[SANDBOX] Подтвердить пополнение"
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses:
        "200": { description: Credited }
        "403": { description: Sandbox-only }
  /webhook-endpoints:
    post:
      summary: Зарегистрировать вебхук
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url: { type: string, format: uri, description: "только https, не приватный адрес" }
                events: { type: array, items: { type: string, enum: [credited, issued, transaction, card_status, balance] } }
      responses:
        "201": { description: Created (secret shown once) }
        "422": { description: Unsafe url }
    get:
      summary: Список вебхуков
      responses: { "200": { description: OK } }
# Исходящие вебхуки: POST на ваш url с заголовком
#   X-CardOS-Signature: t=<unix>,v1=<hmac_sha256(secret, `${t}.${body}`)>
#   X-CardOS-Event: <type>
# Тело: { id, type, mode, created, data }. Проверяйте подпись + окно t (±5 мин). Дедуп по id.
