diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000000..c6f278edf7
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,30 @@
+# OpenAPI specification
+
+This directory includes the OpenAPI spec for the ABS server.
+The spec is made up of a number of individual `yaml` files located here and in the subfolders, with `root.yaml` being the file that references all of the others.
+The files are organized to have the same hierarchy as the server source files.
+The full spec is bundled into one file in `openapi.json`.
+
+The spec is linted and bundled by the [`vacuum` tool](https://quobix.com/vacuum/).
+The spec can also be tested with a real server using the [`wiretap` tool](https://pb33f.io/wiretap/).
+Both of these tools are created by [pb33f](https://pb33f.io/).
+
+### Bundling the spec
+The command to bundle the spec into a `yaml` file is `vacuum bundle root.yaml openapi.yaml`.
+
+The current version of `vacuum` cannot convert input `yaml` files to `json` files.
+To convert the spec to `json`, you can use the `yq` tool or another tool.
+
+The command to convert the spec using `yq` is `yq -p yaml -o json openapi.yaml > openapi.json`.
+
+### Viewing report
+To generate an HTML report, you can use `vacuum html-report [file]` to generate `report.html` and view the report in your browser.
+
+### Putting it all together
+The full command that I run to bundle the spec and generate the report is:
+
+```
+vacuum bundle root.yaml openapi.yaml && \
+yq -p yaml -o json openapi.yaml > openapi.json && \
+vacuum html-report openapi.json
+```
diff --git a/docs/controllers/AuthorController.yaml b/docs/controllers/AuthorController.yaml
new file mode 100644
index 0000000000..2af95a62f8
--- /dev/null
+++ b/docs/controllers/AuthorController.yaml
@@ -0,0 +1,139 @@
+components:
+  schemas:
+    authorUpdated:
+      description: Whether the author was updated without errors. Will not exist if author was merged.
+      type: boolean
+      nullable: true
+  parameters:
+    authorId:
+      name: id
+      in: path
+      description: Author ID
+      required: true
+      schema:
+        $ref: '../objects/entities/Author.yaml#/components/schemas/authorId'
+    authorInclude:
+      name: include
+      in: query
+      description: A comma separated list of what to include with the author. The options are `items` and `series`. `series` will only have an effect if `items` is included.
+      required: false
+      schema:
+        type: string
+        example: "items"
+      examples:
+        empty:
+          summary: Do not return library items
+          value: ""
+        itemOnly:
+          summary: Only return library items
+          value: "items"
+        itemsAndSeries:
+          summary: Return library items and series
+          value: "items,series"
+    authorLibraryId:
+      name: library
+      in: query
+      description: The ID of the library to to include filter included items from.
+      required: false
+      schema:
+        $ref: '../objects/Library.yaml#/components/schemas/libraryId'
+    asin:
+      name: asin
+      in: query
+      description: The Audible Identifier (ASIN).
+      required: false
+      schema:
+        $ref: '../objects/entities/Author.yaml#/components/schemas/authorAsin'
+    authorSearchName:
+      name: q
+      in: query
+      description: The name of the author to use for searching.
+      required: false
+      schema:
+        type: string
+        example: Terry Goodkind
+    authorName:
+      name: name
+      in: query
+      description: The new name of the author.
+      required: false
+      schema:
+        $ref: '../objects/entities/Author.yaml#/components/schemas/authorName'
+    authorDescription:
+      name: description
+      in: query
+      description: The new description of the author.
+      required: false
+      schema:
+        type: string
+        nullable: true
+        example: Terry Goodkind is a #1 New York Times Bestselling Author and creator of the critically acclaimed masterwork, ‘The Sword of Truth’. He has written 30+ major, bestselling novels, has been published in more than 20 languages world-wide, and has sold more than 26 Million books. ‘The Sword of Truth’ is a revered literary tour de force, comprised of 17 volumes, borne from over 25 years of dedicated writing.
+    authorImagePath:
+      name: imagePath
+      in: query
+      description: The new absolute path for the author image.
+      required: false
+      schema:
+        type: string
+        nullable: true
+        example: /metadata/authors/aut_z3leimgybl7uf3y4ab.jpg
+    imageUrl:
+      name: url
+      in: query
+      description: The URL of the image to add to the server
+      required: true
+      schema:
+        type: string
+        format: uri
+        example: https://images-na.ssl-images-amazon.com/images/I/51NoQTm33OL.__01_SX120_CR0,0,120,120__.jpg
+    imageWidth:
+      name: width
+      in: query
+      description: The requested width of image in pixels.
+      schema:
+        type: integer
+        default: 400
+        example: 400
+      example: 400
+    imageHeight:
+      name: height
+      in: query
+      description: The requested height of image in pixels. If `null`, the height is scaled to maintain aspect ratio based on the requested width.
+      schema:
+        type: integer
+        nullable: true
+        default: null
+        example: 600
+      examples:
+        scaleHeight:
+          summary: Scale height with width
+          value: null
+        fixedHeight:
+          summary: Force height of image
+          value: 600
+    imageFormat:
+      name: format
+      in: query
+      description: The requested output format.
+      schema:
+        type: string
+        default: jpeg
+        example: webp
+    imageRaw:
+      name: raw
+      in: query
+      description: Return the raw image without scaling if true.
+      schema:
+        type: boolean
+        default: false
+  responses: 
+    author404:
+      description: Author not found.
+      content:
+        text/html:
+          schema:
+            type: string
+            example: Not found
+tags:
+  - name: Authors
+    description: Author endpoints
diff --git a/docs/objects/Folder.yaml b/docs/objects/Folder.yaml
new file mode 100644
index 0000000000..5c477b35a7
--- /dev/null
+++ b/docs/objects/Folder.yaml
@@ -0,0 +1,21 @@
+components:
+  schemas:
+    folderId:
+      type: string
+      description: The ID of the folder.
+      format: uuid
+      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
+    folder:
+      type: object
+      description: Folder used in library
+      properties:
+        id:
+          $ref: '#/components/schemas/folderId'
+        fullPath:
+          description: The path on the server for the folder. (Read Only)
+          type: string
+          example: /podcasts
+        libraryId:
+          $ref: './Library.yaml#/components/schemas/libraryId'
+        addedAt:
+          $ref: '../schemas.yaml#/components/schemas/addedAt'
diff --git a/docs/objects/Library.yaml b/docs/objects/Library.yaml
new file mode 100644
index 0000000000..7d2a7833c9
--- /dev/null
+++ b/docs/objects/Library.yaml
@@ -0,0 +1,12 @@
+components:
+  schemas:
+    oldLibraryId:
+      type: string
+      description: The ID of the libraries created on server version 2.2.23 and before.
+      format: "lib_[a-z0-9]{18}"
+      example: lib_o78uaoeuh78h6aoeif
+    libraryId:
+      type: string
+      description: The ID of the library.
+      format: uuid
+      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
diff --git a/docs/objects/LibraryItem.yaml b/docs/objects/LibraryItem.yaml
new file mode 100644
index 0000000000..5d2f2b3dae
--- /dev/null
+++ b/docs/objects/LibraryItem.yaml
@@ -0,0 +1,66 @@
+components:
+  schemas:
+    oldLibraryItemId:
+      description: The ID of library items on server version 2.2.23 and before.
+      type: string
+      nullable: true
+      format: "li_[a-z0-9]{18}"
+      example: li_o78uaoeuh78h6aoeif
+    libraryItemId:
+      type: string
+      description: The ID of library items after 2.3.0.
+      format: uuid
+      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
+    libraryItemBase:
+      type: object
+      description: Base library item schema
+      properties:
+        id:
+          $ref: '#/components/schemas/libraryItemId'
+        oldLibraryItemId:
+          $ref: '#/components/schemas/oldLibraryItemId'
+        ino:
+          $ref: '../schemas.yaml#/components/schemas/inode'
+        libraryId:
+          $ref: './Library.yaml#/components/schemas/libraryId'
+        folderId:
+          $ref: './Folder.yaml#/components/schemas/folderId'
+        path:
+          description: The path of the library item on the server.
+          type: string
+        relPath:
+          description: The path, relative to the library folder, of the library item.
+          type: string
+        isFile:
+          description: Whether the library item is a single file in the root of the library folder.
+          type: boolean
+        mtimeMs:
+          description: The time (in ms since POSIX epoch) when the library item was last modified on disk.
+          type: integer
+        ctimeMs:
+          description: The time (in ms since POSIX epoch) when the library item status was changed on disk.
+          type: integer
+        birthtimeMs:
+          description: The time (in ms since POSIX epoch) when the library item was created on disk. Will be 0 if unknown.
+          type: integer
+        addedAt:
+          $ref: '../schemas.yaml#/components/schemas/addedAt'
+        updatedAt:
+          $ref: '../schemas.yaml#/components/schemas/updatedAt'
+        isMissing:
+          description: Whether the library item was scanned and no longer exists.
+          type: boolean
+        isInvalid:
+          description: Whether the library item was scanned and no longer has media files.
+          type: boolean
+        mediaType:
+          $ref: './mediaTypes/media.yaml#/components/schemas/mediaType'
+    libraryItemMinified:
+      type: object
+      description: A single item on the server, like a book or podcast. Minified media format.
+      allOf:
+        - $ref : '#/components/schemas/libraryItemBase'
+        - type: object
+          properties:
+            media:
+              $ref: './mediaTypes/media.yaml#/components/schemas/mediaMinified'
diff --git a/docs/objects/entities/Author.yaml b/docs/objects/entities/Author.yaml
new file mode 100644
index 0000000000..a38344d38b
--- /dev/null
+++ b/docs/objects/entities/Author.yaml
@@ -0,0 +1,104 @@
+components:
+  schemas:
+    authorId:
+      type: string
+      description: The ID of the author.
+      format: uuid
+      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
+    authorAsin:
+      type: string
+      description: The Audible identifier (ASIN) of the author. Will be null if unknown. Not the Amazon identifier.
+      nullable: true
+      example: B000APZOQA
+    authorName:
+      description: The name of the author.
+      type: string
+      example: Terry Goodkind
+    authorSeries:
+      type: object
+      description: Series and the included library items that an author has written.
+      properties:
+        id:
+          $ref: './Series.yaml#/components/schemas/seriesId'
+        name:
+          $ref: './Series.yaml#/components/schemas/seriesName'
+        items:
+          description: The items in the series. Each library item's media's metadata will have a `series` attribute, a `Series Sequence`, which is the matching series.
+          type: array
+          items:
+            ref: '../LibraryItem.yaml#/components/schemas/libraryItemMinified'
+    author:
+      description: An author object which includes a description and image path.
+      type: object
+      properties:
+        id:
+          $ref: '#/components/schemas/authorId'
+        asin:
+          $ref: '#/components/schemas/authorAsin'
+        name:
+          $ref: '#/components/schemas/authorName'
+        description:
+          description: A description of the author. Will be null if there is none.
+          type: string
+          nullable: true
+          example: |
+            Terry Goodkind is a #1 New York Times Bestselling Author and creator of the critically acclaimed masterwork,
+            ‘The Sword of Truth’. He has written 30+ major, bestselling novels, has been published in more than 20
+            languages world-wide, and has sold more than 26 Million books. ‘The Sword of Truth’ is a revered literary
+            tour de force, comprised of 17 volumes, borne from over 25 years of dedicated writing. Terry Goodkind's
+            brilliant books are character-driven stories, with a focus on the complexity of the human psyche. Goodkind
+            has an uncanny grasp for crafting compelling stories about people like you and me, trapped in terrifying
+            situations.
+        imagePath:
+          description: The absolute path for the author image located in the `metadata/` directory. Will be null if there is no image.
+          type: string
+          nullable: true
+          example: /metadata/authors/aut_bxxbyjiptmgb56yzoz.jpg
+        addedAt:
+          $ref: '../../schemas.yaml#/components/schemas/addedAt'
+        updatedAt:
+          $ref: '../../schemas.yaml#/components/schemas/updatedAt'
+    authorWithItems:
+      type: object
+      description: The author schema with an array of items they are associated with.
+      allOf:
+        - $ref: '#/components/schemas/author'
+        - type: object
+          properties:
+            libraryItems:
+              description: The items associated with the author
+              type: string
+              type: array
+              items:
+                $ref: '../LibraryItem.yaml#/components/schemas/libraryItemMinified'
+    authorWithSeries:
+      type: object
+      description: The author schema with an array of items and series they are associated with.
+      allOf:
+        - $ref: '#/components/schemas/authorWithItems'
+        - type: object
+          properties:
+            series:
+              description: The series associated with the author
+              type: array
+              items:
+                $ref: '#/components/schemas/authorSeries'
+    authorMinified:
+      type: object
+      description: Minified author object which only contains the author name and ID.
+      properties:
+        id:
+          $ref: '#/components/schemas/authorId'
+        name:
+          $ref: '#/components/schemas/authorName'
+    authorExpanded:
+      type: object
+      description: The author schema with the total number of books in the library.
+      allOf:
+        - $ref: '#/components/schemas/author'
+        - type: object
+          properties:
+            numBooks:
+              description: The number of books associated with the author in the library.
+              type: integer
+              example: 1
\ No newline at end of file
diff --git a/docs/objects/entities/Series.yaml b/docs/objects/entities/Series.yaml
new file mode 100644
index 0000000000..af818477c0
--- /dev/null
+++ b/docs/objects/entities/Series.yaml
@@ -0,0 +1,11 @@
+components:
+  schemas:
+    seriesId:
+      type: string
+      description: The ID of the series.
+      format: uuid
+      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
+    seriesName:
+      description: The name of the series.
+      type: string
+      example: Sword of Truth
\ No newline at end of file
diff --git a/docs/objects/files/AudioFile.yaml b/docs/objects/files/AudioFile.yaml
new file mode 100644
index 0000000000..06a9c80ccb
--- /dev/null
+++ b/docs/objects/files/AudioFile.yaml
@@ -0,0 +1,94 @@
+components:
+  schemas:
+    audioFile:
+      type: object
+      description: An audio file for a book. Includes audio metadata and track numbers.
+      properties:
+        index:
+          description: The index of the audio file.
+          type: integer
+          example: 1
+        ino:
+          $ref: '../../schemas.yaml#/components/schemas/inode'
+        metadata:
+          $ref: '../metadata/FileMetadata.yaml#/components/schemas/fileMetadata'
+        addedAt:
+          $ref: '../../schemas.yaml#/components/schemas/addedAt'
+        updatedAt:
+          $ref: '../../schemas.yaml#/components/schemas/updatedAt'
+        trackNumFromMeta:
+          description: The track number of the audio file as pulled from the file's metadata. Will be null if unknown.
+          type: integer
+          nullable: true
+          example: 1
+        discNumFromMeta:
+          description: The disc number of the audio file as pulled from the file's metadata. Will be null if unknown.
+          type: string
+          nullable: true
+        trackNumFromFilename:
+          description: The track number of the audio file as determined from the file's name. Will be null if unknown.
+          type: integer
+          nullable: true
+          example: 1
+        discNumFromFilename:
+          description: The disc number of the audio file as determined from the file's name. Will be null if unknown.
+          type: string
+          nullable: true
+        manuallyVerified:
+          description: Whether the audio file has been manually verified by a user.
+          type: boolean
+        invalid:
+          description: Whether the audio file is missing from the server.
+          type: boolean
+        exclude:
+          description: Whether the audio file has been marked for exclusion.
+          type: boolean
+        error:
+          description: Any error with the audio file. Will be null if there is none.
+          type: string
+          nullable: true
+        format:
+          description: The format of the audio file.
+          type: string
+          example: MP2/3 (MPEG audio layer 2/3)
+        duration:
+          $ref: '#/components/schemas/durationSec'
+        bitRate:
+          description: The bit rate (in bit/s) of the audio file.
+          type: integer
+          example: 64000
+        language:
+          description: The language of the audio file.
+          type: string
+          nullable: true
+        codec:
+          description: The codec of the audio file.
+          type: string
+          example: mp3
+        timeBase:
+          description: The time base of the audio file.
+          type: string
+          example: 1/14112000
+        channels:
+          description: The number of channels the audio file has.
+          type: integer
+          example: 2
+        channelLayout:
+          description: The layout of the audio file's channels.
+          type: string
+          example: stereo
+        chapters:
+          description: If the audio file is part of an audiobook, the chapters the file contains.
+          type: array
+          items:
+            $ref: '../metadata/BookMetadata.yaml#/components/schemas/bookChapter'
+        embeddedCoverArt:
+          description: The type of embedded cover art in the audio file. Will be null if none exists.
+          type: string
+          nullable: true
+        metaTags:
+          $ref: '../metadata/AudioMetaTags.yaml#/components/schemas/audioMetaTags'
+        mimeType:
+          description: The MIME type of the audio file.
+          type: string
+          example: audio/mpeg
diff --git a/docs/objects/mediaTypes/Book.yaml b/docs/objects/mediaTypes/Book.yaml
new file mode 100644
index 0000000000..44f3552e57
--- /dev/null
+++ b/docs/objects/mediaTypes/Book.yaml
@@ -0,0 +1,70 @@
+components:
+  schemas:
+    bookCoverPath:
+      description: The absolute path on the server of the cover file. Will be null if there is no cover.
+      type: string
+      nullable: true
+      example: /audiobooks/Terry Goodkind/Sword of Truth/Wizards First Rule/cover.jpg
+    bookBase:
+      type: object
+      description: Base book schema
+      properties:
+        libraryItemId:
+          $ref: '../LibraryItem.yaml#/components/schemas/libraryItemId'
+        coverPath:
+          $ref: '#/components/schemas/bookCoverPath'
+        tags:
+          $ref: '../../schemas.yaml#/components/schemas/tags'
+        audioFiles:
+          type: array
+          items:
+            $ref: '#/components/schemas/audioFile'
+        chapters:
+          type: array
+          items:
+            $ref: '#/components/schemas/bookChapter'
+        missingParts:
+          description: Any parts missing from the book by track index.
+          type: array
+          items:
+            type: integer
+        ebookFile:
+          $ref: '#/components/schemas/ebookFile'
+    bookMinified:
+      type: object
+      description: Minified book schema. Does not depend on `bookBase` because there's pretty much no overlap.
+      properties:
+        metadata:
+          $ref: '../metadata/BookMetadata.yaml#/components/schemas/bookMetadataMinified'
+        coverPath:
+          $ref: '#/components/schemas/bookCoverPath'
+        tags:
+          $ref: '../../schemas.yaml#/components/schemas/tags'
+        numTracks:
+          description: The number of tracks the book's audio files have.
+          type: integer
+          example: 1
+        numAudioFiles:
+          description: The number of audio files the book has.
+          type: integer
+          example: 1
+        numChapters:
+          description: The number of chapters the book has.
+          type: integer
+          example: 1
+        numMissingParts:
+          description: The total number of missing parts the book has.
+          type: integer
+          example: 0
+        numInvalidAudioFiles:
+          description: The number of invalid audio files the book has.
+          type: integer
+          example: 0
+        duration:
+          $ref: '../../schemas.yaml#/components/schemas/durationSec'
+        size:
+          $ref: '../../schemas.yaml#/components/schemas/size'
+        ebookFormat:
+          description: The format of ebook of the book. Will be null if the book is an audiobook.
+          type: string
+          nullable: true
diff --git a/docs/objects/mediaTypes/media.yaml b/docs/objects/mediaTypes/media.yaml
new file mode 100644
index 0000000000..a7d4b08eff
--- /dev/null
+++ b/docs/objects/mediaTypes/media.yaml
@@ -0,0 +1,10 @@
+components:
+  schemas:
+    mediaType:
+      type: string
+      description: The type of media, will be book or podcast.
+      enum: [book, podcast]
+    mediaMinified:
+      description: The minified media of the library item.
+      oneOf:
+        - $ref: './Book.yaml#/components/schemas/bookMinified'
diff --git a/docs/objects/metadata/AudioMetaTags.yaml b/docs/objects/metadata/AudioMetaTags.yaml
new file mode 100644
index 0000000000..ef4058a82b
--- /dev/null
+++ b/docs/objects/metadata/AudioMetaTags.yaml
@@ -0,0 +1,103 @@
+components:
+  schemas:
+    audioMetaTags:
+      description: ID3 metadata tags pulled from the audio file on import. Only non-null tags will be returned in requests.
+      type: object
+      properties:
+        tagAlbum:
+          type: string
+          nullable: true
+          example: SOT Bk01
+        tagArtist:
+          type: string
+          nullable: true
+          example: Terry Goodkind
+        tagGenre:
+          type: string
+          nullable: true
+          example: Audiobook Fantasy
+        tagTitle:
+          type: string
+          nullable: true
+          example: Wizards First Rule 01
+        tagSeries:
+          type: string
+          nullable: true
+        tagSeriesPart:
+          type: string
+          nullable: true
+        tagTrack:
+          type: string
+          nullable: true
+          example: 01/20
+        tagDisc:
+          type: string
+          nullable: true
+        tagSubtitle:
+          type: string
+          nullable: true
+        tagAlbumArtist:
+          type: string
+          nullable: true
+          example: Terry Goodkind
+        tagDate:
+          type: string
+          nullable: true
+        tagComposer:
+          type: string
+          nullable: true
+          example: Terry Goodkind
+        tagPublisher:
+          type: string
+          nullable: true
+        tagComment:
+          type: string
+          nullable: true
+        tagDescription:
+          type: string
+          nullable: true
+        tagEncoder:
+          type: string
+          nullable: true
+        tagEncodedBy:
+          type: string
+          nullable: true
+        tagIsbn:
+          type: string
+          nullable: true
+        tagLanguage:
+          type: string
+          nullable: true
+        tagASIN:
+          type: string
+          nullable: true
+        tagOverdriveMediaMarker:
+          type: string
+          nullable: true
+        tagOriginalYear:
+          type: string
+          nullable: true
+        tagReleaseCountry:
+          type: string
+          nullable: true
+        tagReleaseType:
+          type: string
+          nullable: true
+        tagReleaseStatus:
+          type: string
+          nullable: true
+        tagISRC:
+          type: string
+          nullable: true
+        tagMusicBrainzTrackId:
+          type: string
+          nullable: true
+        tagMusicBrainzAlbumId:
+          type: string
+          nullable: true
+        tagMusicBrainzAlbumArtistId:
+          type: string
+          nullable: true
+        tagMusicBrainzArtistId:
+          type: string
+          nullable: true
diff --git a/docs/objects/metadata/BookMetadata.yaml b/docs/objects/metadata/BookMetadata.yaml
new file mode 100644
index 0000000000..faa054ac3c
--- /dev/null
+++ b/docs/objects/metadata/BookMetadata.yaml
@@ -0,0 +1,126 @@
+components:
+  schemas:
+    narrators:
+      description: The narrators of the audiobook.
+      type: array
+      items:
+        type: string
+        example: Sam Tsoutsouvas
+    bookMetadataBase:
+      type: object
+      description: The base book metadata object for minified, normal, and extended schemas to inherit from.
+      properties:
+        title:
+          description: The title of the book. Will be null if unknown.
+          type: string
+          nullable: true
+          example: Wizards First Rule
+        subtitle:
+          description: The subtitle of the book. Will be null if there is no subtitle.
+          type: string
+          nullable: true
+        genres:
+          description: The genres of the book.
+          type: array
+          items:
+            type: string
+          example: ["Fantasy", "Sci-Fi", "Nonfiction: History"]
+        publishedYear:
+          description: The year the book was published. Will be null if unknown.
+          type: string
+          nullable: true
+          example: '2008'
+        publishedDate:
+          description: The date the book was published. Will be null if unknown.
+          type: string
+          nullable: true
+        publisher:
+          description: The publisher of the book. Will be null if unknown.
+          type: string
+          nullable: true
+          example: Brilliance Audio
+        description:
+          description: A description for the book. Will be null if empty.
+          type: string
+          nullable: true
+          example: >-
+              The masterpiece that started Terry Goodkind's New York Times bestselling
+              epic Sword of Truth In the aftermath of the brutal murder of his father,
+              a mysterious woman, Kahlan Amnell, appears in Richard Cypher's forest
+              sanctuary seeking help...and more. His world, his very beliefs, are
+              shattered when ancient debts come due with thundering violence. In a
+              dark age it takes courage to live, and more than mere courage to
+              challenge those who hold dominion, Richard and Kahlan must take up that
+              challenge or become the next victims. Beyond awaits a bewitching land
+              where even the best of their hearts could betray them. Yet, Richard
+              fears nothing so much as what secrets his sword might reveal about his
+              own soul. Falling in love would destroy them - for reasons Richard can't
+              imagine and Kahlan dare not say. In their darkest hour, hunted
+              relentlessly, tormented by treachery and loss, Kahlan calls upon Richard
+              to reach beyond his sword - to invoke within himself something more
+              noble. Neither knows that the rules of battle have just changed...or
+              that their time has run out. Wizard's First Rule is the beginning. One
+              book. One Rule. Witness the birth of a legend.
+        isbn:
+          description: The ISBN of the book. Will be null if unknown.
+          type: string
+          nullable: true
+        asin:
+          description: The ASIN of the book. Will be null if unknown.
+          type: string
+          nullable: true
+          example: B002V0QK4C
+        language:
+          description: The language of the book. Will be null if unknown.
+          type: string
+          nullable: true
+        explicit:
+          description: Whether the book has been marked as explicit.
+          type: boolean
+          example: false
+    bookMetadataMinified:
+      type: object
+      description: The minified metadata for a book in the database.
+      allOf:
+        - $ref : '#/components/schemas/bookMetadataBase'
+        - type: object
+          properties:
+            titleIgnorePrefix:
+              description: The title of the book with any prefix moved to the end.
+              type: string
+            authorName:
+              description: The name of the book's author(s).
+              type: string
+              example: Terry Goodkind
+            authorNameLF:
+              description: The name of the book's author(s) with last names first.
+              type: string
+              example: Goodkind, Terry
+            narratorName:
+              description: The name of the audiobook's narrator(s).
+              type: string
+              example: Sam Tsoutsouvas
+            seriesName:
+              description: The name of the book's series.
+              type: string
+              example: Sword of Truth
+    bookChapter:
+      type: object
+      description: A book chapter. Includes the title and timestamps.
+      properties:
+        id:
+          description: The ID of the book chapter.
+          type: integer
+          example: 0
+        start:
+          description: When in the book (in seconds) the chapter starts.
+          type: integer
+          example: 0
+        end:
+          description: When in the book (in seconds) the chapter ends.
+          type: number
+          example: 6004.6675
+        title:
+          description: The title of the chapter.
+          type: string
+          example: Wizards First Rule 01 Chapter 1
diff --git a/docs/objects/metadata/FileMetadata.yaml b/docs/objects/metadata/FileMetadata.yaml
new file mode 100644
index 0000000000..13e15e8bf8
--- /dev/null
+++ b/docs/objects/metadata/FileMetadata.yaml
@@ -0,0 +1,39 @@
+components:
+  schemas:
+    fileMetadata:
+      type: object
+      description: The metadata for a file, including the path, size, and unix timestamps of the file.
+      nullable: true
+      properties:
+        filename:
+          description: The filename of the file.
+          type: string
+          example: Wizards First Rule 01.mp3
+        ext:
+          description: The file extension of the file.
+          type: string
+          example: .mp3
+        path:
+          description: The absolute path on the server of the file.
+          type: string
+          example: >-
+              /audiobooks/Terry Goodkind/Sword of Truth/Wizards First Rule/Terry
+              Goodkind - SOT Bk01 - Wizards First Rule 01.mp3
+        relPath:
+          description: The path of the file, relative to the book's or podcast's folder.
+          type: string
+          example: Wizards First Rule 01.mp3
+        size:
+          $ref: '../../schemas.yaml#/components/schemas/size'
+        mtimeMs:
+          description: The time (in ms since POSIX epoch) when the file was last modified on disk.
+          type: integer
+          example: 1632223180278
+        ctimeMs:
+          description: The time (in ms since POSIX epoch) when the file status was changed on disk.
+          type: integer
+          example: 1645978261001
+        birthtimeMs:
+          description: The time (in ms since POSIX epoch) when the file was created on disk. Will be 0 if unknown.
+          type: integer
+          example: 0
diff --git a/docs/openapi.json b/docs/openapi.json
new file mode 100644
index 0000000000..93c7b27add
Binary files /dev/null and b/docs/openapi.json differ
diff --git a/docs/root.yaml b/docs/root.yaml
new file mode 100644
index 0000000000..f31ccf67c9
--- /dev/null
+++ b/docs/root.yaml
@@ -0,0 +1,157 @@
+openapi: 3.0.0
+info:
+  title: Audiobookshelf API
+  version: 0.1.0
+  description: Audiobookshelf API with autogenerated OpenAPI doc
+servers:
+  - url: http://localhost:3000
+    description: Development server
+components:
+    securitySchemes:
+        BearerAuth:
+            type: http
+            scheme: bearer
+    responses:
+        ok200:
+            description: OK
+security:
+    - BearerAuth: []
+paths:
+  /api/authors/{id}:
+    get:
+      operationId: getAuthorById
+      summary: Get a single author by ID on server
+      tags:
+        - Authors
+      parameters:
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorId'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorInclude'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorLibraryId'
+      responses:
+        200:
+          description: getAuthorById OK
+          content:
+            application/json:
+              schema:
+                oneOf:
+                  - $ref: './objects/entities/Author.yaml#/components/schemas/author'
+                  - $ref: './objects/entities/Author.yaml#/components/schemas/authorWithItems'
+                  - $ref: './objects/entities/Author.yaml#/components/schemas/authorWithSeries'
+        404:
+          $ref: './controllers/AuthorController.yaml#/components/responses/author404'
+    patch:
+      operationId: updateAuthorById
+      summary: Update a single author by ID on server. This endpoint will merge two authors if the new author name matches another author in the database.
+      tags:
+        - Authors
+      parameters:
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorId'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/asin'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorName'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorDescription'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorImagePath'
+      responses:
+        200:
+          description: updateAuthorById OK
+          content:
+            application/json:
+              schema:
+                allOf:
+                  - $ref: './objects/entities/Author.yaml#/components/schemas/author'
+                  - $ref: './controllers/AuthorController.yaml#/components/schemas/authorUpdated'
+                  - type: object
+                    properties:
+                      merged:
+                        description: Will only exist and be `true` if the author was merged with another author
+                        type: boolean
+                        nullable: true
+        404:
+          $ref: './controllers/AuthorController.yaml#/components/responses/author404'
+    delete:
+      operationId: deleteAuthorById
+      summary: Delete a single author by ID on server and remove author from all books.
+      tags:
+        - Authors
+      parameters:
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorId'
+      responses:
+        200:
+          $ref: '#/components/responses/ok200'
+        404:
+          $ref: './controllers/AuthorController.yaml#/components/responses/author404'
+  /api/authors/{id}/image:
+    post:
+      operationId: setAuthorImageById
+      summary: Set an author image using a provided URL.
+      tags:
+        - Authors
+      parameters:
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorId'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/imageUrl'
+      responses:
+        200:
+          description: setAuthorImageById OK
+          content:
+            application/json:
+              schema:
+                oneOf:
+                  - $ref: './objects/entities/Author.yaml#/components/schemas/author'
+        404:
+          $ref: './controllers/AuthorController.yaml#/components/responses/author404'
+    delete:
+      operationId: deleteAuthorImageById
+      summary: Delete an author image from the server and remove the image from the database.
+      tags:
+        - Authors
+      parameters:
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorId'
+      responses:
+        200:
+          $ref: '#/components/responses/ok200'
+        404:
+          $ref: './controllers/AuthorController.yaml#/components/responses/author404'
+    patch:
+      operationId: getAuthorImageById
+      summary: Return the author image by author ID.
+      tags:
+        - Authors
+      parameters:
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorId'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/imageWidth'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/imageHeight'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/imageFormat'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/imageRaw'
+      responses:
+        200:
+          description: getAuthorImageById OK
+          content:
+            image/*:
+              schema:
+                type: string
+                format: binary
+        404:
+          $ref: './controllers/AuthorController.yaml#/components/responses/author404'
+  /api/authors/{id}/match:
+    post:
+      operationId: matchAuthorById
+      summary: Match the author against Audible using quick match. Quick match updates the author's description and image (if no image already existed) with information from audible. Either `asin` or `q` must be provided, with `asin` taking priority if both are provided.
+      tags:
+        - Authors
+      parameters:
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorId'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/asin'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorSearchName'
+      responses:
+        200:
+          description: matchAuthorById OK
+          content:
+            application/json:
+              schema:
+                allOf:
+                  - $ref: './objects/entities/Author.yaml#/components/schemas/author'
+                  - $ref: './controllers/AuthorController.yaml#/components/schemas/authorUpdated'
+        404:
+          $ref: './controllers/AuthorController.yaml#/components/responses/author404'
+tags:
+  - name: Authors
+    description: Author endpoints
diff --git a/docs/schemas.yaml b/docs/schemas.yaml
new file mode 100644
index 0000000000..de506d4609
--- /dev/null
+++ b/docs/schemas.yaml
@@ -0,0 +1,33 @@
+components:
+  schemas:
+    addedAt:
+      type: integer
+      description: The time (in ms since POSIX epoch) when added to the server.
+      example: 1633522963509
+    createdAt:
+      type: integer
+      description: The time (in ms since POSIX epoch) when was created.
+      example: 1633522963509
+    updatedAt:
+      type: integer
+      description: The time (in ms since POSIX epoch) when last updated.
+      example: 1633522963509
+    size:
+      description: The total size (in bytes) of the item or file.
+      type: integer
+      example: 268824228
+    durationSec:
+      description: The total length (in seconds) of the item or file.
+      type: number
+      example: 33854.905
+    tags:
+      description: Tags applied to items.
+      type: array
+      items:
+        type: string
+      example: ["To Be Read", "Genre: Nonfiction"]
+    inode:
+      description: The inode of the item in the file system.
+      type: string
+      format: "[0-9]*"
+      example: '649644248522215260'
\ No newline at end of file