diff --git a/content/en/docs/Endpoints/createcollection.md b/content/en/docs/Endpoints/createcollection.md new file mode 100644 index 00000000..567b5b24 --- /dev/null +++ b/content/en/docs/Endpoints/createcollection.md @@ -0,0 +1,80 @@ +--- +title: "createCollection" +linkTitle: "createCollection [OS]" +categories: +- Collections +opensubsonic: +- Extension +description: > + Creates a collection. +--- + +`http://your-server/rest/createCollection` + +Creates a collection. +This endpoint must be accessed using an HTTP POST request. + +### Request Body + +The request payload should be provided in the body as a JSON object, as specified by the [CreateCollectionRequest](../payloads/createcollectionrequest) schema. + +{{< tabpane persist=false >}} +{{< tab header="CreateCollectionRequest" lang="json">}} +{ + "name": "test collection", + "comment": "this is a collection", + "items": [ + { + "type": "song", + "id": "300000060" + }, + { + "type": "album", + "id": "200000021" + }, + { + "type": "genre", + "id": "vaporwave" + } + ] +} +{{< /tab >}} +{{< /tabpane >}} + +### Example request + +{{< alert color="primary" >}} `POST http://your-server/rest/createCollection.view?u=demo&p=demo&v=1.16.1&c=AwesomeClientName&f=json` {{< /alert >}} + +### Result + +A [`subsonic-response`](../../responses/subsonic-response) element with a top-level [`collection`](../../responses/collection) object on success. +In case of an error, a standard HTTP error code is returned with a descriptive message. + +{{< tabpane persist=false >}} +{{< tab header="**Example**:" disabled=true />}} +{{< tab header="OpenSubsonic" lang="json">}} +{ + "subsonic-response": { + "status": "ok", + "version": "1.16.1", + "type": "AwesomeServerName", + "serverVersion": "0.1.3 (tag)", + "openSubsonic": true, + "collection": { + "id": "800000075", + "name": "test collection", + "comment" "this is a collection", + "owner": "user", + "public": false, + "itemCount": 3, + "created": "2023-03-16T03:18:41+00:00", + "changed": "2023-03-16T03:18:41+00:00" + } + } +} +{{< /tab >}} +{{< /tabpane >}} + +| Field | Type | Req. | OpenS. | Details | +| --- | --- | --- | --- | --- | +| `collection` | [`Collection`](../../responses/collection) | **Yes** | | The collection | diff --git a/content/en/docs/Endpoints/deletecollection.md b/content/en/docs/Endpoints/deletecollection.md new file mode 100644 index 00000000..111e5980 --- /dev/null +++ b/content/en/docs/Endpoints/deletecollection.md @@ -0,0 +1,42 @@ +--- +title: "deleteCollection" +linkTitle: "deleteCollection [OS]" +opensubsonic: +- Extension +categories: +- Collections +description: > + Deletes a collection. +--- +`http://your-server/rest/deleteCollection` + +Deletes a collection. + +### Parameters + +| Parameter | Req. | OpenS. | Default | Comment | +| --- | --- | --- | --- | --- | +| `id` | **Yes** | | | ID of the collection to delete, as obtained by `getCollections`. | + +### Example + +{{< alert color="primary" >}} `http://your-server/rest/deleteCollection.view?id=123&u=demo&p=demo&v=1.13.0&c=AwesomeClientName&f=json` {{< /alert >}} + +### Result + +An empty [`subsonic-response`](../../responses/subsonic-response) element on success. + +{{< tabpane persist=false >}} +{{< tab header="**Example**:" disabled=true />}} +{{< tab header="OpenSubsonic" lang="json">}} +{ + "subsonic-response": { + "status": "ok", + "version": "1.16.1", + "type": "AwesomeServerName", + "serverVersion": "0.1.3 (tag)", + "openSubsonic": true + } +} +{{< /tab >}} +{{< /tabpane >}} diff --git a/content/en/docs/Endpoints/getcollection.md b/content/en/docs/Endpoints/getcollection.md new file mode 100644 index 00000000..09edcd19 --- /dev/null +++ b/content/en/docs/Endpoints/getcollection.md @@ -0,0 +1,117 @@ +--- +title: "getCollection" +linkTitle: "getCollection [OS]" +categories: +- Collections +OpenSubsonic: +- Extension +description: > + Returns a collection. +--- + +`http://your-server/rest/getCollection` + +Returns a collection with the items it contains. + +### Parameters + +| Parameter | Req. | OpenS. | Default | Comment | +| --- | --- | --- | --- | --- | +| `id` | **Yes** | | | ID of the collection to return, as obtained by `getCollections`. | +| `itemCount` | No | | | The number of items from the collection to return. If unset or set to a negative value, return all items. | +| `itemOffset` | No | | | The number of items to skip. | + +### Example + +{{< alert color="primary" >}} `http://your-server/rest/getCollection.view?id=123&u=demo&p=demo&v=1.13.0&c=AwesomeClientName&f=json` {{< /alert >}} + +### Result + +A [`subsonic-response`](../../responses/subsonic-response) element with a top-level `collection` object of type [collectionWithItems](../../responses/collectionwithitems) + +{{< tabpane persist=false >}} +{{< tab header="**Example**:" disabled=true />}} +{{< tab header="OpenSubsonic" lang="json">}} +{ + "subsonic-response": { + "status": "ok", + "version": "1.16.1", + "type": "AwesomeServerName", + "serverVersion": "0.1.3 (tag)", + "openSubsonic": true, + "collection": { + "id": "800000075", + "name": "testcreate", + "comment" "this is a collection", + "owner": "user", + "public": true, + "itemCount": 3, + "created": "2023-03-16T03:18:41+00:00", + "changed": "2023-03-16T03:18:41+00:00", + "items": [ + { + "song": { + "id": "300000060", + "parent": "200000002", + "title": "BrownSmoke", + "isDir": false, + "isVideo": false, + "type": "music", + "albumId": "200000002", + "album": "Colorsmoke EP", + "artistId": "100000002", + "artist": "Synthetic", + "coverArt": "300000060", + "duration": 304, + "bitRate": 20, + "bitDepth": 16, + "samplingRate": 44100, + "channelCount": 2, + "userRating": 5, + "averageRating": 5, + "track": 4, + "year": 2007, + "genre": "Electronic", + "size": 792375, + "discNumber": 1, + "suffix": "wma", + "contentType": "audio/x-ms-wma", + "path": "Synthetic/Synthetic_-_Colorsmoke_EP-20k217-2007/04-Synthetic_-_BrownSmokeYSBM20k22khS.wma" + } + }, + { + "album": { + "id": "200000021", + "parent": "100000036", + "album": "Forget and Remember", + "title": "Forget and Remember", + "name": "Forget and Remember", + "isDir": true, + "coverArt": "al-200000021", + "songCount": 20, + "created": "2021-07-22T02:09:31+00:00", + "duration": 4248, + "playCount": 0, + "artistId": "100000036", + "artist": "Comfort Fit", + "year": 2005, + "genre": "Hip-Hop" + } + }, + { + "genre": { + "value": "vaporwave", + "songCount": 6, + "albumCount": 1 + } + } + ] + } + } +} +{{< /tab >}} +{{< /tabpane >}} + +| Field | Type | Req. | OpenS. | Details | +| --- | --- | --- | --- | --- | +| `collection` | [`collectionWithItems`](../../responses/collectionwithitems) | **Yes** | | The collection | diff --git a/content/en/docs/Endpoints/getcollections.md b/content/en/docs/Endpoints/getcollections.md new file mode 100644 index 00000000..053ee6b4 --- /dev/null +++ b/content/en/docs/Endpoints/getcollections.md @@ -0,0 +1,77 @@ +--- +title: "getCollections" +linkTitle: "getCollections [OS]" +OpenSubsonic: +- Extension +categories: +- Collections +description: > + Returns collections a user has access to. +--- + +`http://your-server/rest/getCollections` + +Returns collections a user has access to, with optional filters and pagination. + +### Parameters + +| Parameter | Req. | OpenS. | Default | Comment | +| --- | --- | --- | --- | --- | +| `username` | No | | | If specified, return collections for this user rather than for the authenticated user. The authenticated user must have admin role if this parameter is used. | +| `name` | No | | | If specified, return collections that have the provided string in their name. | +| `sort` | No | | name | If specified, sort the list by the provided field or randomly. Sorting is applied before the `count` and `offset` parameters are taken into account. The default is to sort by name. Allowed values: name, created, random. | +| `order` | No | | asc | The order of sorting. Allowed values: asc, desc. | +| `count` | No | | | The number of collections to return. If unset or set to a negative value, return all collections. | +| `offset` | No | | | The number of collections to skip. | + +### Example + +{{< alert color="primary" >}} `http://your-server/rest/getCollections.view?count=2&u=demo&p=demo&v=1.13.0&c=AwesomeClientName&f=json` {{< /alert >}} + +### Result + +A [`subsonic-response`](../../responses/subsonic-response) element with a top-level `collections` array of [collection](../../responses/collection) elements on success. + +{{< tabpane persist=false >}} +{{< tab header="**Example**:" disabled=true />}} +{{< tab header="OpenSubsonic" lang="json">}} +{ + "subsonic-response": { + "status": "ok", + "version": "1.16.1", + "type": "AwesomeServerName", + "serverVersion": "0.1.3 (tag)", + "openSubsonic": true, + "collections": [ + { + "id": "800000075", + "name": "testcollection", + "owner": "user", + "public": true, + "created": "2026-03-16T03:18:41+00:00", + "changed": "2026-03-16T03:18:41+00:00", + "itemCount": 3, + "readonly": true + }, + { + "id": "800000076", + "name": "testcollection2", + "comment" "this is another collection", + "owner": "user", + "public": false, + "itemCount": 17, + "created": "2026-03-16T03:18:41+00:00", + "changed": "2026-03-16T03:18:41+00:00", + "readonly": true + } + ], + "totalCount": 10 + } +} +{{< /tab >}} +{{< /tabpane >}} + +| Field | Type | Req. | OpenS. | Details | +| --- | --- | --- | --- | --- | +| `collections` | Array of [`collection`](../../responses/collection) | **Yes** | | The collections. | +| `totalCount` | integer | **Yes** | | The total number of collections a user has access to. | diff --git a/content/en/docs/Endpoints/updatecollection.md b/content/en/docs/Endpoints/updatecollection.md new file mode 100644 index 00000000..699aaff6 --- /dev/null +++ b/content/en/docs/Endpoints/updatecollection.md @@ -0,0 +1,81 @@ +--- +title: "updateCollection" +linkTitle: "updateCollection [OS]" +categories: +- Collections +opensubsonic: +- Extension +description: > + Updates a collection. +--- +`http://your-server/rest/updateCollection` + +Updates a collection. Only the owner of a collection is allowed to update it. +This endpoint must be accessed using an HTTP POST request. + +### Request Body + +The request payload should be provided in the body as a JSON object, as specified by the [UpdateCollectionRequest](../payloads/updatecollectionrequest) schema. +Only the fields specified in the request payload will have an effect on a collection. + +{{< tabpane persist=false >}} +{{< tab header="**Body**:" disabled=true />}} +{{< tab header="Name and comment" lang="json">}} +{ + "collectionId": "23785995", + "name": "new name", + "comment": "this is a new comment" +} +{{< /tab >}} +{{< tab header="Add" lang="json">}} +{ + "collectionId": "23785995", + "add": [ + { + "type": "album", + "id": "1234" + } + ] +} +{{< /tab >}} +{{< tab header="Move" lang="json">}} +{ + "collectionId": "23785995", + "move": { + "fromStart": 0, + "fromEnd": 5, + "to": 20 + } +} +{{< /tab >}} +{{< tab header="Remove" lang="json">}} +{ + "collectionId": "23785995", + "remove": [0, 1, 2, 6] +} +{{< /tab >}} +{{< /tabpane >}} + +### Example request + +{{< alert color="primary" >}} `POST http://your-server/rest/updateCollection.view?u=demo&p=demo&v=1.13.0&c=AwesomeClientName&f=json` {{< /alert >}} + +### Result + +An empty [`subsonic-response`](../../responses/subsonic-response) element on success. +In case of an error, a standard HTTP error code is returned with a descriptive message. + +{{< tabpane persist=false >}} +{{< tab header="**Example**:" disabled=true />}} +{{< tab header="OpenSubsonic" lang="json">}} +{ + "subsonic-response": { + "status": "ok", + "version": "1.16.1", + "type": "AwesomeServerName", + "serverVersion": "0.1.3 (tag)", + "openSubsonic": true + } +} +{{< /tab >}} +{{< /tabpane >}} diff --git a/content/en/docs/Extensions/collections.md b/content/en/docs/Extensions/collections.md new file mode 100644 index 00000000..8743359f --- /dev/null +++ b/content/en/docs/Extensions/collections.md @@ -0,0 +1,25 @@ +--- +title: "Collections" +linkTitle: "Collections" +OpenSubsonic: +- Extension +description: > + Add support for collections. +--- + +**OpenSubsonic version**: [1](../../opensubsonic-versions) + +**OpenSubsonic extension name**: `collections` (As returned by [`getOpenSubsonicExtensions`](../../endpoints/getopensubsonicextensions)) + +This extension allows users to create and manage collections. +A collection is an ordered list of items - songs, artist, albums, playlists, etc. + +## Version 1 + +Servers that support this extension provide the following endpoints: + +- [`getCollections`](../../endpoints/getCollections) +- [`getCollection`](../../endpoints/getCollection) +- [`createCollection`](../../endpoints/createCollection) +- [`updateCollection`](../../endpoints/updateCollection) +- [`deleteCollection`](../../endpoints/deleteCollection) diff --git a/content/en/docs/Payloads/CollectionItemID.md b/content/en/docs/Payloads/CollectionItemID.md new file mode 100644 index 00000000..61a2be49 --- /dev/null +++ b/content/en/docs/Payloads/CollectionItemID.md @@ -0,0 +1,39 @@ +--- +title: "collectionItemID" +linkTitle: "collectionItemID [OS]" +categories: +- Collections +opensubsonic: +- Extension +description: > + An identifier for a collection item to be added. +--- + +When the 'id' field is not present for an item type (e.g. [Genre](../responses/genre)), use its name or some other natural identifier. + +{{< tabpane persist=false >}} +{{< tab header="**Example**:" disabled=true />}} +{{< tab header="Song" lang="json">}} +{ + "type": "song", + "id": "20" +} +{{< /tab >}} +{{< tab header="Album" lang="json">}} +{ + "type": "album", + "id": "30" +} +{{< /tab >}} +{{< tab header="Genre" lang="json">}} +{ + "type": "genre", + "id": "vaporwave" +} +{{< /tab >}} +{{< /tabpane >}} + +| Field | Type | Req. | OpenS. | Details | +| --- | --- | --- | --- | --- | +| `type` | `string` | **Yes** | | One of: song, album, artist, playlist, genre, internetRadioStation, podcastEpisode, podcast. | +| `id` | `string` | **Yes** | | ID (or name, if 'id' is not available) of an item. | diff --git a/content/en/docs/Payloads/CreateCollectionRequest.md b/content/en/docs/Payloads/CreateCollectionRequest.md new file mode 100644 index 00000000..51e130c6 --- /dev/null +++ b/content/en/docs/Payloads/CreateCollectionRequest.md @@ -0,0 +1,43 @@ +--- +title: "CreateCollectionRequest" +linkTitle: "CreateCollectionRequest [OS]" +categories: +- Collections +opensubsonic: +- Extension +description: > + Request payload for creating a collection. +--- + +Should be used in the [CreateCollection](../endpoints/createcollection) endpoint. + +{{< tabpane persist=false >}} +{{< tab header="**Example**:" disabled=true />}} +{{< tab header="OpenSubsonic" lang="json">}} +{ + "name": "test collection", + "comment": "this is a collection", + "items": [ + { + "type": "song", + "id": "300000060" + }, + { + "type": "album", + "id": "200000021" + }, + { + "type": "genre", + "id": "vaporwave" + } + ] +} +{{< /tab >}} +{{< /tabpane >}} + +| Field | Type | Req. | OpenS. | Details | +| --- | --- | --- | --- | --- | +| `name` | string | **Yes** | **Yes** | The human-readable name of the collection. | +| `comment` | string | No | **Yes** | The collection comment. | +| `public` | boolean | No | **Yes** | `true` if the collection should be visible to all users, `false` otherwise. Default `false`. | +| `items` | [CollectionItemID[]](../payloads/collectionitemid) | No | **Yes** | A list of items. | diff --git a/content/en/docs/Payloads/MoveRange.md b/content/en/docs/Payloads/MoveRange.md new file mode 100644 index 00000000..43e00bb8 --- /dev/null +++ b/content/en/docs/Payloads/MoveRange.md @@ -0,0 +1,27 @@ +--- +title: "MoveRange" +linkTitle: "MoveRange [OS]" +categories: +- Collections +opensubsonic: +- Extension +description: > + Move a range of items from one position to another. +--- + +{{< tabpane persist=false >}} +{{< tab header="**Example**:" disabled=true />}} +{{< tab header="OpenSubsonic" lang="json">}} +{ + "fromStart": 0, + "fromEnd": 5, + "to": 20 +} +{{< /tab >}} +{{< /tabpane >}} + +| Field | Type | Req. | OpenS. | Details | +| --- | --- | --- | --- | --- | +| `fromStart` | integer | **Yes** | **Yes** | Start of the original range (inclusive). | +| `fromEnd` | integer | **Yes** | **Yes** | End of the original range (exclusive). | +| `to` | integer | **Yes** | **Yes** | New index of the first item in the range. | diff --git a/content/en/docs/Payloads/UpdateCollectionRequest.md b/content/en/docs/Payloads/UpdateCollectionRequest.md new file mode 100644 index 00000000..a1e80fdc --- /dev/null +++ b/content/en/docs/Payloads/UpdateCollectionRequest.md @@ -0,0 +1,63 @@ +--- +title: "UpdateCollectionRequest" +linkTitle: "UpdateCollectionRequest [OS]" +categories: +- Collections +opensubsonic: +- Extension +description: > + Request payload for updating a collection. +--- + +Should be used in the [UpdateCollection](../endpoints/updatecollection) endpoint. +Only the fields supplied in the request payload will have an effect on a collection. + +{{< tabpane persist=false >}} +{{< tab header="**Example**:" disabled=true />}} +{{< tab header="Name and comment" lang="json">}} +{ + "collectionId": "23785995", + "name": "new name", + "comment": "this is a new comment" +} +{{< /tab >}} +{{< tab header="Add" lang="json">}} +{ + "collectionId": "23785995", + "add": [ + { + "type": "album", + "id": "1234" + } + ] +} +{{< /tab >}} +{{< tab header="Move" lang="json">}} +{ + "collectionId": "23785995", + "move": { + "fromStart": 0, + "fromEnd": 5, + "to": 20 + } +} +{{< /tab >}} +{{< tab header="Remove" lang="json">}} +{ + "collectionId": "23785995", + "remove": [0, 1, 2, 6] +} +{{< /tab >}} +{{< /tabpane >}} + +| Field | Type | Req. | OpenS. | Details | +| --- | --- | --- | --- | --- | +| `collectionId` | string | **Yes** | **Yes** | The collection ID. | +| `name` | string | No | **Yes** | The human-readable name of the collection. Cannot be an empty string. | +| `comment` | string | No | **Yes** | The collection comment. | +| `public` | boolean | No | **Yes** | `true` if the collection should be visible to all users, `false` otherwise. | +| `add` | [CollectionItemID[]](../payloads/collectionitemid) | No | **Yes** | Add the specified items to the collection. Items are appended to the tail of the collection. | +| `move` | [MoveRange](../payloads/moverange) | No | **Yes** | Move items from the original position to another in the collection. | +| `remove` | Array of integer | No | **Yes** | Remove the items at the specified positions from the collection. | + +Only one of `add`, `move`, and `remove` can be non-empty in a single request. diff --git a/content/en/docs/Responses/collection.md b/content/en/docs/Responses/collection.md new file mode 100644 index 00000000..db1d4d13 --- /dev/null +++ b/content/en/docs/Responses/collection.md @@ -0,0 +1,45 @@ +--- +title: "collection" +linkTitle: "collection [OS]" +categories: +- Collections +opensubsonic: +- Extension +description: > + A collection, as returned by the [getCollections](../../endpoints/getcollections) endpoint. +--- + +Does not contain the items in the collection, look at [collectionWithItems](../../responses/collectionwithitems) instead. + +{{< tabpane persist=false >}} +{{< tab header="**Example**:" disabled=true />}} +{{< tab header="Collection" lang="json">}} +{ + "id": "800000075", + "name": "testcollection", + "owner": "user", + "public": true, + "created": "2026-03-16T03:18:41+00:00", + "changed": "2026-03-16T03:18:41+00:00", + "itemCount": 3, + "readonly": true +} +{{< /tab >}} +{{< /tabpane >}} + +| Field | Type | Req. | OpenS. | Details | +| --- | --- | --- | --- | --- | +| `id` | `string` | **Yes** | | Id of the collection | +| `name` | `string` | **Yes** | | Name of the collection | +| `comment` | `string` | No| | A commnet | +| `owner` | `string` | No | | Owner of the collection | +| `public` | `boolean` | No| | Is the collection public | +| `itemCount` | `int` | **Yes** | | number of items | +| `created` | `string` | **Yes** | | Creation date [ISO 8601] | +| `changed` | `string` | **Yes** | | Last changed date [ISO 8601] | +| `coverArt` | `string` | No | | A cover Art Id | +| `readonly` | `boolean` | No | | If true the collection cannot be edited by the current user | + +When `readonly` is true, clients should hide or disable UI actions that modify the collection. The value should reflect the current authenticated user's access level. When omitted, clients should assume the collection is editable (`false`). + +The `validUntil` field indicates how long the collection contents can be treated as fresh, inspired by HTTP caching semantics. Clients may use this to determine when to refresh the collection data. An empty or absent value indicates no caching guarantee; clients should refresh the collection data on each access. diff --git a/content/en/docs/Responses/collectionItem.md b/content/en/docs/Responses/collectionItem.md new file mode 100644 index 00000000..6f65c296 --- /dev/null +++ b/content/en/docs/Responses/collectionItem.md @@ -0,0 +1,331 @@ +--- +title: "collectionItem" +linkTitle: "collectionItem [OS]" +categories: +- Collections +opensubsonic: +- Extension +description: > + A collection item. +--- + +An item in a collection is a wrapper around one of the following types: + +- [Child](../responses/child) +- [AlbumID3](../albumid3/) +- [ArtistID3](../artistid3/) +- [Playlist](../playlist/) +- [Genre](../genre) +- [InternetRadioStation](../internetradiostation) +- [PodcastEpisode](../podcastepisode) +- [PodcastChannel](../podcastchannel) + +{{< tabpane persist=false >}} +{{< tab header="**Example**:" disabled=true />}} +{{< tab header="Song (child)" lang="json">}} +{ + "song": { + "id": "082f435a363c32c57d5edb6a678a28d4", + "parent": "e8a0685e3f3ec6f251649af2b58b8617", + "isDir": false, + "title": "\"polar expedition\"", + "album": "Live at The Casbah - 2005-04-29", + "artist": "The New Deal", + "track": 4, + "year": 2005, + "coverArt": "mf-082f435a363c32c57d5edb6a678a28d4_6410b3ce", + "size": 19866778, + "contentType": "audio/flac", + "suffix": "flac", + "starred": "2023-03-27T09:45:27Z", + "duration": 178, + "bitRate": 880, + "bitDepth": 16, + "samplingRate": 44100, + "channelCount": 2, + "path": "The New Deal/Live at The Casbah - 2005-04-29/04 - \"polar expedition\".flac", + "playCount": 8, + "played": "2023-03-26T22:27:46Z", + "discNumber": 1, + "created": "2023-03-14T17:51:22.112827504Z", + "albumId": "e8a0685e3f3ec6f251649af2b58b8617", + "artistId": "97e0398acf63f9fb930d7d4ce209a52b", + "type": "music", + "mediaType": "song", + "isVideo": false, + "bpm": 134, + "comment": "This is a song comment", + "sortName": "Polar expedition", + "musicBrainzId": "189002e7-3285-4e2e-92a3-7f6c30d407a2", + "isrc": [ + "USSM18300073", + "DELV42300297", + "DEE868300011", + "DEE868300007" + ], + "genres": [ + { + "name": "Hip-Hop" + }, + { + "name": "East coast" + } + ], + "artists": [ + { + "id": "ar-1", + "name": "Artist 1" + }, + { + "id": "ar-2", + "name": "Artist 2" + } + ], + "displayArtist": "Artist 1 feat. Artist 2", + "albumArtists": [ + { + "id": "ar-6", + "name": "Artist 6" + }, + { + "id": "ar-7", + "name": "Artist 7" + } + ], + "displayAlbumArtist": "Artist 6 & Artist 7", + "contributors": [ + { + "role": "composer", + "artist": { + "id": "ar-3", + "name": "Artist 3" + } + }, + { + "role": "composer", + "artist": { + "id": "ar-4", + "name": "Artist 4" + } + }, + { + "role": "lyricist", + "artist": { + "id": "ar-5", + "name": "Artist 5" + } + }, + { + "role": "performer", + "subRole": "Bass", + "artist": { + "id": "ar-5", + "name": "Artist 5" + } + } + ], + "displayComposer": "Artist 3, Artist 4", + "moods": [ + "slow", + "cool" + ], + "explicitStatus": "explicit", + "replayGain": { + "trackGain": 0.1, + "albumGain": 1.1, + "trackPeak": 9.2, + "albumPeak": 9, + "baseGain": 0 + }, + "works": [ + { + "name": "Symphony No. 5 in C minor, Op. 67", + "musicBrainzId": "d03bff61-26fc-301b-98ac-4d8e85771cbc" + } + ], + "movements": [ + { + "name": "Andante con moto", + "number": 2, + "count": 4 + } + ], + "groupings": ["Soundtrack", "Live"] + } +} +{{< /tab >}} +{{< tab header="Album" lang="json">}} +{ + "album": { + "id": "ad0f112b6dcf83de5e9cae85d07f0d35", + "name": "8-bit lagerfeuer", + "version": "Deluxe Edition", + "artist": "pornophonique", + "year": 2007, + "coverArt": "al-ad0f112b6dcf83de5e9cae85d07f0d35_640a93a8", + "starred": "2023-03-22T01:51:06Z", + "duration": 1954, + "playCount": 97, + "genre": "Hip-Hop", + "created": "2023-03-10T02:19:35.784818075Z", + "artistId": "91c3901ac465b9efc439e4be4270c2b6", + "songCount": 8, + "played": "2023-03-28T00:45:13Z", + "userRating": 4, + "recordLabels": [ + { + "name": "Sony" + } + ], + "musicBrainzId": "189002e7-3285-4e2e-92a3-7f6c30d407a2", + "genres": [ + { + "name": "Hip-Hop" + }, + { + "name": "East coast" + } + ], + "artists": [ + { + "id": "ar-1", + "name": "Artist 1" + }, + { + "id": "ar-2", + "name": "Artist 2" + } + ], + "displayArtist": "Artist 1 feat. Artist 2", + "releaseTypes": [ + "Album", + "Remixes" + ], + "moods": [ + "slow", + "cool" + ], + "sortName": "lagerfeuer (8-bit)", + "originalReleaseDate": { + "year": 2001, + "month": 3, + "day": 10 + }, + "releaseDate": { + "year": 2001, + "month": 3, + "day": 10 + }, + "isCompilation": false, + "explicitStatus": "explicit", + "discTitles": [ + { + "disc": 0, + "title": "Disc 0 title" + "coverArt": "42" + }, + { + "disc": 2, + "title": "Disc 1 title" + "coverArt": "6547" + } + ] + } +} +{{< /tab >}} +{{< tab header="Artist" lang="json">}} +{ + "artist": { + "id": "37ec820ca7193e17040c98f7da7c4b51", + "name": "2 Mello", + "coverArt": "ar-37ec820ca7193e17040c98f7da7c4b51_0", + "albumCount": 1, + "userRating": 5, + "artistImageUrl": "https://demo.org/image.jpg", + "starred": "2017-04-11T10:42:50.842Z", + "musicBrainzId": "189002e7-3285-4e2e-92a3-7f6c30d407a2", + "sortName": "Mello (2)", + "roles": [ + "artist", + "albumartist", + "composer" + ] + } +} +{{< /tab >}} +{{< tab header="Playlist" lang="json">}} +{ + "playlist": { + "id": "800000075", + "name": "testplaylist", + "owner": "user", + "public": true, + "created": "2023-03-16T03:18:41+00:00", + "changed": "2023-03-16T03:18:41+00:00", + "songCount": 1, + "duration": 304, + "readonly": true + } +} +{{< /tab >}} +{{< tab header="Genre" lang="json">}} +{ + "genre": { + "value": "vaporwave", + "songCount": 6, + "albumCount": 1 + } +} +{{< /tab >}} +{{< tab header="Internet radio station" lang="json">}} +{ + "internetRadioStation": { + "id": "2", + "name": "HBR1.com - I.D.M. Tranceponder", + "streamUrl": "http://ubuntu.hbr1.com:19800/trance.ogg", + "homePageUrl": "http://www.hbr1.com/" + } +} +{{< /tab >}} +{{< tab header="Podcast episode" lang="json">}} +{ + "podcastEpisode": { + "id": "7390", + "parent": "7389", + "isDir": "false", + "title": "Jonas Gahr Støre", + "album": "NRK – Hallo P3", + "artist": "Podcast", + "year": "2015", + "coverArt": "7389", + "size": "41808585", + "contentType": "audio/mpeg", + "suffix": "mp3", + "duration": "2619", + "bitRate": "128", + "isVideo": "false", + "created": "2015-09-07T20:07:31.000Z", + "artistId": "453", + "type": "podcast", + "streamId": "7410", + "channelId": "17", + "description": "Jonas Gahr Støre fra Arbeiderpartiet er med i dagens partilederutspørring i Hallo P3!", + "status": "completed", + "publishDate": "2015-09-07T15:29:00.000Z" + } +} +{{< /tab >}} +{{< tab header="Podcast" lang="json">}} +{ + "podcast": { + "id": "1", + "url": "http://downloads.bbc.co.uk/podcasts/fivelive/drkarl/rss.xml", + "title": "Dr Karl and the Naked Scientist", + "description": "Dr Chris Smith aka The Naked Scientist with the latest news from the world of science and Dr Karl answers listeners' science questions.", + "coverArt": "pod-1", + "originalImageUrl": "http://downloads.bbc.co.uk/podcasts/fivelive/drkarl/drkarl.jpg", + "status": "completed" + } +} +{{< /tab >}} +{{< /tabpane >}} diff --git a/content/en/docs/Responses/collectionWithItems.md b/content/en/docs/Responses/collectionWithItems.md new file mode 100644 index 00000000..d9574157 --- /dev/null +++ b/content/en/docs/Responses/collectionWithItems.md @@ -0,0 +1,99 @@ +--- +title: "collectionWithItems" +linkTitle: "collectionWithItems [OS]" +categories: +- Collections +opensubsonic: +- Extension +description: > + A collection with items, as returned by the [getCollection](../../endpoints/getcollection) endpoint. +--- + +{{< tabpane persist=false >}} +{{< tab header="**Example**:" disabled=true />}} +{{< tab header="Collection" lang="json">}} +{ + "id": "800000075", + "name": "testcollection", + "owner": "user", + "public": true, + "created": "2026-03-16T03:18:41+00:00", + "changed": "2026-03-16T03:18:41+00:00", + "itemCount": 3, + "readonly": true, + "items": [ + { + "song": { + "id": "300000060", + "parent": "200000002", + "title": "BrownSmoke", + "isDir": false, + "isVideo": false, + "type": "music", + "albumId": "200000002", + "album": "Colorsmoke EP", + "artistId": "100000002", + "artist": "Synthetic", + "coverArt": "300000060", + "duration": 304, + "bitRate": 20, + "bitDepth": 16, + "samplingRate": 44100, + "channelCount": 2, + "userRating": 5, + "averageRating": 5, + "track": 4, + "year": 2007, + "genre": "Electronic", + "size": 792375, + "discNumber": 1, + "suffix": "wma", + "contentType": "audio/x-ms-wma", + "path": "Synthetic/Synthetic_-_Colorsmoke_EP-20k217-2007/04-Synthetic_-_BrownSmokeYSBM20k22khS.wma" + } + }, + { + "album": { + "id": "200000021", + "parent": "100000036", + "album": "Forget and Remember", + "title": "Forget and Remember", + "name": "Forget and Remember", + "isDir": true, + "coverArt": "al-200000021", + "songCount": 20, + "created": "2021-07-22T02:09:31+00:00", + "duration": 4248, + "playCount": 0, + "artistId": "100000036", + "artist": "Comfort Fit", + "year": 2005, + "genre": "Hip-Hop" + } + }, + { + "genre": { + "value": "vaporwave", + "songCount": 6, + "albumCount": 1 + } + } + ] +} +{{< /tab >}} +{{< /tabpane >}} + +| Field | Type | Req. | OpenS. | Details | +| --- | --- | --- | --- | --- | +| `id` | `string` | **Yes** | | Id of the collection | +| `name` | `string` | **Yes** | | Name of the collection | +| `comment` | `string` | No| | A commnet | +| `owner` | `string` | No | | Owner of the collection | +| `public` | `boolean` | No| | Is the collection public | +| `itemCount` | `int` | **Yes** | | number of items | +| `created` | `string` | **Yes** | | Creation date [ISO 8601] | +| `changed` | `string` | **Yes** | | Last changed date [ISO 8601] | +| `readonly` | `boolean` | No | | If true the collection cannot be edited by the current user | +| `items` | Array of [`collectionItem`](../collectionitem) | **Yes** | | The list of items | + +When `readonly` is true, clients should hide or disable UI actions that modify the collection. This is useful for server-generated collections like smart collections, recommendations, or curated system lists. The value should reflect the current authenticated user's access level. When omitted, clients should assume the collection is editable (`false`). diff --git a/openapi/endpoints/createCollection.json b/openapi/endpoints/createCollection.json new file mode 100644 index 00000000..33ba04b3 --- /dev/null +++ b/openapi/endpoints/createCollection.json @@ -0,0 +1,47 @@ +{ + "post": { + "summary": "Creates a collection.", + "description": "Creates a collection.", + "operationId": "createCollection", + "tags": [ + "Collections", + "Extension" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "./createCollection/CreateCollectionRequest.json" + } + } + } + }, + "parameters": [], + "responses": { + "200": { + "description": "Successful or failed response", + "content": { + "application/json": { + "schema": { + "$ref": "./createCollection/CreateCollectionResponse.json" + } + } + } + }, + "400": { + "description": "Request is malformed or doesn't follow the schema." + }, + "404": { + "description": "Some items don't exist, or extension not supported." + }, + "405": { + "$ref": "../responses/HTTPFormPostNotSupported.json" + } + }, + "externalDocs": { + "description": "createCollection", + "url": "https://opensubsonic.netlify.app/docs/endpoints/createcollection/" + } + } +} diff --git a/openapi/endpoints/createCollection/CreateCollectionRequest.json b/openapi/endpoints/createCollection/CreateCollectionRequest.json new file mode 100644 index 00000000..6618a963 --- /dev/null +++ b/openapi/endpoints/createCollection/CreateCollectionRequest.json @@ -0,0 +1,28 @@ +{ + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The human-readable name of the collection." + }, + "comment": { + "type": "string", + "description": "The collection comment." + }, + "public": { + "description": "`true` if the collection should be visible to all users, `false` otherwise. Default is `false`.", + "default": "false", + "type": "boolean" + }, + "items": { + "type": "array", + "items": { + "$ref": "../../schemas/CollectionItemId.json" + }, + "description": "Identifiers of items in the collection." + } + }, + "required": [ + "name" + ] +} diff --git a/openapi/endpoints/createCollection/CreateCollectionResponse.json b/openapi/endpoints/createCollection/CreateCollectionResponse.json new file mode 100644 index 00000000..c873b21a --- /dev/null +++ b/openapi/endpoints/createCollection/CreateCollectionResponse.json @@ -0,0 +1,20 @@ +{ + "type": "object", + "description": "A subsonic-response element with a nested collection element on success.", + "properties": { + "subsonic-response": { + "oneOf": [ + { + "$ref": "./CreateCollectionSuccessResponse.json" + }, + { + "$ref": "../../schemas/SubsonicResponse/SubsonicFailureResponse.json" + } + ] + } + }, + "externalDocs": { + "description": "CreateCollectionResponse", + "url": "https://opensubsonic.netlify.app/docs/endpoints/createcollection/" + } +} diff --git a/openapi/endpoints/createCollection/CreateCollectionSuccessResponse.json b/openapi/endpoints/createCollection/CreateCollectionSuccessResponse.json new file mode 100644 index 00000000..08d97fdd --- /dev/null +++ b/openapi/endpoints/createCollection/CreateCollectionSuccessResponse.json @@ -0,0 +1,18 @@ +{ + "allOf": [ + { + "$ref": "../../schemas/SubsonicResponse/SubsonicSuccessResponse.json" + }, + { + "type": "object", + "properties": { + "collection": { + "$ref": "../../schemas/Collection.json" + } + }, + "required": [ + "collection" + ] + } + ] +} diff --git a/openapi/endpoints/deleteCollection.json b/openapi/endpoints/deleteCollection.json new file mode 100644 index 00000000..1b43ec5c --- /dev/null +++ b/openapi/endpoints/deleteCollection.json @@ -0,0 +1,34 @@ +{ + "get": { + "summary": "Deletes a saved collection.", + "description": "Deletes a saved collection.", + "operationId": "deleteCollection", + "tags": [ + "Collections", + "Extension" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "description": "ID of the collection to delete, as obtained by `getCollections`.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "$ref": "../responses/EmptySubsonicResponse.json" + }, + "404": { + "description": "Collection does not exist, or extension not supported." + } + }, + "externalDocs": { + "description": "deleteCollection", + "url": "https://opensubsonic.netlify.app/docs/endpoints/deletecollection/" + } + } +} diff --git a/openapi/endpoints/getCollection.json b/openapi/endpoints/getCollection.json new file mode 100644 index 00000000..5bef4691 --- /dev/null +++ b/openapi/endpoints/getCollection.json @@ -0,0 +1,63 @@ +{ + "get": { + "summary": "Get a collection", + "description": "Get a collection", + "operationId": "getCollection", + "tags": [ + "Collections", + "Extension" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "description": "ID of the collection to return, as obtained by `getCollections`.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "itemCount", + "in": "query", + "description": "The number of items from the collection to return. If unset or set to a negative value, return all items.", + "required": false, + "schema": { + "type": "integer", + "default": -1, + "minimum": 0 + } + }, + { + "name": "itemOffset", + "in": "query", + "description": "The number of items to skip.", + "required": false, + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Successful or failed response", + "content": { + "application/json": { + "schema": { + "$ref": "./getCollection/GetCollectionResponse.json" + } + } + } + }, + "404": { + "description": "Collection does not exist, or extension not supported." + } + }, + "externalDocs": { + "description": "getCollection", + "url": "https://opensubsonic.netlify.app/docs/endpoints/getcollection/" + } + } +} diff --git a/openapi/endpoints/getCollection/GetCollectionResponse.json b/openapi/endpoints/getCollection/GetCollectionResponse.json new file mode 100644 index 00000000..0b11a40d --- /dev/null +++ b/openapi/endpoints/getCollection/GetCollectionResponse.json @@ -0,0 +1,20 @@ +{ + "type": "object", + "description": "A subsonic-response element with a nested collection element on success.", + "properties": { + "subsonic-response": { + "oneOf": [ + { + "$ref": "./GetCollectionSuccessResponse.json" + }, + { + "$ref": "../../schemas/SubsonicResponse/SubsonicFailureResponse.json" + } + ] + } + }, + "externalDocs": { + "description": "GetCollectionResponse", + "url": "https://opensubsonic.netlify.app/docs/endpoints/getcollection/" + } +} diff --git a/openapi/endpoints/getCollection/GetCollectionSuccessResponse.json b/openapi/endpoints/getCollection/GetCollectionSuccessResponse.json new file mode 100644 index 00000000..f7e164c5 --- /dev/null +++ b/openapi/endpoints/getCollection/GetCollectionSuccessResponse.json @@ -0,0 +1,18 @@ +{ + "allOf": [ + { + "$ref": "../../schemas/SubsonicResponse/SubsonicSuccessResponse.json" + }, + { + "type": "object", + "properties": { + "collection": { + "$ref": "../../schemas/CollectionWithItems.json" + } + }, + "required": [ + "collection" + ] + } + ] +} diff --git a/openapi/endpoints/getCollections.json b/openapi/endpoints/getCollections.json new file mode 100644 index 00000000..fd776960 --- /dev/null +++ b/openapi/endpoints/getCollections.json @@ -0,0 +1,99 @@ +{ + "get": { + "summary": "Get collections a user has access to.", + "description": "Get collections a user has access to, with optional filters and pagination.", + "operationId": "getCollections", + "tags": [ + "Collections", + "Extension" + ], + "parameters": [ + { + "name": "username", + "in": "query", + "description": "If specified, return collections for this user rather than for the authenticated user. The authenticated user must have admin role if this parameter is used.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "name", + "in": "query", + "description": "If specified, return collections that have the provided string in their name.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "If specified, sort the list by the provided field or randomly. Sorting is applied before the `count` and `offset` parameters are taken into account. The default is to sort by name.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "name", + "created", + "random" + ] + } + }, + { + "name": "order", + "in": "query", + "description": "The order of sorting.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "count", + "in": "query", + "description": "The number of collections to return. If unset or set to a negative value, return all collections.", + "required": false, + "schema": { + "type": "integer", + "default": -1, + "minimum": 0 + } + }, + { + "name": "offset", + "in": "query", + "description": "The number of collections to skip.", + "required": false, + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Successful or failed response", + "content": { + "application/json": { + "schema": { + "$ref": "./getCollections/GetCollectionsResponse.json" + } + } + } + }, + "404": { + "description": "User does not exist, or extension not supported." + } + }, + "externalDocs": { + "description": "getCollections", + "url": "https://opensubsonic.netlify.app/docs/endpoints/getcollections/" + } + } +} diff --git a/openapi/endpoints/getCollections/GetCollectionsResponse.json b/openapi/endpoints/getCollections/GetCollectionsResponse.json new file mode 100644 index 00000000..83d245ce --- /dev/null +++ b/openapi/endpoints/getCollections/GetCollectionsResponse.json @@ -0,0 +1,20 @@ +{ + "type": "object", + "description": "A subsonic-response element with a nested `collections` element on success.", + "properties": { + "subsonic-response": { + "oneOf": [ + { + "$ref": "./GetCollectionsSuccessResponse.json" + }, + { + "$ref": "../../schemas/SubsonicResponse/SubsonicFailureResponse.json" + } + ] + } + }, + "externalDocs": { + "description": "GetCollectionsResponse", + "url": "https://opensubsonic.netlify.app/docs/endpoints/getcollections/" + } +} diff --git a/openapi/endpoints/getCollections/GetCollectionsSuccessResponse.json b/openapi/endpoints/getCollections/GetCollectionsSuccessResponse.json new file mode 100644 index 00000000..1cc27b1a --- /dev/null +++ b/openapi/endpoints/getCollections/GetCollectionsSuccessResponse.json @@ -0,0 +1,27 @@ +{ + "allOf": [ + { + "$ref": "../../schemas/SubsonicResponse/SubsonicSuccessResponse.json" + }, + { + "type": "object", + "properties": { + "collections": { + "type": "array", + "items": { + "$ref": "../../schemas/Collection.json" + }, + "description": "The collections." + }, + "totalCount": { + "type": "integer", + "description": "The total number of collections a user has access to." + } + }, + "required": [ + "collections", + "totalCount" + ] + } + ] +} diff --git a/openapi/endpoints/updateCollection.json b/openapi/endpoints/updateCollection.json new file mode 100644 index 00000000..1149f29a --- /dev/null +++ b/openapi/endpoints/updateCollection.json @@ -0,0 +1,40 @@ +{ + "post": { + "summary": "Updates a collection.", + "description": "Updates a collection. Only the owner of a collection is allowed to update it.", + "operationId": "updateCollection", + "tags": [ + "Collections", + "Extension" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "./updateCollection/UpdateCollectionRequest.json" + } + } + } + }, + "parameters": [], + "responses": { + "200": { + "$ref": "../responses/EmptySubsonicResponse.json" + }, + "400": { + "description": "Request is malformed or doesn't follow the schema." + }, + "404": { + "description": "Collection does not exist, or extension not supported." + }, + "405": { + "$ref": "../responses/HTTPFormPostNotSupported.json" + } + }, + "externalDocs": { + "description": "updateCollection", + "url": "https://opensubsonic.netlify.app/docs/endpoints/updatecollection/" + } + } +} diff --git a/openapi/endpoints/updateCollection/UpdateCollectionRequest.json b/openapi/endpoints/updateCollection/UpdateCollectionRequest.json new file mode 100644 index 00000000..ae4e05bf --- /dev/null +++ b/openapi/endpoints/updateCollection/UpdateCollectionRequest.json @@ -0,0 +1,42 @@ +{ + "type": "object", + "description": "Request payload for updating a collection. Only the fields supplied in the request payload will have an effect. Only one of `add`, `move`, and `remove` can be non-empty in a single request.", + "properties": { + "collectionId": { + "description": "The collection ID.", + "type": "string" + }, + "name": { + "description": "The human-readable name of the collection.", + "type": "string" + }, + "comment": { + "description": "The collection comment.", + "type": "string" + }, + "public": { + "description": "`true` if the collection should be visible to all users, `false` otherwise.", + "type": "boolean" + }, + "add": { + "description": "Add the specified items to the collection. Multiple items can be specified.", + "type": "array", + "items": { + "$ref": "../../schemas/CollectionItemId.json" + } + }, + "move": { + "$ref": "../../schemas/MoveRange.json" + }, + "remove": { + "description": "Remove the item at this position from the collection. Multiple items can be specified.", + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "collectionId" + ] +} diff --git a/openapi/openapi.json b/openapi/openapi.json index 9d0da653..5a244aef 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -58,6 +58,9 @@ { "name": "Playlists" }, + { + "name": "Collections" + }, { "name": "Lists" }, @@ -117,6 +120,9 @@ "/rest/createPlaylist": { "$ref": "./endpoints/createPlaylist.json" }, + "/rest/createCollection": { + "$ref": "./endpoints/createCollection.json" + }, "/rest/createPodcastChannel": { "$ref": "./endpoints/createPodcastChannel.json" }, @@ -135,6 +141,9 @@ "/rest/deletePlaylist": { "$ref": "./endpoints/deletePlaylist.json" }, + "/rest/deleteCollection": { + "$ref": "./endpoints/deleteCollection.json" + }, "/rest/deletePodcastChannel": { "$ref": "./endpoints/deletePodcastChannel.json" }, @@ -237,6 +246,12 @@ "/rest/getPlaylists": { "$ref": "./endpoints/getPlaylists.json" }, + "/rest/getCollection": { + "$ref": "./endpoints/getCollection.json" + }, + "/rest/getCollections": { + "$ref": "./endpoints/getCollections.json" + }, "/rest/getPlayQueue": { "$ref": "./endpoints/getPlayQueue.json" }, @@ -357,6 +372,9 @@ "/rest/updatePlaylist": { "$ref": "./endpoints/updatePlaylist.json" }, + "/rest/updateCollection": { + "$ref": "./endpoints/updateCollection.json" + }, "/rest/updateShare": { "$ref": "./endpoints/updateShare.json" }, @@ -479,6 +497,33 @@ "CreatePlaylistResponse": { "$ref": "./endpoints/createPlaylist/CreatePlaylistResponse.json" }, + "Collection": { + "$ref": "./schemas/Collection.json" + }, + "CollectionItem": { + "$ref": "./schemas/CollectionItem.json" + }, + "CollectionItemId": { + "$ref": "./schemas/CollectionItemId.json" + }, + "CollectionWithItems": { + "$ref": "./schemas/CollectionWithItems.json" + }, + "CreateCollectionRequest": { + "$ref": "./endpoints/createCollection/CreateCollectionRequest.json" + }, + "CreateCollectionSuccessResponse": { + "$ref": "./endpoints/createCollection/CreateCollectionSuccessResponse.json" + }, + "CreateCollectionResponse": { + "$ref": "./endpoints/createCollection/CreateCollectionResponse.json" + }, + "UpdateCollectionRequest": { + "$ref": "./endpoints/updateCollection/UpdateCollectionRequest.json" + }, + "MoveRange": { + "$ref": "./schemas/MoveRange.json" + }, "Share": { "$ref": "./schemas/Share.json" }, @@ -758,6 +803,18 @@ "GetPlaylistsResponse": { "$ref": "./endpoints/getPlaylists/GetPlaylistsResponse.json" }, + "GetCollectionSuccessResponse": { + "$ref": "./endpoints/getCollection/GetCollectionSuccessResponse.json" + }, + "GetCollectionResponse": { + "$ref": "./endpoints/getCollection/GetCollectionResponse.json" + }, + "GetCollectionsSuccessResponse": { + "$ref": "./endpoints/getCollections/GetCollectionsSuccessResponse.json" + }, + "GetCollectionsResponse": { + "$ref": "./endpoints/getCollections/GetCollectionsResponse.json" + }, "PlayQueue": { "$ref": "./schemas/PlayQueue.json" }, diff --git a/openapi/schemas/Collection.json b/openapi/schemas/Collection.json new file mode 100644 index 00000000..397f5959 --- /dev/null +++ b/openapi/schemas/Collection.json @@ -0,0 +1,59 @@ +{ + "type": "object", + "description": "Collection.", + "properties": { + "id": { + "type": "string", + "description": "Id of the collection" + }, + "name": { + "type": "string", + "description": "Name of the collection" + }, + "comment": { + "type": "string", + "description": "A comment" + }, + "owner": { + "type": "string", + "description": "Owner of the collection" + }, + "public": { + "type": "boolean", + "description": "Is the collection public" + }, + "itemCount": { + "type": "integer", + "description": "Number of items" + }, + "created": { + "type": "string", + "format": "date-time", + "description": "Creation date [ISO 8601]" + }, + "changed": { + "type": "string", + "format": "date-time", + "description": "Last changed date [ISO 8601]" + }, + "coverArt": { + "type": "string", + "description": "A cover Art Id" + }, + "readonly": { + "type": "boolean", + "description": "If true the collection cannot be edited by the current user" + } + }, + "required": [ + "id", + "name", + "itemCount", + "created", + "changed" + ], + "externalDocs": { + "description": "Collection", + "url": "https://opensubsonic.netlify.app/docs/responses/collection/" + } +} diff --git a/openapi/schemas/CollectionItem.json b/openapi/schemas/CollectionItem.json new file mode 100644 index 00000000..6239234d --- /dev/null +++ b/openapi/schemas/CollectionItem.json @@ -0,0 +1,70 @@ +{ + "type": "object", + "description": "Item that can be part of a collection.", + "oneOf": [ + { + "type": "object", + "properties": { + "song": { + "$ref": "Child.json" + } + } + }, + { + "type": "object", + "properties": { + "album": { + "$ref": "AlbumID3.json" + } + } + }, + { + "type": "object", + "properties": { + "artist": { + "$ref": "ArtistID3.json" + } + } + }, + { + "type": "object", + "properties": { + "playlist": { + "$ref": "Playlist.json" + } + } + }, + { + "type": "object", + "properties": { + "genre": { + "$ref": "Genre.json" + } + } + }, + { + "type": "object", + "properties": { + "internetRadioStation": { + "$ref": "InternetRadioStation.json" + } + } + }, + { + "type": "object", + "properties": { + "podcastEpisode": { + "$ref": "PodcastEpisode.json" + } + } + }, + { + "type": "object", + "properties": { + "podcast": { + "$ref": "PodcastChannel.json" + } + } + } + ] +} diff --git a/openapi/schemas/CollectionItemId.json b/openapi/schemas/CollectionItemId.json new file mode 100644 index 00000000..3553eba5 --- /dev/null +++ b/openapi/schemas/CollectionItemId.json @@ -0,0 +1,31 @@ +{ + "type": "object", + "description": "Collection item identifier.", + "properties": { + "type": { + "type": "string", + "description": "Type of the collection item.", + "enum": [ + "song", + "album", + "artist", + "playlist", + "genre", + "internetRadioStation", + "podcastEpisode", + "podcast" + ] + }, + "id": { + "type": "string", + "description": "Identifier of an item. When the 'id' field is not present for an item type (e.g. for a genre), use its name or some other natural identifier." + } + }, + "required": [ + "type" + ], + "externalDocs": { + "description": "CollectionItemId", + "url": "https://opensubsonic.netlify.app/docs/payloads/collectionitemid/" + } +} diff --git a/openapi/schemas/CollectionWithItems.json b/openapi/schemas/CollectionWithItems.json new file mode 100644 index 00000000..cb120cad --- /dev/null +++ b/openapi/schemas/CollectionWithItems.json @@ -0,0 +1,26 @@ +{ + "allOf": [ + { + "$ref": "./Collection.json" + }, + { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "./CollectionItem.json" + }, + "description": "The list of items in the collection" + } + }, + "required": [ + "items" + ], + "externalDocs": { + "description": "CollectionWithItems", + "url": "https://opensubsonic.netlify.app/docs/responses/collectionwithitems/" + } + } + ] +} diff --git a/openapi/schemas/Collections.json b/openapi/schemas/Collections.json new file mode 100644 index 00000000..7a8912ac --- /dev/null +++ b/openapi/schemas/Collections.json @@ -0,0 +1,17 @@ +{ + "type": "object", + "description": "Collections.", + "externalDocs": { + "description": "Collections", + "url": "https://opensubsonic.netlify.app/docs/responses/collections/" + }, + "properties": { + "collection": { + "type": "array", + "items": { + "$ref": "./Collection.json" + }, + "description": "The collections" + } + } +} diff --git a/openapi/schemas/MoveRange.json b/openapi/schemas/MoveRange.json new file mode 100644 index 00000000..30acfd55 --- /dev/null +++ b/openapi/schemas/MoveRange.json @@ -0,0 +1,10 @@ +{ + "type": "object", + "description": "Move a range of items from one position to another. The range is specified by the `fromStart` (inclusive) and `fromEnd` (exclusive) indices. The first item in the range moves to the index `to`.", + "properties": { + "fromStart": {"type": "integer"}, + "fromEnd": {"type": "integer"}, + "to": {"type": "integer"} + }, + "required": ["fromStart", "fromEnd", "to"] +}