openapi: 3.1.0
info:
  title: LoviSchet API v1
  version: 1.0.0
  description: |
    Актуальный публичный API для внешних интеграций.
    Контракт отражает текущее runtime-поведение `/api/v1/*`.
servers:
  - url: /
    description: Current origin
tags:
  - name: API v1
security:
  - ApiKeyHeader: []
  - BearerAuth: []
  - ApiKeyQuery: []
paths:
  /api/openapi.yaml:
    get:
      tags: [API v1]
      summary: Машинно-читаемая спецификация API v1
      responses:
        '200':
          description: YAML спецификация
          content:
            application/yaml:
              schema:
                type: string
  /api/openapi.json:
    get:
      tags: [API v1]
      summary: JSON-спецификация API v1
      responses:
        '200':
          description: JSON спецификация
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
  /api/v1/ref/catalog:
    get:
      tags: [API v1]
      summary: Справочники команды
      description: Возвращает справочники и enum-значения для активной команды API-ключа.
      responses:
        '200':
          description: Успешный ответ
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CatalogResponse'
        '401':
          $ref: '#/components/responses/AuthError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '429':
          $ref: '#/components/responses/RateLimitError'
  /api/v1/invoices:
    get:
      tags: [API v1]
      summary: Список документов команды
      parameters:
        - in: query
          name: q
          schema:
            type: string
          description: Текстовый поиск
        - in: query
          name: date_from
          schema:
            type: string
            format: date
        - in: query
          name: date_to
          schema:
            type: string
            format: date
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            default: 50
        - in: query
          name: offset
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        '200':
          description: Успешный ответ
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceListResponse'
        '401':
          $ref: '#/components/responses/AuthError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '429':
          $ref: '#/components/responses/RateLimitError'
    post:
      tags: [API v1]
      summary: Создать документ
      description: |
        Поддерживает два формата тела:
        1. корневой объект документа
        2. объект с полем `invoice`
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateInvoiceRequest'
      responses:
        '200':
          description: Документ создан
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateUpdateInvoiceResponse'
        '207':
          description: Документ сохранен, часть писем не отправлена
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateUpdateInvoiceResponse'
        '400':
          $ref: '#/components/responses/BadRequestError'
        '401':
          $ref: '#/components/responses/AuthError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '413':
          $ref: '#/components/responses/PayloadTooLargeError'
        '429':
          $ref: '#/components/responses/RateLimitError'
  /api/v1/invoices/{invoice_public_id}:
    get:
      tags: [API v1]
      summary: Карточка документа
      parameters:
        - in: path
          name: invoice_public_id
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Успешный ответ
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceDetailResponse'
        '401':
          $ref: '#/components/responses/AuthError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '429':
          $ref: '#/components/responses/RateLimitError'
    put:
      tags: [API v1]
      summary: Полностью обновить документ
      description: Partial update не поддерживается. Для PUT требуется полный объект в поле `invoice`.
      parameters:
        - in: path
          name: invoice_public_id
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateInvoiceRequest'
      responses:
        '200':
          description: Документ обновлен
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateUpdateInvoiceResponse'
        '207':
          description: Документ обновлен, часть писем не отправлена
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateUpdateInvoiceResponse'
        '400':
          $ref: '#/components/responses/BadRequestError'
        '401':
          $ref: '#/components/responses/AuthError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '413':
          $ref: '#/components/responses/PayloadTooLargeError'
        '429':
          $ref: '#/components/responses/RateLimitError'
