openapi: 3.0.3
info:
  title: ledger-core
  version: 0.1.0
  description: >
    Двойная бухгалтерская запись. Суммы: целые числа i64 в минимальных единицах валюты
    (положительное: дебет, отрицательное: кредит). Сумма строк каждой проводки равна 0.
    Журнал неизменяем (триггеры SQLite) и связан хэш-цепочкой SHA-256.
    Значения больше 2^53 теряют точность в JavaScript-клиентах.
servers:
  - url: http://localhost:8115
paths:
  /v1/accounts:
    get:
      summary: Счета с текущим сальдо
      responses:
        "200": { description: Список счетов }
    post:
      summary: Создать счёт (нужен Bearer, если задан API_TOKEN)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [code, name, kind, currency]
              properties:
                code: { type: string, example: "1000" }
                name: { type: string }
                kind: { type: string, enum: [asset, liability, equity, revenue, expense] }
                currency: { type: string, example: RUB }
      responses:
        "201": { description: Создан }
        "400": { description: Некорректный ввод }
        "401": { description: Нужен токен }
        "409": { description: Счёт уже есть }
  /v1/accounts/{code}/balance:
    get:
      summary: Сальдо счёта
      parameters:
        - { name: code, in: path, required: true, schema: { type: string } }
      responses:
        "200": { description: Счёт и сальдо }
        "404": { description: Не найден }
  /v1/transactions:
    get:
      summary: Журнал (новые сверху)
      parameters:
        - { name: limit, in: query, schema: { type: integer, default: 50, maximum: 200 } }
        - { name: before, in: query, schema: { type: integer }, description: Только записи с seq меньше этого }
      responses:
        "200": { description: Записи с проводками и хэшами }
    post:
      summary: Провести транзакцию
      description: >
        Заголовок Idempotency-Key делает запрос безопасным для повтора: тот же ключ и то же тело
        вернёт исходную запись (200, Idempotent-Replay true), другое тело даст 409.
      parameters:
        - { name: Idempotency-Key, in: header, schema: { type: string, maxLength: 128 } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [currency, postings]
              properties:
                currency: { type: string, example: RUB }
                memo: { type: string, maxLength: 200 }
                postings:
                  type: array
                  minItems: 2
                  maxItems: 100
                  items:
                    type: object
                    required: [account, amount]
                    properties:
                      account: { type: string }
                      amount: { type: integer, format: int64, description: "Ненулевое; сумма всех строк равна 0" }
      responses:
        "201": { description: Записано }
        "200": { description: Повтор по ключу идемпотентности }
        "400": { description: Некорректный ввод }
        "401": { description: Нужен токен }
        "409": { description: Ключ использован с другим телом }
        "422": { description: Не сбалансировано, неизвестный счёт, другая валюта, переполнение }
  /v1/verify:
    get:
      summary: Проверка целостности хэш-цепочки
      responses:
        "200":
          description: Отчёт
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  checked: { type: integer }
                  head_hash: { type: string }
                  first_bad_seq: { type: integer, nullable: true }
                  reason: { type: string, nullable: true }
  /v1/reports/trial-balance:
    get:
      summary: Оборотно-сальдовая ведомость
      parameters:
        - { name: currency, in: query, schema: { type: string, default: RUB } }
      responses:
        "200": { description: Обороты дебет/кредит и сальдо по счетам, итоги }
  /healthz:
    get:
      summary: Проверка живости
      responses:
        "200": { description: ok }
