From baf5f7fbc3d86be9ee89ba8a220ca5987775a681 Mon Sep 17 00:00:00 2001 From: Nicholas W Date: Thu, 13 Jun 2024 15:09:02 -0700 Subject: [PATCH] Initial library endpoints (#3012) * Fix: extra type in `Author.yaml` * Fix: formatting * Initial library schema * Additional debugging * Fix: spec relative paths * Add: ebook file spec * Fix: response type should be string * Linting updates * Add: missing librarySettings * Temporary fix: Library cron can be null or false * Author controller updates * Add: `/api/libraries/{id}` endpoint * Update library responses * Add: descriptions * Fix: queries should be in body * Fix: `body` should be `requestBody` * Move: `libraryController` paths, clean up `requestBody` * Clean up libraryController parameters * Move: author endpoints to controller * Add `get` for author images * Simplify author schema with items * Remove: unused response type * Update: formatting * Update json * Update requestBody on LibraryController * LibrarySettings update * Replace: generic parameter with path specific parameter * Fix: requestBody descriptions * Fix: match post operation * Temporary: nullable Author schemas * LibraryController items endpoint * Add: delete library items with issues * Massive cleanup and violation fixing * Update bundled spec * Add: remove library items with issues * Add: library items endpoint * Fix: errors * Fix: base schemas * Add: series schemas * Add: library series endpoint * Fix: oneOf and array issues * Add: author search region for matching * Add: series endpoints * Fix: series issues * Add library series endpoint and update deprecation * Fix: series endpoint deprecation * Fix: `name` in `sortDesc` schema * Add: workflow for linting spec * Update OpenAPI readme --- .github/workflows/lint-openapi.yml | 30 ++ docs/README.md | 37 +- docs/controllers/AuthorController.yaml | 377 ++++++++++++++------ docs/controllers/LibraryController.yaml | 435 ++++++++++++++++++++++++ docs/controllers/SeriesController.yaml | 72 ++++ docs/objects/Library.yaml | 92 ++++- docs/objects/LibraryItem.yaml | 10 +- docs/objects/entities/Author.yaml | 70 ++-- docs/objects/entities/Series.yaml | 108 +++++- docs/objects/files/AudioFile.yaml | 2 +- docs/objects/files/EBookFile.yaml | 17 + docs/objects/mediaTypes/Book.yaml | 6 +- docs/openapi.json | Bin 75826 -> 63853 bytes docs/root.yaml | 169 ++------- docs/schemas.yaml | 35 +- 15 files changed, 1135 insertions(+), 325 deletions(-) create mode 100644 .github/workflows/lint-openapi.yml create mode 100644 docs/controllers/LibraryController.yaml create mode 100644 docs/controllers/SeriesController.yaml create mode 100644 docs/objects/files/EBookFile.yaml diff --git a/.github/workflows/lint-openapi.yml b/.github/workflows/lint-openapi.yml new file mode 100644 index 0000000000..3c6072d8dc --- /dev/null +++ b/.github/workflows/lint-openapi.yml @@ -0,0 +1,30 @@ +name: API linting + +# Run on pull requests or pushes when there is a change to the OpenAPI file +on: + push: + paths: + - docs/ + pull_request: + paths: + - docs/ + +jobs: + build: + runs-on: ubuntu-latest + steps: + # Check out the repository + - name: Checkout + uses: actions/checkout@v4 + # Set up node to run the javascript + - name: Set up node + uses: actions/setup-node@v4 + # Install Redocly CLI + - name: Install Redocly CLI + run: npm install -g @redocly/cli@latest + # Perform linting for exploded spec + - name: Run linting for exploded spec + run: redocly lint docs/root.yaml --format=github-actions + # Perform linting for bundled spec + - name: Run linting for bundled spec + run: redocly lint docs/openapi.json --format=github-actions diff --git a/docs/README.md b/docs/README.md index c6f278edf7..14b1aba25f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,30 +1,33 @@ # 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`. +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/). +The spec is linted and bundled using [`redocly-cli`](https://redocly.com/docs/cli/). This tool also generates HTML docs for the spec. + +The tools created by [`pb33f`](https://pb33f.io/), specifically `vacuum` and `wiretap`, are also useful for linting and verification. These tools check for some other things, such as validating requests to and responses from the server. ### 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 used to bundle the spec into a `yaml` file is `redocly bundle root.yaml > bundled.yaml`. + +The `yq` tool is used to convert the `yaml` to a `json` using the `yq -p yaml -o json bundled.yaml > openapi.json`. + +### Linting the spec -The command to convert the spec using `yq` is `yq -p yaml -o json openapi.yaml > openapi.json`. +The command used to lint the spec is `redocly lint root.yaml` -### 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. +To generate an HTML report using `vacuum`, you can use `vacuum html-report [file]` to generate `report.html` and view the report in your browser. + +### Generating documentation + +Redocly allows for creating a static HTML page to document the API. This is done by using `redocly build-docs [file]` and supports exploded specs. ### Putting it all together -The full command that I run to bundle the spec and generate the report is: + +The full command that I run to bundle the spec and generate the documentation is: ``` -vacuum bundle root.yaml openapi.yaml && \ -yq -p yaml -o json openapi.yaml > openapi.json && \ -vacuum html-report openapi.json +redocly bundle root.yaml > bundled.yaml && \ +yq -p yaml -o json bundled.yaml > openapi.json && \ +redocly build-docs openapi.json ``` diff --git a/docs/controllers/AuthorController.yaml b/docs/controllers/AuthorController.yaml index 2af95a62f8..1106a0892e 100644 --- a/docs/controllers/AuthorController.yaml +++ b/docs/controllers/AuthorController.yaml @@ -4,136 +4,291 @@ components: 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' + authorMerged: + description: Whether the author was merged with another author. Will not exist if author was updated. + type: boolean + nullable: true 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" + 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. For example, the value `items,series` will include both library items and series. + type: string + example: 'items' 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' + $ref: '../objects/Library.yaml#/components/schemas/libraryId' authorSearchName: - name: q - in: query description: The name of the author to use for searching. - required: false - schema: - type: string - example: Terry Goodkind + 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 + $ref: '../objects/entities/Author.yaml#/components/schemas/authorName' 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 + 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 + type: integer + default: 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 + type: integer + nullable: true + default: null + example: 600 imageFormat: - name: format - in: query description: The requested output format. - schema: - type: string - default: jpeg - example: webp + 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: + type: boolean + default: false + responses: + author200: + description: Author found. + content: + application/json: + schema: + $ref: '../objects/entities/Author.yaml#/components/schemas/author' author404: description: Author not found. content: text/html: schema: type: string - example: Not found -tags: - - name: Authors - description: Author endpoints + example: Author not found. +paths: + /api/authors/{id}: + parameters: + - name: id + in: path + description: Author ID + required: true + schema: + $ref: '../objects/entities/Author.yaml#/components/schemas/authorId' + get: + operationId: getAuthorById + summary: Get an author by ID + description: Get an author by ID. The author's books and series can be included in the response. + tags: + - Authors + requestBody: + required: false + description: The author object to create. + content: + application/json: + schema: + properties: + include: + $ref: '#/components/schemas/authorInclude' + library: + $ref: '#/components/schemas/authorLibraryId' + responses: + '200': + description: getAuthorById OK + content: + application/json: + schema: + $ref: '../objects/entities/Author.yaml#/components/schemas/author' + '404': + $ref: '#/components/responses/author404' + patch: + operationId: updateAuthorById + summary: Update an author by ID + description: Update an author by ID. The author's name and description can be updated. This endpoint will merge two authors if the new author name matches another author name in the database. + tags: + - Authors + requestBody: + description: The author object to update. + content: + application/json: + schema: + properties: + name: + $ref: '#/components/schemas/authorName' + description: + $ref: '../objects/entities/Author.yaml#/components/schemas/authorDescription' + imagePath: + $ref: '../objects/entities/Author.yaml#/components/schemas/authorImagePath' + asin: + $ref: '../objects/entities/Author.yaml#/components/schemas/authorAsin' + responses: + '200': + description: updateAuthorById OK + content: + application/json: + schema: + oneOf: + - $ref: '../objects/entities/Author.yaml#/components/schemas/author' + - $ref: '#/components/schemas/authorUpdated' + - $ref: '#/components/schemas/authorMerged' + '404': + $ref: '#/components/responses/author404' + delete: + operationId: deleteAuthorById + summary: Delete an author by ID + description: Delete an author by ID. This will remove the author from all books. + tags: + - Authors + responses: + '200': + description: deleteAuthorById OK + content: + text/plain: + schema: + type: string + example: Author deleted. + '404': + $ref: '#/components/responses/author404' + /api/authors/{id}/image: + parameters: + - name: id + in: path + description: Author ID + required: true + schema: + $ref: '../objects/entities/Author.yaml#/components/schemas/authorId' + get: + operationId: getAuthorImageById + summary: Get an author image by author ID + description: Get an author image by author ID. The image will be returned in the requested format and size. + tags: + - Authors + requestBody: + required: false + description: The author image to get. + content: + application/json: + schema: + properties: + width: + $ref: '#/components/schemas/imageWidth' + height: + $ref: '#/components/schemas/imageHeight' + format: + $ref: '#/components/schemas/imageFormat' + raw: + $ref: '#/components/schemas/imageRaw' + responses: + '200': + description: getAuthorImageById OK + content: + image/webp: + schema: + type: string + format: binary + image/jpeg: + schema: + type: string + format: binary + image/*: + schema: + type: string + format: binary + '404': + $ref: '#/components/responses/author404' + post: + operationId: addAuthorImageById + summary: Add an author image to the server + description: Add an author image to the server. The image will be downloaded from the provided URL and stored on the server. + tags: + - Authors + requestBody: + required: true + description: The author image to add by URL. + content: + application/json: + schema: + $ref: '#/components/schemas/imageUrl' + responses: + '200': + description: addAuthorImageById OK + content: + image/*: + schema: + type: string + format: binary + '404': + $ref: '#/components/responses/author404' + patch: + operationId: updateAuthorImageById + summary: Update an author image by author ID + description: Update an author image by author ID. The image will be resized if the width, height, or format is provided. + tags: + - Authors + requestBody: + description: The author image to update. + content: + application/json: + schema: + properties: + width: + $ref: '#/components/schemas/imageWidth' + height: + $ref: '#/components/schemas/imageHeight' + format: + $ref: '#/components/schemas/imageFormat' + raw: + $ref: '#/components/schemas/imageRaw' + responses: + '200': + description: updateAuthorImageById OK + content: + image/*: + schema: + type: string + format: binary + '404': + $ref: '#/components/responses/author404' + delete: + operationId: deleteAuthorImageById + summary: Delete an author image by author ID + description: Delete an author image by author ID. This will remove the image from the server and the database. + tags: + - Authors + responses: + '200': + description: deleteAuthorImageById OK + '404': + $ref: '#/components/responses/author404' + /api/authors/{id}/match: + parameters: + - name: id + in: path + description: Author ID + required: true + schema: + $ref: '../objects/entities/Author.yaml#/components/schemas/authorId' + post: + operationId: matchAuthorById + summary: Match the author against Audible using quick match + description: 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 + requestBody: + required: true + description: The author object to match against an online provider. + content: + application/json: + schema: + type: object + properties: + q: + $ref: '#/components/schemas/authorSearchName' + asin: + $ref: '../objects/entities/Author.yaml#/components/schemas/authorAsin' + region: + $ref: '../schemas.yaml#/components/schemas/region' + responses: + '200': + description: matchAuthorById OK + content: + application/json: + schema: + oneOf: + - $ref: '../objects/entities/Author.yaml#/components/schemas/author' + - $ref: '#/components/schemas/authorUpdated' + '404': + $ref: '#/components/responses/author404' diff --git a/docs/controllers/LibraryController.yaml b/docs/controllers/LibraryController.yaml new file mode 100644 index 0000000000..8963b48fe1 --- /dev/null +++ b/docs/controllers/LibraryController.yaml @@ -0,0 +1,435 @@ +components: + schemas: + sortBy: + type: string + description: The field to sort by from the request. + example: 'media.metadata.title' + sortDesc: + description: Whether to sort in descending order. + type: boolean + example: true + filterBy: + type: string + description: The field to filter by from the request. TODO + example: 'media.metadata.title' + collapseSeries: + type: boolean + description: Whether collapse series was set in the request. + example: true + libraryFolders: + description: The folders of the library. Only specify the fullPath. + type: array + items: + $ref: '../objects/Folder.yaml#/components/schemas/folder' + libraryDisplayOrder: + description: The display order of the library. Must be >= 1. + type: integer + minimum: 1 + example: 1 + libraryIcon: + description: The icon of the library. See Library Icons for a list of possible icons. + type: string + example: 'audiobookshelf' + libraryMediaType: + description: The type of media that the library contains. Must be `book` or `podcast`. + type: string + example: 'book' + libraryProvider: + description: Preferred metadata provider for the library. See Metadata Providers for a list of possible providers. + type: string + example: 'audible' + librarySettings: + $ref: '../objects/Library.yaml#/components/schemas/librarySettings' + librarySort: + description: The sort order of the library. For example, to sort by title use 'sort=media.metadata.title'. + type: string + example: 'media.metadata.title' + libraryFilter: + description: The filter for the library. + type: string + example: 'media.metadata.title' + libraryCollapseSeries: + description: Whether to collapse series. + type: boolean + example: true + default: false + libraryInclude: + description: The fields to include in the response. The only current option is `rssfeed`. + type: string + example: 'rssfeed' + responses: + library200: + description: Library found. + content: + application/json: + schema: + $ref: '../objects/Library.yaml#/components/schemas/library' + library404: + description: Library not found. + content: + text/html: + schema: + type: string + example: Library not found. +paths: + /api/libraries: + get: + operationId: getLibraries + summary: Get all libraries on server + description: Get all libraries on server. + tags: + - Libraries + responses: + '200': + description: getLibraries OK + content: + application/json: + schema: + type: array + items: + $ref: '../objects/Library.yaml#/components/schemas/library' + post: + operationId: createLibrary + summary: Create a new library on server + description: Create a new library on server. + tags: + - Libraries + requestBody: + description: The library object to create. + content: + application/json: + schema: + type: object + required: [name, folders] + properties: + name: + $ref: '../objects/Library.yaml#/components/schemas/libraryName' + folders: + $ref: '#/components/schemas/libraryFolders' + displayOrder: + $ref: '#/components/schemas/libraryDisplayOrder' + icon: + $ref: '#/components/schemas/libraryIcon' + mediaType: + $ref: '#/components/schemas/libraryMediaType' + provider: + $ref: '#/components/schemas/libraryProvider' + settings: + $ref: '#/components/schemas/librarySettings' + responses: + '200': + $ref: '#/components/responses/library200' + '404': + $ref: '#/components/responses/library404' + /api/libraries/{id}: + parameters: + - name: id + in: path + description: The ID of the library. + required: true + schema: + $ref: '../objects/Library.yaml#/components/schemas/libraryId' + get: + operationId: getLibraryById + summary: Get a single library by ID on server + description: Get a single library by ID on server. + tags: + - Libraries + responses: + '200': + $ref: '#/components/responses/library200' + '404': + $ref: '#/components/responses/library404' + patch: + operationId: updateLibraryById + summary: Update a single library by ID on server + description: Update a single library by ID on server. + tags: + - Libraries + requestBody: + required: true + description: The library object to update. + content: + application/json: + schema: + type: object + properties: + name: + $ref: '../objects/Library.yaml#/components/schemas/libraryName' + folders: + $ref: '#/components/schemas/libraryFolders' + displayOrder: + $ref: '#/components/schemas/libraryDisplayOrder' + icon: + $ref: '#/components/schemas/libraryIcon' + mediaType: + $ref: '#/components/schemas/libraryMediaType' + provider: + $ref: '#/components/schemas/libraryProvider' + settings: + $ref: '#/components/schemas/librarySettings' + responses: + '200': + $ref: '#/components/responses/library200' + '404': + $ref: '#/components/responses/library404' + delete: + operationId: deleteLibraryById + summary: Delete a single library by ID on server + description: Delete a single library by ID on server and return the deleted object. + tags: + - Libraries + responses: + '200': + $ref: '#/components/responses/library200' + '404': + $ref: '#/components/responses/library404' + /api/libraries/{id}/issues: + parameters: + - name: id + in: path + description: The ID of the library. + required: true + schema: + $ref: '../objects/Library.yaml#/components/schemas/libraryId' + delete: + operationId: deleteLibraryIssues + summary: Delete items with issues in a library. + description: Delete all items with issues in a library by library ID on the server. This only removes the items from the ABS database and does not delete media files. + tags: + - Libraries + responses: + '200': + description: deleteLibraryIssues OK + content: + application/json: + schema: + type: string + example: 'Issues deleted.' + '404': + $ref: '#/components/responses/library404' + /api/libraries/{id}/items: + parameters: + - name: id + in: path + description: The ID of the library. + required: true + schema: + $ref: '../objects/Library.yaml#/components/schemas/libraryId' + get: + operationId: getLibraryItems + summary: Get items in a library + description: Get items in a library by ID on server. + tags: + - Libraries + requestBody: + required: false + description: The filters to apply to the requested library items. + content: + application/json: + schema: + type: object + properties: + limit: + $ref: '../schemas.yaml#/components/schemas/limit' + page: + $ref: '../schemas.yaml#/components/schemas/page' + sort: + $ref: '#/components/schemas/librarySort' + desc: + $ref: '../schemas.yaml#/components/schemas/sortDesc' + filter: + $ref: '#/components/schemas/libraryFilter' + minified: + $ref: '../schemas.yaml#/components/schemas/minified' + collapseSeries: + $ref: '#/components/schemas/libraryCollapseSeries' + include: + $ref: '#/components/schemas/libraryInclude' + responses: + '200': + description: getLibraryItems OK + content: + application/json: + schema: + type: object + properties: + results: + type: array + items: + $ref: '../objects/LibraryItem.yaml#/components/schemas/libraryItemBase' + total: + $ref: '../schemas.yaml#/components/schemas/total' + limit: + $ref: '../schemas.yaml#/components/schemas/limit' + page: + $ref: '../schemas.yaml#/components/schemas/page' + sortBy: + $ref: '#/components/schemas/sortBy' + sortDesc: + $ref: '#/components/schemas/sortDesc' + filterBy: + $ref: '#/components/schemas/filterBy' + mediaType: + $ref: '../objects/mediaTypes/media.yaml#/components/schemas/mediaType' + minified: + $ref: '../schemas.yaml#/components/schemas/minified' + collapseSeries: + $ref: '#/components/schemas/collapseSeries' + include: + $ref: '#/components/schemas/libraryInclude' + '404': + $ref: '#/components/responses/library404' + /api/libraries/{id}/authors: + parameters: + - name: id + in: path + description: The ID of the library. + required: true + schema: + $ref: '../objects/Library.yaml#/components/schemas/libraryId' + get: + operationId: getLibraryAuthors + summary: Get all authors in a library + description: Get all authors in a library by ID on server. + tags: + - Libraries + responses: + '200': + description: getLibraryAuthors OK + content: + application/json: + schema: + type: object + properties: + authors: + type: array + items: + $ref: '../objects/entities/Author.yaml#/components/schemas/authorExpanded' + '404': + $ref: '#/components/responses/library404' + /api/libraries/{id}/series: + parameters: + - name: id + in: path + description: The ID of the library. + required: true + schema: + $ref: '../objects/Library.yaml#/components/schemas/libraryId' + get: + operationId: getLibrarySeries + summary: Get library series + description: Get series in a library. Filtering and sorting can be applied. + tags: + - Libraries + requestBody: + required: false + description: The filters to apply to the requested library series. + content: + application/json: + schema: + type: object + properties: + limit: + $ref: '../schemas.yaml#/components/schemas/limit' + page: + $ref: '../schemas.yaml#/components/schemas/page' + sort: + description: The field to sort by from the request. + type: string + enum: ['name', 'numBooks', 'totalDuration', 'addedAt', 'lastBookAdded', 'lastBookUpdated'] + example: 'numBooks' + default: 'name' + desc: + $ref: '../schemas.yaml#/components/schemas/sortDesc' + filter: + $ref: '#/components/schemas/libraryFilter' + include: + $ref: '#/components/schemas/libraryInclude' + responses: + '200': + description: getLibrarySeries OK + content: + application/json: + schema: + type: object + properties: + results: + type: array + items: + $ref: '../objects/entities/Series.yaml#/components/schemas/seriesBooks' + total: + $ref: '../schemas.yaml#/components/schemas/total' + limit: + $ref: '../schemas.yaml#/components/schemas/limit' + page: + $ref: '../schemas.yaml#/components/schemas/page' + sortBy: + $ref: '#/components/schemas/sortBy' + sortDesc: + $ref: '#/components/schemas/sortDesc' + filterBy: + $ref: '#/components/schemas/filterBy' + minified: + $ref: '../schemas.yaml#/components/schemas/minified' + include: + $ref: '#/components/schemas/libraryInclude' + + '404': + $ref: '#/components/responses/library404' + /api/libraries/{id}/series/{seriesId}: + parameters: + - name: id + in: path + description: The ID of the library. + required: true + schema: + $ref: '../objects/Library.yaml#/components/schemas/libraryId' + - name: seriesId + in: path + description: The ID of the series. + required: true + schema: + $ref: '../objects/entities/Series.yaml#/components/schemas/seriesId' + get: + operationId: getLibrarySeriesById + summary: Get single series in library + description: Get a single series in a library by ID on server. This endpoint is deprecated and `/api/series/{id}` should be used instead. + deprecated: true + tags: + - Libraries + requestBody: + required: false + description: The filters to apply to the requested library series. + content: + application/json: + schema: + type: object + properties: + limit: + $ref: '../schemas.yaml#/components/schemas/limit' + page: + $ref: '../schemas.yaml#/components/schemas/page' + sort: + description: The field to sort by from the request. + type: string + enum: ['name', 'numBooks', 'totalDuration', 'addedAt', 'lastBookAdded', 'lastBookUpdated'] + example: 'numBooks' + default: 'name' + desc: + $ref: '../schemas.yaml#/components/schemas/sortDesc' + filter: + $ref: '#/components/schemas/libraryFilter' + minified: + $ref: '../schemas.yaml#/components/schemas/minified' + include: + $ref: '#/components/schemas/libraryInclude' + responses: + '200': + description: getLibrarySeriesById OK + content: + application/json: + schema: + $ref: '../objects/entities/Series.yaml#/components/schemas/seriesWithProgressAndRSS' + '404': + $ref: '#/components/responses/library404' diff --git a/docs/controllers/SeriesController.yaml b/docs/controllers/SeriesController.yaml new file mode 100644 index 0000000000..1fd999ed4d --- /dev/null +++ b/docs/controllers/SeriesController.yaml @@ -0,0 +1,72 @@ +components: + responses: + series404: + description: Series not found. + content: + text/html: + schema: + type: string + example: Series not found. +paths: + /api/series/{id}: + parameters: + - name: id + in: path + description: The ID of the series. + required: true + schema: + $ref: '../objects/entities/Series.yaml#/components/schemas/seriesId' + get: + operationId: getSeries + tags: + - Series + summary: Get series + description: Get a series by ID. + requestBody: + required: false + description: A comma separated list of what to include with the series. + content: + application/json: + schema: + type: object + properties: + include: + type: string + description: A comma separated list of what to include with the series. + example: 'progress,rssfeed' + enum: ['progress', 'rssfeed', 'progress,rssfeed', 'rssfeed,progress'] + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../objects/entities/Series.yaml#/components/schemas/seriesWithProgressAndRSS' + '404': + $ref: '#/components/responses/series404' + patch: + operationId: updateSeries + tags: + - Series + summary: Update series + description: Update a series by ID. + requestBody: + required: true + description: The series to update. + content: + application/json: + schema: + properties: + name: + $ref: '../objects/entities/Series.yaml#/components/schemas/seriesName' + description: + $ref: '../objects/entities/Series.yaml#/components/schemas/seriesDescription' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../objects/entities/Series.yaml#/components/schemas/series' + '404': + $ref: '#/components/responses/series404' diff --git a/docs/objects/Library.yaml b/docs/objects/Library.yaml index 7d2a7833c9..c7a2a52e00 100644 --- a/docs/objects/Library.yaml +++ b/docs/objects/Library.yaml @@ -3,10 +3,96 @@ components: 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 + 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 + example: 'e4bb1afb-4a4f-4dd6-8be0-e615d233185b' + libraryName: + description: The name of the library. + type: string + example: My Audiobooks + librarySettings: + description: The settings for the library. + type: object + properties: + coverAspectRatio: + description: Whether the library should use square book covers. Must be 0 (for false) or 1 (for true). + type: integer + example: 1 + disableWatcher: + description: Whether to disable the folder watcher for the library. + type: boolean + example: false + skipMatchingMediaWithAsin: + description: Whether to skip matching books that already have an ASIN. + type: boolean + example: false + skipMatchingMediaWithIsbn: + description: Whether to skip matching books that already have an ISBN. + type: boolean + example: false + autoScanCronExpression: + description: The cron expression for when to automatically scan the library folders. If null, automatic scanning will be disabled. + type: string + nullable: true + example: '0 0 0 * * *' + audiobooksOnly: + description: Whether the library should ignore ebook files and only allow ebook files to be supplementary. + type: boolean + example: false + hideSingleBookSeries: + description: Whether to hide series with only one book. + type: boolean + example: false + onlyShowLaterBooksInContinueSeries: + description: Whether to only show books in a series after the highest series sequence. + type: boolean + example: false + metadataPrecedence: + description: The precedence of metadata sources. See Metadata Providers for a list of possible providers. + type: array + items: + type: string + example: ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata'] + podcastSearchRegion: + description: The region to use when searching for podcasts. + type: string + example: 'us' + library: + description: A library object which includes either books or podcasts. + type: object + properties: + id: + $ref: '#/components/schemas/libraryId' + name: + $ref: '#/components/schemas/libraryName' + folders: + description: The folders that belong to the library. + type: array + items: + $ref: './Folder.yaml#/components/schemas/folder' + displayOrder: + description: Display position of the library in the list of libraries. Must be >= 1. + type: integer + example: 1 + icon: + description: The selected icon for the library. See Library Icons for a list of possible icons. + type: string + example: 'audiobookshelf' + mediaType: + description: The type of media that the library contains. Will be `book` or `podcast`. (Read Only) + type: string + example: 'book' + provider: + description: Preferred metadata provider for the library. See Metadata Providers for a list of possible providers. + type: string + example: 'audible' + settings: + $ref: '#/components/schemas/librarySettings' + createdAt: + $ref: '../schemas.yaml#/components/schemas/createdAt' + lastUpdate: + $ref: '../schemas.yaml#/components/schemas/updatedAt' diff --git a/docs/objects/LibraryItem.yaml b/docs/objects/LibraryItem.yaml index 5d2f2b3dae..0b85310d0c 100644 --- a/docs/objects/LibraryItem.yaml +++ b/docs/objects/LibraryItem.yaml @@ -4,7 +4,7 @@ components: description: The ID of library items on server version 2.2.23 and before. type: string nullable: true - format: "li_[a-z0-9]{18}" + format: 'li_[a-z0-9]{18}' example: li_o78uaoeuh78h6aoeif libraryItemId: type: string @@ -59,8 +59,14 @@ components: type: object description: A single item on the server, like a book or podcast. Minified media format. allOf: - - $ref : '#/components/schemas/libraryItemBase' + - $ref: '#/components/schemas/libraryItemBase' - type: object properties: media: $ref: './mediaTypes/media.yaml#/components/schemas/mediaMinified' + libraryItemSequence: + type: object + description: A single item on the server, like a book or podcast. Includes series sequence information. + allOf: + - $ref: '#/components/schemas/libraryItemBase' + - $ref: './entities/Series.yaml#/components/schemas/sequence' diff --git a/docs/objects/entities/Author.yaml b/docs/objects/entities/Author.yaml index a38344d38b..838e5b517d 100644 --- a/docs/objects/entities/Author.yaml +++ b/docs/objects/entities/Author.yaml @@ -6,14 +6,24 @@ components: 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. + type: string nullable: true example: B000APZOQA authorName: description: The name of the author. type: string example: Terry Goodkind + authorDescription: + description: The new description of the author. + 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: + description: The absolute path for the author image. This will be in the `metadata/` directory. Will be null if there is no image. + type: string + nullable: true + example: /metadata/authors/aut_z3leimgybl7uf3y4ab.jpg authorSeries: type: object description: Series and the included library items that an author has written. @@ -26,9 +36,9 @@ components: 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' + $ref: '../LibraryItem.yaml#/components/schemas/libraryItemMinified' author: - description: An author object which includes a description and image path. + description: An author object which includes a description and image path. The library items and series associated with the author are optionally included. type: object properties: id: @@ -38,51 +48,23 @@ components: 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. + $ref: '#/components/schemas/authorDescription' 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 + $ref: '#/components/schemas/authorImagePath' 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' + libraryItems: + description: The items associated with the author + type: array + items: + $ref: '../LibraryItem.yaml#/components/schemas/libraryItemMinified' + 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. @@ -101,4 +83,4 @@ components: numBooks: description: The number of books associated with the author in the library. type: integer - example: 1 \ No newline at end of file + example: 1 diff --git a/docs/objects/entities/Series.yaml b/docs/objects/entities/Series.yaml index af818477c0..ef35a5b354 100644 --- a/docs/objects/entities/Series.yaml +++ b/docs/objects/entities/Series.yaml @@ -8,4 +8,110 @@ components: seriesName: description: The name of the series. type: string - example: Sword of Truth \ No newline at end of file + example: Sword of Truth + seriesDescription: + description: A description for the series. Will be null if there is none. + type: string + nullable: true + example: The Sword of Truth is a series of twenty one epic fantasy novels written by Terry Goodkind. + sequence: + description: The position in the series the book is. + type: string + nullable: true + seriesProgress: + type: object + description: The user's progress of a series. + properties: + libraryItemIds: + description: The IDs of the library items in the series. + type: array + items: + $ref: '../LibraryItem.yaml#/components/schemas/libraryItemId' + libraryItemIdsFinished: + description: The IDs of the library items in the series that are finished. + type: array + items: + $ref: '../LibraryItem.yaml#/components/schemas/libraryItemId' + isFinished: + description: Whether the series is finished. + type: boolean + series: + type: object + description: A series object which includes the name and description of the series. + properties: + id: + $ref: '#/components/schemas/seriesId' + name: + $ref: '#/components/schemas/seriesName' + description: + $ref: '#/components/schemas/seriesDescription' + addedAt: + $ref: '../../schemas.yaml#/components/schemas/addedAt' + updatedAt: + $ref: '../../schemas.yaml#/components/schemas/updatedAt' + seriesNumBooks: + type: object + description: A series object which includes the name and number of books in the series. + properties: + id: + $ref: '#/components/schemas/seriesId' + name: + $ref: '#/components/schemas/seriesName' + numBooks: + description: The number of books in the series. + type: integer + libraryItemIds: + description: The IDs of the library items in the series. + type: array + items: + $ref: '../LibraryItem.yaml#/components/schemas/libraryItemId' + seriesBooks: + type: object + description: A series object which includes the name and books in the series. + properties: + id: + $ref: '#/components/schemas/seriesId' + name: + $ref: '#/components/schemas/seriesName' + addedAt: + $ref: '../../schemas.yaml#/components/schemas/addedAt' + nameIgnorePrefix: + description: The name of the series with any prefix moved to the end. + type: string + nameIgnorePrefixSort: + description: The name of the series with any prefix removed. + type: string + type: + description: Will always be `series`. + type: string + books: + description: The library items that contain the books in the series. A sequence attribute that denotes the position in the series the book is in, is tacked on. + type: array + items: + $ref: '../LibraryItem.yaml#/components/schemas/libraryItemSequence' + totalDuration: + description: The combined duration (in seconds) of all books in the series. + type: number + seriesSequence: + type: object + description: A series object which includes the name and sequence of the series. + properties: + id: + $ref: '#/components/schemas/seriesId' + name: + $ref: '#/components/schemas/seriesName' + sequence: + $ref: '#/components/schemas/sequence' + seriesWithProgressAndRSS: + type: object + description: A series object which includes the name and progress of the series. + oneOf: + - $ref: '#/components/schemas/series' + - type: object + properties: + progress: + $ref: '#/components/schemas/seriesProgress' + rssFeed: + description: The RSS feed for the series. + type: string + example: 'TBD' diff --git a/docs/objects/files/AudioFile.yaml b/docs/objects/files/AudioFile.yaml index 06a9c80ccb..20994438fa 100644 --- a/docs/objects/files/AudioFile.yaml +++ b/docs/objects/files/AudioFile.yaml @@ -52,7 +52,7 @@ components: type: string example: MP2/3 (MPEG audio layer 2/3) duration: - $ref: '#/components/schemas/durationSec' + $ref: '../../schemas.yaml#/components/schemas/durationSec' bitRate: description: The bit rate (in bit/s) of the audio file. type: integer diff --git a/docs/objects/files/EBookFile.yaml b/docs/objects/files/EBookFile.yaml new file mode 100644 index 0000000000..50dbb44f96 --- /dev/null +++ b/docs/objects/files/EBookFile.yaml @@ -0,0 +1,17 @@ +components: + schemas: + ebookFile: + type: object + properties: + ino: + $ref: '../../schemas.yaml#/components/schemas/inode' + metadata: + $ref: '../metadata/FileMetadata.yaml#/components/schemas/fileMetadata' + ebookFormat: + description: The ebook format of the ebook file. + type: string + example: epub + addedAt: + $ref: '../../schemas.yaml#/components/schemas/addedAt' + updatedAt: + $ref: '../../schemas.yaml#/components/schemas/updatedAt' diff --git a/docs/objects/mediaTypes/Book.yaml b/docs/objects/mediaTypes/Book.yaml index 44f3552e57..ebe2b63d9d 100644 --- a/docs/objects/mediaTypes/Book.yaml +++ b/docs/objects/mediaTypes/Book.yaml @@ -18,18 +18,18 @@ components: audioFiles: type: array items: - $ref: '#/components/schemas/audioFile' + $ref: '../files/AudioFile.yaml#/components/schemas/audioFile' chapters: type: array items: - $ref: '#/components/schemas/bookChapter' + $ref: '../metadata/BookMetadata.yaml#/components/schemas/bookChapter' missingParts: description: Any parts missing from the book by track index. type: array items: type: integer ebookFile: - $ref: '#/components/schemas/ebookFile' + $ref: '../files/EBookFile.yaml#/components/schemas/ebookFile' bookMinified: type: object description: Minified book schema. Does not depend on `bookBase` because there's pretty much no overlap. diff --git a/docs/openapi.json b/docs/openapi.json index 93c7b27adde742fbd558d44dd7fd2b64b7b2d03a..57ba2fd3c46c99b8fafaf47bb50a98fb84e4e620 100644 GIT binary patch literal 63853 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 literal 75826 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 diff --git a/docs/root.yaml b/docs/root.yaml index f31ccf67c9..3c40f8dc49 100644 --- a/docs/root.yaml +++ b/docs/root.yaml @@ -7,151 +7,40 @@ servers: - url: http://localhost:3000 description: Development server components: - securitySchemes: - BearerAuth: - type: http - scheme: bearer - responses: - ok200: - description: OK + securitySchemes: + BearerAuth: + description: Bearer authentication + type: http + scheme: bearer security: - - BearerAuth: [] + - 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' + $ref: './controllers/AuthorController.yaml#/paths/~1api~1authors~1{id}' /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' + $ref: './controllers/AuthorController.yaml#/paths/~1api~1authors~1{id}~1image' /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' + $ref: './controllers/AuthorController.yaml#/paths/~1api~1authors~1{id}~1match' + /api/libraries: + $ref: './controllers/LibraryController.yaml#/paths/~1api~1libraries' + /api/libraries/{id}: + $ref: './controllers/LibraryController.yaml#/paths/~1api~1libraries~1{id}' + /api/libraries/{id}/authors: + $ref: './controllers/LibraryController.yaml#/paths/~1api~1libraries~1{id}~1authors' + /api/libraries/{id}/items: + $ref: './controllers/LibraryController.yaml#/paths/~1api~1libraries~1{id}~1items' + /api/libraries/{id}/issues: + $ref: './controllers/LibraryController.yaml#/paths/~1api~1libraries~1{id}~1issues' + /api/libraries/{id}/series: + $ref: './controllers/LibraryController.yaml#/paths/~1api~1libraries~1{id}~1series' + /api/libraries/{id}/series/{seriesId}: + $ref: './controllers/LibraryController.yaml#/paths/~1api~1libraries~1{id}~1series~1{seriesId}' + /api/series/{id}: + $ref: './controllers/SeriesController.yaml#/paths/~1api~1series~1{id}' tags: - name: Authors description: Author endpoints + - name: Libraries + description: Library endpoints + - name: Series + description: Series endpoints diff --git a/docs/schemas.yaml b/docs/schemas.yaml index de506d4609..6b371892fc 100644 --- a/docs/schemas.yaml +++ b/docs/schemas.yaml @@ -25,9 +25,38 @@ components: type: array items: type: string - example: ["To Be Read", "Genre: Nonfiction"] + 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 + format: '[0-9]*' + example: '649644248522215260' + total: + description: The total number of items in the response. + type: integer + example: 100 + limit: + description: The number of items to return. If 0, no items are returned. + type: integer + example: 10 + default: 0 + page: + description: The page number (zero indexed) to return. If no limit is specified, then page will have no effect. + type: integer + example: 1 + default: 0 + sortDesc: + description: Return items in reversed order if true. + type: boolean + example: true + default: false + minified: + description: Return minified items if true. + type: boolean + example: true + default: false + region: + description: The region used to search. + type: string + example: 'us' + default: 'us'