components:
  securitySchemes:
    ApiKeyHeader:
      type: apiKey
      in: header
      name: x-api-key
    ApiKeyQuery:
      type: apiKey
      in: query
      name: api_key
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
  responses:
    AuthError:
      description: Ошибка авторизации API
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/AuthErrorResponse'
    ForbiddenError:
      description: Ошибка scope/tenant/seller
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    BadRequestError:
      description: Неверный запрос
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFoundError:
      description: Счет не найден
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    ConflictError:
      description: Конфликт данных или ограничение операций
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    PayloadTooLargeError:
      description: Слишком большой JSON payload
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimitError:
      description: Превышен rate limit
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RateLimitErrorResponse'
  schemas:
    CatalogResponse:
      type: object
      required: [ok, organizations, bank_accounts, managers, signers, enums]
      properties:
        ok:
          type: boolean
          const: true
        organizations:
          type: array
          items:
            type: object
            additionalProperties: true
        bank_accounts:
          type: array
          items:
            type: object
            additionalProperties: true
        managers:
          type: array
          items:
            type: object
            additionalProperties: true
        signers:
          type: array
          items:
            type: object
            additionalProperties: true
        enums:
          type: object
          additionalProperties: true
    InvoiceListResponse:
      type: object
      required: [ok, total, items]
      properties:
        ok:
          type: boolean
          const: true
        total:
          type: integer
        demo_mode:
          type: boolean
        demo_guids:
          type: object
          additionalProperties: true
        items:
          type: array
          items:
            type: object
            additionalProperties: true
    InvoiceDetailResponse:
      type: object
      required: [ok, public_id, invoice_url, portal_view_url, portal_edit_url, share_links, status, validation, metadata, invoice]
      properties:
        ok:
          type: boolean
          const: true
        public_id:
          type: string
        document_public_id:
          type: string
        document_kind:
          type: string
        demo_mode:
          type: boolean
        demo_guids:
          type: object
          additionalProperties: true
        document_url:
          type: string
        invoice_url:
          type: string
        portal_view_url:
          type: string
        portal_document_view_url:
          type: string
        portal_edit_url:
          type: string
        portal_document_edit_url:
          type: string
        share_links:
          $ref: '#/components/schemas/ShareLinks'
        status:
          $ref: '#/components/schemas/InvoiceStatus'
        validation:
          type: object
          additionalProperties: true
        metadata:
          $ref: '#/components/schemas/InvoiceMetadata'
        invoice:
          type: object
          additionalProperties: true
    CreateUpdateInvoiceResponse:
      allOf:
        - $ref: '#/components/schemas/InvoiceDetailResponse'
        - type: object
          required: [document, notifications, share_gate, api, totals, qr]
          properties:
            document:
              $ref: '#/components/schemas/DocumentSummary'
            notifications:
              $ref: '#/components/schemas/Notifications'
            share_gate:
              $ref: '#/components/schemas/ShareGateSummary'
            api:
              $ref: '#/components/schemas/ApiWriteMeta'
            totals:
              $ref: '#/components/schemas/InvoiceTotalsSummary'
            qr:
              $ref: '#/components/schemas/InvoiceQrSummary'
    DocumentContext:
      type: object
      properties:
        document_public_id:
          type: string
        document_kind:
          type: string
    DocumentSummary:
      type: object
      properties:
        public_id:
          type: string
        document_public_id:
          type: string
        document_kind:
          type: string
        kind_version:
          type: string
        kind_ordinal:
          type: integer
        sort_order:
          type: integer
        document_title:
          type: string
        title_is_custom:
          type: boolean
        package_public_id:
          type: string
        legacy_invoice_public_id:
          type: string
    ShareGateSummary:
      allOf:
        - $ref: '#/components/schemas/DocumentContext'
        - type: object
          additionalProperties: true
    ApiWriteMeta:
      allOf:
        - $ref: '#/components/schemas/DocumentContext'
        - type: object
          properties:
            key_id:
              type: integer
            key_name:
              type: string
          additionalProperties: true
    InvoiceTotalsSummary:
      allOf:
        - $ref: '#/components/schemas/DocumentContext'
        - type: object
          properties:
            subtotal:
              type: number
            vat_total:
              type: number
            total:
              type: number
            currency:
              type: string
          additionalProperties: true
    InvoiceQrSummary:
      allOf:
        - $ref: '#/components/schemas/DocumentContext'
        - type: object
          properties:
            enabled:
              type: boolean
            url:
              type: string
            payload:
              type: string
          additionalProperties: true
    CreateInvoiceRequest:
      type: object
      description: |
        Либо корневой invoice-объект, либо envelope с полем `invoice`.
        Медиа через внешний API не принимаются. Поля с URL/base64 изображений должны отсутствовать.
      properties:
        actor_login:
          type: string
        notify_recipients:
          type: boolean
        send_notifications:
          type: boolean
        invoice:
          $ref: '#/components/schemas/InvoicePayload'
      additionalProperties: true
    UpdateInvoiceRequest:
      type: object
      description: |
        Полное обновление документа.
        Медиа через внешний API не принимаются. Поля с URL/base64 изображений должны отсутствовать.
      properties:
        actor_login:
          type: string
        notify_recipients:
          type: boolean
        send_notifications:
          type: boolean
        partial_update:
          type: boolean
          description: Не поддерживается. Приведет к `400 partial_update_not_supported`.
        patch:
          type: boolean
          description: Не поддерживается. Приведет к `400 partial_update_not_supported`.
        invoice:
          $ref: '#/components/schemas/InvoicePayload'
      additionalProperties: true
      required: [invoice]
    InvoicePayload:
      type: object
      description: |
        Полезная нагрузка документа.
        Медиа-поля через внешний API запрещены. Не передавайте логотипы, печати, факсимиле и QR-изображения в JSON.
      required: [number, date, currency, seller, buyer, items]
      properties:
        public_id:
          type: string
        number:
          type: string
        date:
          type: string
          format: date
        currency:
          type: string
          enum: [RUB, USD, EUR, CNY, KZT, BYN]
        invoice_direction:
          type: string
          enum: [incoming, outgoing]
        seller:
          $ref: '#/components/schemas/Party'
        buyer:
          $ref: '#/components/schemas/Party'
        items:
          type: array
          items:
            $ref: '#/components/schemas/InvoiceItem'
      additionalProperties: true
      x-prohibited-fields:
        - seller_logo_url
        - seller_stamp_url
        - payment_qr_url
        - signers[].facsimile_url
        - manager.contact_qr_url
        - buyer_contact.contact_qr_url
    Party:
      type: object
      properties:
        name:
          type: string
        inn:
          type: string
        email:
          type: string
      additionalProperties: true
    InvoiceItem:
      type: object
      properties:
        name:
          type: string
        qty:
          type: number
        unit:
          type: string
        price:
          type: number
        vat_pct:
          type: number
      additionalProperties: true
    ShareLinks:
      type: object
      properties:
        colleague_url:
          type: string
        colleague_document_url:
          type: string
        buyer_url:
          type: string
        buyer_document_url:
          type: string
        recipients:
          type: array
          items:
            $ref: '#/components/schemas/ShareRecipientLink'
    ShareRecipientLink:
      type: object
      properties:
        email:
          type: string
        name:
          type: string
        position:
          type: string
        contact_id:
          type: integer
          nullable: true
        url:
          type: string
        document_url:
          type: string
      additionalProperties: true
    InvoiceStatus:
      type: object
      properties:
        seller_status:
          type: string
        seller_status_label:
          type: string
        buyer_status:
          type: string
        buyer_status_label:
          type: string
        service_status:
          type: string
        service_status_label:
          type: string
        service_status_reason:
          type: string
    InvoiceMetadata:
      type: object
      properties:
        public_id:
          type: string
        document_public_id:
          type: string
        document_kind:
          type: string
        kind_version:
          type: string
        kind_ordinal:
          type: integer
        sort_order:
          type: integer
        document_title:
          type: string
        title_is_custom:
          type: boolean
        package_public_id:
          type: string
        legacy_invoice_public_id:
          type: string
        source_type:
          type: string
        editor_mode:
          type: string
        information_system:
          type: string
        created_at:
          type: string
        created_by_login:
          type: string
        last_modified_at:
          type: string
        last_modified_by_login:
          type: string
        seller_edo_id:
          type: string
        buyer_edo_id:
          type: string
        public_share_enabled:
          type: boolean
        api_key_id:
          type: integer
        api_key_name:
          type: string
        api_request_host:
          type: string
        api_created_at:
          type: string
        api_updated_at:
          type: string
        status:
          $ref: '#/components/schemas/InvoiceStatus'
        validation:
          type: object
          additionalProperties: true
        notifications:
          type: object
          additionalProperties: true
        api_recipients:
          type: array
          items:
            $ref: '#/components/schemas/ApiRecipient'
        links:
          $ref: '#/components/schemas/InvoiceLinks'
    InvoiceLinks:
      type: object
      properties:
        share_url:
          type: string
        document_url:
          type: string
        portal_view_url:
          type: string
        portal_document_view_url:
          type: string
        portal_edit_url:
          type: string
        portal_document_edit_url:
          type: string
      additionalProperties: true
    ApiRecipient:
      type: object
      properties:
        email:
          type: string
        name:
          type: string
        position:
          type: string
        contact_id:
          type: integer
          nullable: true
        buyer_invoice_url:
          type: string
        buyer_document_url:
          type: string
      additionalProperties: true
    Notifications:
      type: object
      properties:
        document_public_id:
          type: string
        document_kind:
          type: string
        planned_count:
          type: integer
        sent_count:
          type: integer
        failed_count:
          type: integer
        sent_to:
          type: array
          items:
            type: string
        failed:
          type: array
          items:
            $ref: '#/components/schemas/NotificationFailure'
        blocked_by_service_gate:
          type: boolean
        block_reason:
          type: string
        mail_runtime:
          type: object
          additionalProperties: true
    NotificationFailure:
      type: object
      properties:
        email:
          type: string
        error:
          type: string
      additionalProperties: true
    ErrorResponse:
      type: object
      required: [ok, error]
      properties:
        ok:
          type: boolean
          const: false
        error:
          type: string
        document_public_id:
          type: string
        document_kind:
          type: string
        details:
          type: string
        missing_fields:
          type: array
          items:
            type: string
        demo_public_id:
          type: string
    AuthErrorResponse:
      allOf:
        - $ref: '#/components/schemas/ErrorResponse'
        - type: object
          properties:
            error:
              type: string
              enum: [unauthorized]
            code:
              type: string
              enum:
                - api_key_missing
                - api_key_invalid
                - api_key_inactive
                - api_key_over_tariff_limit
    RateLimitErrorResponse:
      allOf:
        - $ref: '#/components/schemas/ErrorResponse'
        - type: object
          properties:
            error:
              type: string
              enum: [rate_limited]
            code:
              type: string
            retry_after_sec:
              type: integer
