openapi: 3.1.0
info:
  title: Nasak Library API
  version: 1.0.0-alpha.1
  description: Versioned book discovery, contribution, reading, download, review, and administration contracts.
servers:
  - url: https://api.nasak.ir/v1/library
paths:
  /live:
    get:
      operationId: getLibraryLiveness
      summary: Check Library API liveness
      security: []
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /books:
    get:
      operationId: listLibraryBooks
      summary: Search or list publicly available books
      security: []
      parameters:
        - { name: query, in: query, schema: { type: string, maxLength: 220 } }
        - { name: author, in: query, schema: { type: string, maxLength: 180 } }
        - { name: isbn, in: query, schema: { type: string, maxLength: 32 } }
        - { name: year, in: query, schema: { type: string, maxLength: 8 } }
        - { name: language, in: query, schema: { type: string, maxLength: 16 } }
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /books/{id}:
    get:
      operationId: getLibraryBook
      summary: Get public details for a book
      security: []
      parameters: [{ $ref: '#/components/parameters/Id' }]
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '404': { $ref: '#/components/responses/NotFound' }
  /books/{id}/reviews:
    get:
      operationId: listBookReviews
      summary: List approved reviews for a book
      security: []
      parameters: [{ $ref: '#/components/parameters/Id' }]
      responses:
        '200': { $ref: '#/components/responses/Success' }
    post:
      operationId: createBookReview
      summary: Submit an authenticated review for moderation
      security: [{ cookieSession: [] }]
      parameters: [{ $ref: '#/components/parameters/Id' }]
      x-idempotency-exempt: true
      x-migration-note: Authenticated, same-origin, rate-limited moderated contribution.
      requestBody: { $ref: '#/components/requestBodies/JsonMutation' }
      responses:
        '201': { $ref: '#/components/responses/Success' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
  /books/{id}/download:
    post:
      operationId: createBookDownload
      summary: Register a download and issue a short-lived file token
      security: []
      parameters: [{ $ref: '#/components/parameters/Id' }]
      x-idempotency-exempt: true
      x-migration-note: Same-origin and rate-limited; anonymous downloads are intentionally supported.
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
  /files/{id}:
    get:
      operationId: downloadBookFile
      summary: Stream a book file using a short-lived signed download token
      security: []
      parameters:
        - { $ref: '#/components/parameters/Id' }
        - { name: token, in: query, required: true, schema: { type: string } }
      responses:
        '200':
          description: Decrypted PDF stream.
          content:
            application/pdf:
              schema: { type: string, contentEncoding: binary }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
  /uploads:
    post:
      operationId: uploadLibraryBook
      summary: Upload a verified user's PDF contribution
      security: [{ cookieSession: [] }]
      x-idempotency-exempt: true
      x-migration-note: Multipart upload with file checksum, encrypted storage, review queue, and rate limiting.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file, title]
              properties:
                file: { type: string, contentEncoding: binary }
                title: { type: string, minLength: 2, maxLength: 220 }
                author: { type: string, maxLength: 180 }
                isbn13: { type: string, maxLength: 32 }
                language: { type: string, default: fa }
                requestId: { type: string, format: uuid }
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '413': { $ref: '#/components/responses/PayloadTooLarge' }
        '422': { $ref: '#/components/responses/BadRequest' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
  /requests:
    post:
      operationId: createBookRequest
      summary: Request a missing book with eligible notification channels
      security: [{ cookieSession: [] }]
      x-idempotency-exempt: true
      x-migration-note: Verified-phone, same-origin, and rate-limited user mutation.
      requestBody: { $ref: '#/components/requestBodies/JsonMutation' }
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
  /requests/approved:
    get:
      operationId: listApprovedBookRequests
      summary: List approved requests available for fulfillment
      security: [{ cookieSession: [] }]
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
  /reading-list:
    get:
      operationId: listReadingList
      summary: List the current user's planned reading
      security: [{ cookieSession: [] }]
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
    post:
      operationId: createReadingListItem
      summary: Schedule a book and bot reminder
      security: [{ cookieSession: [] }]
      x-idempotency-exempt: true
      x-migration-note: Authenticated, same-origin, and rate-limited user mutation.
      requestBody: { $ref: '#/components/requestBodies/JsonMutation' }
      responses:
        '201': { $ref: '#/components/responses/Success' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
  /reading-list/{id}:
    patch:
      operationId: updateReadingListItem
      summary: Reschedule or update a reading-list item
      security: [{ cookieSession: [] }]
      parameters: [{ $ref: '#/components/parameters/Id' }]
      x-idempotency-exempt: true
      x-migration-note: Owner-scoped authenticated mutation.
      requestBody: { $ref: '#/components/requestBodies/JsonMutation' }
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /books/{id}/read-session:
    post:
      operationId: createReaderSession
      summary: Create an expiring online-reading session
      security: [{ cookieSession: [] }]
      parameters: [{ $ref: '#/components/parameters/Id' }]
      x-idempotency-exempt: true
      x-migration-note: Authenticated, same-origin, rate-limited session issuance.
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '202': { $ref: '#/components/responses/Success' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
  /books/{id}/reading-progress:
    get:
      operationId: getReadingProgress
      summary: Get synchronized progress for a book
      security: [{ cookieSession: [] }]
      parameters: [{ $ref: '#/components/parameters/Id' }]
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
    patch:
      operationId: updateReadingProgress
      summary: Save page, offset, and reader preferences
      security: [{ cookieSession: [] }]
      parameters: [{ $ref: '#/components/parameters/Id' }]
      x-idempotency-exempt: true
      x-migration-note: Debounced owner-scoped progress mutation.
      requestBody: { $ref: '#/components/requestBodies/JsonMutation' }
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
  /books/{id}/annotations:
    get:
      operationId: listReaderAnnotations
      summary: List the current user's annotations for a book
      security: [{ cookieSession: [] }]
      parameters: [{ $ref: '#/components/parameters/Id' }]
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
    post:
      operationId: createReaderAnnotation
      summary: Add a private reader annotation
      security: [{ cookieSession: [] }]
      parameters: [{ $ref: '#/components/parameters/Id' }]
      x-idempotency-exempt: true
      x-migration-note: Owner-scoped private annotation mutation.
      requestBody: { $ref: '#/components/requestBodies/JsonMutation' }
      responses:
        '201': { $ref: '#/components/responses/Success' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
  /books/{id}/annotations/{annotationId}:
    parameters:
      - { $ref: '#/components/parameters/Id' }
      - { $ref: '#/components/parameters/AnnotationId' }
    patch:
      operationId: updateReaderAnnotation
      summary: Update a private reader annotation
      security: [{ cookieSession: [] }]
      x-idempotency-exempt: true
      x-migration-note: Owner-scoped private annotation mutation.
      requestBody: { $ref: '#/components/requestBodies/JsonMutation' }
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      operationId: deleteReaderAnnotation
      summary: Delete a private reader annotation
      security: [{ cookieSession: [] }]
      x-idempotency-exempt: true
      x-migration-note: Owner-scoped private annotation deletion.
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /reader/files/{id}:
    parameters: [{ $ref: '#/components/parameters/Id' }]
    head:
      operationId: inspectReaderFile
      summary: Inspect the authorized encrypted reader asset
      security: [{ cookieSession: [] }]
      parameters: [{ $ref: '#/components/parameters/ReaderSession' }]
      responses:
        '200': { description: Reader file headers. }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
    get:
      operationId: streamReaderFile
      summary: Stream an authorized byte range from the encrypted reader asset
      security: [{ cookieSession: [] }]
      parameters:
        - { $ref: '#/components/parameters/ReaderSession' }
        - { name: Range, in: header, schema: { type: string, pattern: '^bytes=' } }
      responses:
        '200': { description: Complete PDF stream. }
        '206': { description: Partial PDF byte range. }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '416': { description: Invalid or unsatisfiable byte range. }
  /reader/sessions/{id}/activity:
    post:
      operationId: recordReaderActivity
      summary: Record visible page and active reading time
      security: [{ cookieSession: [] }]
      parameters:
        - { $ref: '#/components/parameters/Id' }
        - { $ref: '#/components/parameters/ReaderSession' }
      x-idempotency-exempt: true
      x-migration-note: High-frequency, deduplicated session telemetry.
      requestBody: { $ref: '#/components/requestBodies/JsonMutation' }
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
  /admin/books:
    post:
      operationId: createAdminBook
      summary: Create a book record as an administrator
      security: [{ cookieSession: [] }]
      x-idempotency-exempt: true
      x-migration-note: Audited administrator mutation.
      requestBody: { $ref: '#/components/requestBodies/JsonMutation' }
      responses: { '200': { $ref: '#/components/responses/Success' }, '400': { $ref: '#/components/responses/BadRequest' }, '401': { $ref: '#/components/responses/Unauthorized' }, '403': { $ref: '#/components/responses/Forbidden' } }
  /admin/books/{id}:
    patch:
      operationId: updateAdminBook
      summary: Update book metadata or publication status
      security: [{ cookieSession: [] }]
      parameters: [{ $ref: '#/components/parameters/Id' }]
      x-idempotency-exempt: true
      x-migration-note: Audited administrator mutation.
      requestBody: { $ref: '#/components/requestBodies/JsonMutation' }
      responses: { '200': { $ref: '#/components/responses/Success' }, '400': { $ref: '#/components/responses/BadRequest' }, '401': { $ref: '#/components/responses/Unauthorized' }, '403': { $ref: '#/components/responses/Forbidden' }, '404': { $ref: '#/components/responses/NotFound' } }
  /admin/uploads/{id}:
    patch:
      operationId: moderateBookUpload
      summary: Accept, review, or reject a user upload
      security: [{ cookieSession: [] }]
      parameters: [{ $ref: '#/components/parameters/Id' }]
      x-idempotency-exempt: true
      x-migration-note: Audited administrator moderation.
      requestBody: { $ref: '#/components/requestBodies/JsonMutation' }
      responses: { '200': { $ref: '#/components/responses/Success' }, '400': { $ref: '#/components/responses/BadRequest' }, '401': { $ref: '#/components/responses/Unauthorized' }, '403': { $ref: '#/components/responses/Forbidden' }, '404': { $ref: '#/components/responses/NotFound' } }
  /admin/files/{id}:
    patch:
      operationId: moderateBookFile
      summary: Change a stored book file's lifecycle status
      security: [{ cookieSession: [] }]
      parameters: [{ $ref: '#/components/parameters/Id' }]
      x-idempotency-exempt: true
      x-migration-note: Audited administrator mutation.
      requestBody: { $ref: '#/components/requestBodies/JsonMutation' }
      responses: { '200': { $ref: '#/components/responses/Success' }, '400': { $ref: '#/components/responses/BadRequest' }, '401': { $ref: '#/components/responses/Unauthorized' }, '403': { $ref: '#/components/responses/Forbidden' }, '404': { $ref: '#/components/responses/NotFound' } }
  /admin/requests/{id}:
    patch:
      operationId: moderateBookRequest
      summary: Approve, resolve, or reject a book request
      security: [{ cookieSession: [] }]
      parameters: [{ $ref: '#/components/parameters/Id' }]
      x-idempotency-exempt: true
      x-migration-note: Audited administrator mutation that may enqueue notifications.
      requestBody: { $ref: '#/components/requestBodies/JsonMutation' }
      responses: { '200': { $ref: '#/components/responses/Success' }, '400': { $ref: '#/components/responses/BadRequest' }, '401': { $ref: '#/components/responses/Unauthorized' }, '403': { $ref: '#/components/responses/Forbidden' }, '404': { $ref: '#/components/responses/NotFound' } }
  /admin/reviews:
    get:
      operationId: listPendingBookReviews
      summary: List reviews awaiting moderation
      security: [{ cookieSession: [] }]
      responses: { '200': { $ref: '#/components/responses/Success' }, '401': { $ref: '#/components/responses/Unauthorized' }, '403': { $ref: '#/components/responses/Forbidden' } }
    patch:
      operationId: moderateBookReview
      summary: Approve or reject a book review
      security: [{ cookieSession: [] }]
      x-idempotency-exempt: true
      x-migration-note: Audited administrator moderation.
      requestBody: { $ref: '#/components/requestBodies/JsonMutation' }
      responses: { '200': { $ref: '#/components/responses/Success' }, '400': { $ref: '#/components/responses/BadRequest' }, '401': { $ref: '#/components/responses/Unauthorized' }, '403': { $ref: '#/components/responses/Forbidden' } }
  /admin/bulk-upload:
    get:
      operationId: listBulkUploadQueue
      summary: List bulk PDF metadata jobs
      security: [{ cookieSession: [] }]
      responses: { '200': { $ref: '#/components/responses/Success' }, '401': { $ref: '#/components/responses/Unauthorized' }, '403': { $ref: '#/components/responses/Forbidden' } }
    post:
      operationId: createBulkUploadJobs
      summary: Upload up to twenty PDFs into the metadata queue
      security: [{ cookieSession: [] }]
      x-idempotency-exempt: true
      x-migration-note: Admin-only multipart ingestion with magic-byte validation and rate limiting.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [files]
              properties:
                files:
                  type: array
                  maxItems: 20
                  items: { type: string, contentEncoding: binary }
      responses: { '201': { $ref: '#/components/responses/Success' }, '401': { $ref: '#/components/responses/Unauthorized' }, '403': { $ref: '#/components/responses/Forbidden' }, '413': { $ref: '#/components/responses/PayloadTooLarge' }, '422': { $ref: '#/components/responses/BadRequest' }, '429': { $ref: '#/components/responses/TooManyRequests' } }
  /admin/bulk-upload/{id}:
    patch:
      operationId: assignBulkUploadMetadata
      summary: Finalize metadata and publish a queued PDF
      security: [{ cookieSession: [] }]
      parameters: [{ $ref: '#/components/parameters/Id' }]
      x-idempotency-exempt: true
      x-migration-note: Audited administrator queue mutation.
      requestBody: { $ref: '#/components/requestBodies/JsonMutation' }
      responses: { '200': { $ref: '#/components/responses/Success' }, '400': { $ref: '#/components/responses/BadRequest' }, '401': { $ref: '#/components/responses/Unauthorized' }, '403': { $ref: '#/components/responses/Forbidden' }, '404': { $ref: '#/components/responses/NotFound' } }
    delete:
      operationId: cancelBulkUploadJob
      summary: Cancel a queued bulk-upload item without deleting the stored audit record
      security: [{ cookieSession: [] }]
      parameters: [{ $ref: '#/components/parameters/Id' }]
      x-idempotency-exempt: true
      x-migration-note: Logical cancellation retained for audit.
      responses: { '200': { $ref: '#/components/responses/Success' }, '401': { $ref: '#/components/responses/Unauthorized' }, '403': { $ref: '#/components/responses/Forbidden' }, '404': { $ref: '#/components/responses/NotFound' } }
  /admin/bulk-upload/archive:
    get:
      operationId: listBulkArchiveJobs
      summary: List ZIP and RAR ingestion jobs
      security: [{ cookieSession: [] }]
      responses: { '200': { $ref: '#/components/responses/Success' }, '401': { $ref: '#/components/responses/Unauthorized' }, '403': { $ref: '#/components/responses/Forbidden' } }
    post:
      operationId: createBulkArchiveJob
      summary: Stream a ZIP or RAR archive into the asynchronous ingestion queue
      security: [{ cookieSession: [] }]
      parameters:
        - { name: filename, in: query, required: true, schema: { type: string, maxLength: 255 } }
      x-idempotency-exempt: true
      x-migration-note: Admin-only raw stream capped at 2 GiB and processed asynchronously.
      requestBody:
        required: true
        content:
          application/zip: { schema: { type: string, contentEncoding: binary } }
          application/vnd.rar: { schema: { type: string, contentEncoding: binary } }
      responses: { '202': { $ref: '#/components/responses/Success' }, '401': { $ref: '#/components/responses/Unauthorized' }, '403': { $ref: '#/components/responses/Forbidden' }, '413': { $ref: '#/components/responses/PayloadTooLarge' }, '422': { $ref: '#/components/responses/BadRequest' }, '429': { $ref: '#/components/responses/TooManyRequests' } }
components:
  securitySchemes:
    cookieSession:
      type: apiKey
      in: cookie
      name: pdf_library_session
  parameters:
    Id:
      name: id
      in: path
      required: true
      schema: { type: string, format: uuid }
    AnnotationId:
      name: annotationId
      in: path
      required: true
      schema: { type: string, format: uuid }
    ReaderSession:
      name: X-Reader-Session
      in: header
      required: true
      schema: { type: string }
  requestBodies:
    JsonMutation:
      required: true
      content:
        application/json:
          schema: { type: object, additionalProperties: true }
  responses:
    Success:
      description: Operation completed.
      content:
        application/json:
          schema: { type: object, additionalProperties: true }
    BadRequest:
      description: Invalid request.
      content:
        application/problem+json:
          schema: { $ref: '#/components/schemas/Problem' }
    Unauthorized:
      description: Authentication required.
      content:
        application/problem+json:
          schema: { $ref: '#/components/schemas/Problem' }
    Forbidden:
      description: Access denied.
      content:
        application/problem+json:
          schema: { $ref: '#/components/schemas/Problem' }
    NotFound:
      description: Resource not found.
      content:
        application/problem+json:
          schema: { $ref: '#/components/schemas/Problem' }
    PayloadTooLarge:
      description: Request body exceeds the endpoint limit.
      content:
        application/problem+json:
          schema: { $ref: '#/components/schemas/Problem' }
    TooManyRequests:
      description: Rate limit exceeded.
      content:
        application/problem+json:
          schema: { $ref: '#/components/schemas/Problem' }
  schemas:
    Problem:
      type: object
      required: [title, status]
      properties:
        type: { type: string, format: uri-reference }
        title: { type: string }
        status: { type: integer }
        detail: { type: string }
        code: { type: string }
        requestId: { type: string }
