openapi: 3.1.0
info:
  title: OpenMusic API
  version: 1.0.0
  description: Open standard for music service communication.
  license:
    name: AGPL-3.0

servers:
  - url: http://localhost:8080/api
    description: Development server
  - url: https://api.example.com/api
    description: Production server

tags:
  - name: Info
  - name: Files
  - name: Songs
  - name: Albums
  - name: Artists
  - name: Playlists
  - name: Stations
  - name: Search
  - name: Explore

paths:
  /:
    get:
      tags: [Info]
      summary: Get API info
      operationId: getInfo
      responses:
        "200":
          description: API info
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Info"
        default:
          $ref: "#/components/responses/Error"

  /file/{id}/stream:
    get:
      tags: [Files]
      summary: Stream a music file
      operationId: streamFile
      description: Streams the file. Implementations that support streaming should support Range requests and return 206 Partial Content for partial responses.
      parameters:
        - $ref: "#/components/parameters/FileId"
        - name: Range
          in: header
          required: false
          description: Byte range for partial content.
          schema:
            type: string
            example: bytes=0-1023
      responses:
        "200":
          description: Full file stream
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "206":
          description: Partial file stream
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "501":
          description: Streaming is not supported.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        default:
          $ref: "#/components/responses/Error"

  /file/{id}/download:
    get:
      tags: [Files]
      summary: Download a music file
      operationId: downloadFile
      parameters:
        - $ref: "#/components/parameters/FileId"
      responses:
        "200":
          description: File download
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        default:
          $ref: "#/components/responses/Error"

  /songs:
    get:
      tags: [Songs]
      summary: List songs
      operationId: listSongs
      parameters:
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/Include"
        - $ref: "#/components/parameters/LimitIncluded"
        - $ref: "#/components/parameters/SortIncluded"
        - $ref: "#/components/parameters/FieldsSong"
        - $ref: "#/components/parameters/FieldsAlbum"
        - $ref: "#/components/parameters/FieldsArtist"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - $ref: "#/components/parameters/Sort"
      responses:
        "200":
          description: Song list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SongList"
        default:
          $ref: "#/components/responses/Error"

  /songs/lookup:
    get:
      tags: [Songs]
      summary: Look up songs
      operationId: lookupSongs
      description: Returns matching songs in request order. Missing ids are omitted from data. A lookup request should contain at most 100 ids.
      parameters:
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/LookupIds"
        - $ref: "#/components/parameters/LookupIsrc"
        - $ref: "#/components/parameters/Include"
        - $ref: "#/components/parameters/LimitIncluded"
        - $ref: "#/components/parameters/SortIncluded"
        - $ref: "#/components/parameters/FieldsSong"
        - $ref: "#/components/parameters/FieldsAlbum"
        - $ref: "#/components/parameters/FieldsArtist"
      responses:
        "200":
          description: Song collection
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SongCollection"
        default:
          $ref: "#/components/responses/Error"

  /song/{id}:
    get:
      tags: [Songs]
      summary: Get a song
      operationId: getSong
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/Include"
        - $ref: "#/components/parameters/LimitIncluded"
        - $ref: "#/components/parameters/SortIncluded"
        - $ref: "#/components/parameters/FieldsSong"
        - $ref: "#/components/parameters/FieldsAlbum"
        - $ref: "#/components/parameters/FieldsArtist"
      responses:
        "200":
          description: Song
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SongSingle"
        default:
          $ref: "#/components/responses/Error"

  /song/{id}/rel/albums:
    get:
      tags: [Songs]
      summary: Get song albums
      operationId: getSongAlbums
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/FieldsAlbum"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - $ref: "#/components/parameters/Sort"
      responses:
        "200":
          description: Albums related to the song
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AlbumList"
        default:
          $ref: "#/components/responses/Error"

  /song/{id}/rel/artists:
    get:
      tags: [Songs]
      summary: Get song artists
      operationId: getSongArtists
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/FieldsArtist"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - $ref: "#/components/parameters/Sort"
      responses:
        "200":
          description: Artists related to the song
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ArtistList"
        default:
          $ref: "#/components/responses/Error"

  /albums:
    get:
      tags: [Albums]
      summary: List albums
      operationId: listAlbums
      parameters:
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/Include"
        - $ref: "#/components/parameters/LimitIncluded"
        - $ref: "#/components/parameters/SortIncluded"
        - $ref: "#/components/parameters/FieldsAlbum"
        - $ref: "#/components/parameters/FieldsSong"
        - $ref: "#/components/parameters/FieldsArtist"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - $ref: "#/components/parameters/Sort"
      responses:
        "200":
          description: Album list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AlbumList"
        default:
          $ref: "#/components/responses/Error"

  /albums/lookup:
    get:
      tags: [Albums]
      summary: Look up albums
      operationId: lookupAlbums
      description: Returns matching albums in request order. Missing ids are omitted from data. A lookup request should contain at most 100 ids.
      parameters:
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/LookupIds"
        - $ref: "#/components/parameters/LookupUpc"
        - $ref: "#/components/parameters/Include"
        - $ref: "#/components/parameters/LimitIncluded"
        - $ref: "#/components/parameters/SortIncluded"
        - $ref: "#/components/parameters/FieldsAlbum"
        - $ref: "#/components/parameters/FieldsSong"
        - $ref: "#/components/parameters/FieldsArtist"
      responses:
        "200":
          description: Album collection
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AlbumCollection"
        default:
          $ref: "#/components/responses/Error"

  /album/{id}:
    get:
      tags: [Albums]
      summary: Get an album
      operationId: getAlbum
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/Include"
        - $ref: "#/components/parameters/LimitIncluded"
        - $ref: "#/components/parameters/SortIncluded"
        - $ref: "#/components/parameters/FieldsAlbum"
        - $ref: "#/components/parameters/FieldsSong"
        - $ref: "#/components/parameters/FieldsArtist"
      responses:
        "200":
          description: Album
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AlbumSingle"
        default:
          $ref: "#/components/responses/Error"

  /album/{id}/rel/tracks:
    get:
      tags: [Albums]
      summary: Get album tracks
      operationId: getAlbumTracks
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/FieldsSong"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - $ref: "#/components/parameters/Sort"
      responses:
        "200":
          description: Album tracks
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SongList"
        default:
          $ref: "#/components/responses/Error"

  /album/{id}/rel/artists:
    get:
      tags: [Albums]
      summary: Get album artists
      operationId: getAlbumArtists
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/FieldsArtist"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - $ref: "#/components/parameters/Sort"
      responses:
        "200":
          description: Album artists
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ArtistList"
        default:
          $ref: "#/components/responses/Error"

  /artists:
    get:
      tags: [Artists]
      summary: List artists
      operationId: listArtists
      parameters:
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/Include"
        - $ref: "#/components/parameters/LimitIncluded"
        - $ref: "#/components/parameters/SortIncluded"
        - $ref: "#/components/parameters/FieldsArtist"
        - $ref: "#/components/parameters/FieldsSong"
        - $ref: "#/components/parameters/FieldsAlbum"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - $ref: "#/components/parameters/Sort"
      responses:
        "200":
          description: Artist list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ArtistList"
        default:
          $ref: "#/components/responses/Error"

  /artists/lookup:
    get:
      tags: [Artists]
      summary: Look up artists
      operationId: lookupArtists
      description: Returns matching artists in request order. Missing ids are omitted from data. A lookup request should contain at most 100 ids.
      parameters:
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/LookupIds"
        - $ref: "#/components/parameters/Include"
        - $ref: "#/components/parameters/LimitIncluded"
        - $ref: "#/components/parameters/SortIncluded"
        - $ref: "#/components/parameters/FieldsArtist"
        - $ref: "#/components/parameters/FieldsSong"
        - $ref: "#/components/parameters/FieldsAlbum"
      responses:
        "200":
          description: Artist collection
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ArtistCollection"
        default:
          $ref: "#/components/responses/Error"

  /artist/{id}:
    get:
      tags: [Artists]
      summary: Get an artist
      operationId: getArtist
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/Include"
        - $ref: "#/components/parameters/LimitIncluded"
        - $ref: "#/components/parameters/SortIncluded"
        - $ref: "#/components/parameters/FieldsArtist"
        - $ref: "#/components/parameters/FieldsSong"
        - $ref: "#/components/parameters/FieldsAlbum"
      responses:
        "200":
          description: Artist
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ArtistSingle"
        default:
          $ref: "#/components/responses/Error"

  /artist/{id}/rel/tracks:
    get:
      tags: [Artists]
      summary: Get artist tracks
      operationId: getArtistTracks
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/FieldsSong"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - $ref: "#/components/parameters/Sort"
      responses:
        "200":
          description: Artist tracks
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SongList"
        default:
          $ref: "#/components/responses/Error"

  /artist/{id}/rel/albums:
    get:
      tags: [Artists]
      summary: Get artist albums
      operationId: getArtistAlbums
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/FieldsAlbum"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - $ref: "#/components/parameters/Sort"
      responses:
        "200":
          description: Artist albums
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AlbumList"
        default:
          $ref: "#/components/responses/Error"

  /playlists:
    get:
      tags: [Playlists]
      summary: List playlists
      operationId: listPlaylists
      parameters:
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/Include"
        - $ref: "#/components/parameters/LimitIncluded"
        - $ref: "#/components/parameters/SortIncluded"
        - $ref: "#/components/parameters/FieldsPlaylist"
        - $ref: "#/components/parameters/FieldsSong"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - $ref: "#/components/parameters/Sort"
      responses:
        "200":
          description: Playlist list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlaylistList"
        default:
          $ref: "#/components/responses/Error"

  /playlists/lookup:
    get:
      tags: [Playlists]
      summary: Look up playlists
      operationId: lookupPlaylists
      description: Returns matching playlists in request order. Missing ids are omitted from data. A lookup request should contain at most 100 ids.
      parameters:
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/LookupIds"
        - $ref: "#/components/parameters/Include"
        - $ref: "#/components/parameters/LimitIncluded"
        - $ref: "#/components/parameters/SortIncluded"
        - $ref: "#/components/parameters/FieldsPlaylist"
        - $ref: "#/components/parameters/FieldsSong"
      responses:
        "200":
          description: Playlist collection
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlaylistCollection"
        default:
          $ref: "#/components/responses/Error"

  /playlist/{id}:
    get:
      tags: [Playlists]
      summary: Get a playlist
      operationId: getPlaylist
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/Include"
        - $ref: "#/components/parameters/LimitIncluded"
        - $ref: "#/components/parameters/SortIncluded"
        - $ref: "#/components/parameters/FieldsPlaylist"
        - $ref: "#/components/parameters/FieldsSong"
      responses:
        "200":
          description: Playlist
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlaylistSingle"
        default:
          $ref: "#/components/responses/Error"

  /playlist/{id}/rel/tracks:
    get:
      tags: [Playlists]
      summary: Get playlist tracks
      operationId: getPlaylistTracks
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/FieldsSong"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - $ref: "#/components/parameters/Sort"
      responses:
        "200":
          description: Playlist tracks
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SongList"
        default:
          $ref: "#/components/responses/Error"

  /stations/{type}/{id}:
    get:
      tags: [Stations]
      summary: Get a resource station
      operationId: getResourceStation
      description: Gets a generated station seeded by the resource type and the resource id of that type. Station ids may expire and should not be stored permanently by clients.
      parameters:
        - name: type
          in: path
          required: true
          description: Resource type used as the station seed.
          schema:
            type: string
            enum: [song, album, artist]
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/Include"
        - $ref: "#/components/parameters/LimitIncluded"
        - $ref: "#/components/parameters/SortIncluded"
        - $ref: "#/components/parameters/FieldsStation"
        - $ref: "#/components/parameters/FieldsSong"
      responses:
        "200":
          description: Station
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StationSingle"
              examples:
                resourceStation:
                  value:
                    data:
                      id: station_artist_123
                      type: station
                      attributes:
                        name: Artist Station
                        seed:
                          kind: resource
                          type: artist
                          id: artist_123
        default:
          $ref: "#/components/responses/Error"

  /stations/genres/{name}:
    get:
      tags: [Stations]
      summary: Get a genre station
      operationId: getGenreStation
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/Include"
        - $ref: "#/components/parameters/LimitIncluded"
        - $ref: "#/components/parameters/SortIncluded"
        - $ref: "#/components/parameters/FieldsStation"
        - $ref: "#/components/parameters/FieldsSong"
      responses:
        "200":
          description: Station
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StationSingle"
              examples:
                genreStation:
                  value:
                    data:
                      id: station_genre_jazz
                      type: station
                      attributes:
                        name: Jazz Station
                        seed:
                          kind: genre
                          name: jazz
        default:
          $ref: "#/components/responses/Error"

  /stations/popular:
    get:
      tags: [Stations]
      summary: Get the popular station
      operationId: getPopularStation
      parameters:
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/Include"
        - $ref: "#/components/parameters/LimitIncluded"
        - $ref: "#/components/parameters/SortIncluded"
        - $ref: "#/components/parameters/FieldsStation"
        - $ref: "#/components/parameters/FieldsSong"
      responses:
        "200":
          description: Station
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StationSingle"
              examples:
                popularStation:
                  value:
                    data:
                      id: station_popular
                      type: station
                      attributes:
                        name: Popular
                        seed:
                          kind: popular
        default:
          $ref: "#/components/responses/Error"

  /station/{id}:
    get:
      tags: [Stations]
      summary: Get a station
      operationId: getStation
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/Include"
        - $ref: "#/components/parameters/LimitIncluded"
        - $ref: "#/components/parameters/SortIncluded"
        - $ref: "#/components/parameters/FieldsStation"
        - $ref: "#/components/parameters/FieldsSong"
      responses:
        "200":
          description: Station
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StationSingle"
              examples:
                customStation:
                  value:
                    data:
                      id: station_custom_123
                      type: station
                      attributes:
                        name: Custom Station
                        seed:
                          kind: custom
        default:
          $ref: "#/components/responses/Error"

  /station/{id}/tracks:
    get:
      tags: [Stations]
      summary: Get station tracks
      operationId: getStationTracks
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/FieldsSong"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - $ref: "#/components/parameters/Sort"
      responses:
        "200":
          description: Station tracks
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SongList"
        default:
          $ref: "#/components/responses/Error"

  /search:
    get:
      tags: [Search]
      summary: Search multiple resource types
      operationId: search
      parameters:
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/SearchTerm"
        - name: types
          in: query
          required: false
          description: Comma-separated result groups to return.
          schema:
            type: string
            example: song,album,artist,playlist,top
        - $ref: "#/components/parameters/FieldsSong"
        - $ref: "#/components/parameters/FieldsAlbum"
        - $ref: "#/components/parameters/FieldsArtist"
        - $ref: "#/components/parameters/FieldsPlaylist"
        - $ref: "#/components/parameters/SearchLimit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Search results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Search"
        default:
          $ref: "#/components/responses/Error"

  /search/{type}:
    get:
      tags: [Search]
      summary: Search one resource type
      operationId: searchType
      parameters:
        - name: type
          in: path
          required: true
          schema:
            type: string
            enum: [song, album, artist, playlist]
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/SearchTerm"
        - $ref: "#/components/parameters/FieldsSong"
        - $ref: "#/components/parameters/FieldsAlbum"
        - $ref: "#/components/parameters/FieldsArtist"
        - $ref: "#/components/parameters/FieldsPlaylist"
        - $ref: "#/components/parameters/SearchLimit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Typed search results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchTypeResponse"
        default:
          $ref: "#/components/responses/Error"

  /search/suggestions:
    get:
      tags: [Search]
      summary: Get search suggestions
      operationId: getSearchSuggestions
      description: Returns at most 10 suggestions.
      parameters:
        - $ref: "#/components/parameters/SearchTerm"
      responses:
        "200":
          description: Search suggestions
          content:
            application/json:
              schema:
                type: array
                items:
                  type: string
        default:
          $ref: "#/components/responses/Error"

  /explore:
    get:
      tags: [Explore]
      summary: Get explore sections
      operationId: getExplore
      parameters:
        - $ref: "#/components/parameters/Lang"
        - $ref: "#/components/parameters/FieldsSong"
        - $ref: "#/components/parameters/FieldsAlbum"
        - $ref: "#/components/parameters/FieldsArtist"
        - $ref: "#/components/parameters/FieldsPlaylist"
        - $ref: "#/components/parameters/FieldsStation"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Explore sections
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ExploreSection"
        default:
          $ref: "#/components/responses/Error"

components:
  responses:
    Error:
      description: Error response
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

  parameters:
    ResourceId:
      name: id
      in: path
      required: true
      description: Resource id. This can be a local id or an OpenMusicMetadata id like omm:song:123456789.
      schema:
        $ref: "#/components/schemas/Id"

    FileId:
      name: id
      in: path
      required: true
      description: File id.
      schema:
        type: string

    Lang:
      name: lang
      in: query
      required: false
      description: Preferred language as a BCP 47 language tag.
      schema:
        type: string
      examples:
        english:
          value: en-US
        germanSwiss:
          value: de-CH

    Include:
      name: include
      in: query
      required: false
      description: Comma-separated relationships to include. Nested includes use dot notation and may be up to three levels deep from the root resource.
      schema:
        type: string
      examples:
        album:
          value: albums
        albumTracks:
          value: albums,albums.tracks
        deep:
          value: albums,albums.tracks,albums.tracks.artists

    LimitIncluded:
      name: limit[{resource}:{relationship}]
      in: query
      required: false
      allowReserved: true
      description: Limit an included relationship. Replace {resource}:{relationship} with the singular resource type and relationship name, for example limit[album:tracks].
      schema:
        type: integer
        minimum: 1
        maximum: 100
      examples:
        albumTracks:
          value: 10

    SortIncluded:
      name: sort[{resource}:{relationship}]
      in: query
      required: false
      allowReserved: true
      description: Sort an included relationship. Replace {resource}:{relationship} with the singular resource type and relationship name, for example sort[album:tracks]. Supports name, dateAdded, and minus-prefixed descending variants. Song and album relationships also support releaseDate and -releaseDate.
      schema:
        type: string
        enum: [name, -name, dateAdded, -dateAdded, releaseDate, -releaseDate]
      examples:
        albumTracks:
          value: name

    FieldsSong:
      name: fields[song]
      in: query
      required: false
      description: Comma-separated song fields.
      schema:
        type: string
      example: name,artworkUrl

    FieldsAlbum:
      name: fields[album]
      in: query
      required: false
      description: Comma-separated album fields.
      schema:
        type: string
      example: name,artworkUrl

    FieldsArtist:
      name: fields[artist]
      in: query
      required: false
      description: Comma-separated artist fields.
      schema:
        type: string
      example: name,artworkUrl

    FieldsPlaylist:
      name: fields[playlist]
      in: query
      required: false
      description: Comma-separated playlist fields.
      schema:
        type: string
      example: name,description

    FieldsStation:
      name: fields[station]
      in: query
      required: false
      description: Comma-separated station fields.
      schema:
        type: string
      example: name,description

    Limit:
      name: limit
      in: query
      required: false
      description: Maximum number of resources to return. The usual maximum is 100.
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 25

    SearchLimit:
      name: limit
      in: query
      required: false
      description: Maximum number of resources per returned search group. The usual maximum is 100. The top group always returns at most 10 resources.
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 25

    Offset:
      name: offset
      in: query
      required: false
      description: Number of resources to skip.
      schema:
        type: integer
        minimum: 0
        default: 0

    Sort:
      name: sort
      in: query
      required: false
      description: Sort field. Supports name, dateAdded, and minus-prefixed descending variants. dateAdded is the date the resource was added to the server. Song and album result sets also support releaseDate and -releaseDate.
      schema:
        type: string
        enum: [name, -name, dateAdded, -dateAdded, releaseDate, -releaseDate]
      example: name

    LookupIds:
      name: ids
      in: query
      required: false
      description: Comma-separated ids. The order of returned resources should match the request order. Maximum 100 ids.
      schema:
        type: string
      example: 1,omm:song:2

    LookupIsrc:
      name: isrc
      in: query
      required: false
      description: Comma-separated ISRC values.
      schema:
        type: string
      example: USRC17607839,GBARL0600786

    LookupUpc:
      name: upc
      in: query
      required: false
      description: Comma-separated UPC values.
      schema:
        type: string

    SearchTerm:
      name: term
      in: query
      required: true
      description: Search term.
      schema:
        type: string

  schemas:
    Id:
      type: string
      description: Local resource id or OpenMusicMetadata id.
      examples:
        - "123456789"
        - "omm:song:123456789"

    ResourceType:
      type: string
      enum: [song, artist, album, playlist, station]

    Resource:
      type: object
      required: [id, type]
      properties:
        id:
          $ref: "#/components/schemas/Id"
        type:
          $ref: "#/components/schemas/ResourceType"

    ArtworkUrl:
      type: string
      description: Artwork URL. It may contain {w} and {h} placeholders for client-selected width and height.

    ListMeta:
      type: object
      required: [limit, offset]
      properties:
        limit:
          type: integer
          minimum: 0
        offset:
          type: integer
          minimum: 0
        total:
          type: integer
          minimum: 0

    MusicFile:
      type: object
      required: [id, lossless, mimeType, sizeBytes, durationMs]
      properties:
        id:
          type: string
        lossless:
          type: boolean
        mimeType:
          type: string
          example: audio/flac
        sizeBytes:
          type: integer
          minimum: 0
        durationMs:
          type: integer
          minimum: 0
        streamUrl:
          type: string
          format: uri
          description: If omitted, clients can use /file/{id}/stream.
        downloadUrl:
          type: string
          format: uri
          description: If omitted, clients can use /file/{id}/download.
        codec:
          type: string
          example: flac
        bitrateKbps:
          type: integer
          minimum: 0
        sampleRateHz:
          type: integer
          minimum: 0
        channels:
          type: integer
          minimum: 1

    Lyrics:
      type: object
      required: [lang, text]
      properties:
        lang:
          type: string
          description: BCP 47 language tag.
          example: en-US
        text:
          type: string
        ttml:
          type: string
          description: Timed Text Markup Language payload.

    Song:
      allOf:
        - $ref: "#/components/schemas/Resource"
        - type: object
          required: [type, attributes]
          properties:
            type:
              const: song
            attributes:
              type: object
              required: [name, files]
              properties:
                name:
                  type: string
                files:
                  type: object
                  required: [preferred]
                  properties:
                    preferred:
                      $ref: "#/components/schemas/MusicFile"
                    alternatives:
                      type: array
                      items:
                        $ref: "#/components/schemas/MusicFile"
                albumName:
                  type: string
                artistName:
                  type: string
                isrc:
                  type: string
                artworkUrl:
                  $ref: "#/components/schemas/ArtworkUrl"
                trackNumber:
                  type: integer
                  minimum: 1
                discNumber:
                  type: integer
                  minimum: 1
                genres:
                  type: array
                  items:
                    type: string
                releaseDate:
                  type: string
                  description: Date string in YYYY, YYYY-MM, or YYYY-MM-DD form.
                durationMs:
                  type: integer
                  minimum: 0
                lyrics:
                  $ref: "#/components/schemas/Lyrics"
            relationships:
              type: object
              properties:
                albums:
                  $ref: "#/components/schemas/AlbumList"
                artists:
                  $ref: "#/components/schemas/ArtistList"

    Album:
      allOf:
        - $ref: "#/components/schemas/Resource"
        - type: object
          required: [type, attributes]
          properties:
            type:
              const: album
            attributes:
              type: object
              required: [name, trackCount]
              properties:
                name:
                  type: string
                trackCount:
                  type: integer
                  minimum: 0
                artistName:
                  type: string
                artworkUrl:
                  $ref: "#/components/schemas/ArtworkUrl"
                upc:
                  type: string
                genres:
                  type: array
                  items:
                    type: string
                releaseDate:
                  type: string
                  description: Date string in YYYY, YYYY-MM, or YYYY-MM-DD form.
            relationships:
              type: object
              properties:
                tracks:
                  $ref: "#/components/schemas/SongList"
                artists:
                  $ref: "#/components/schemas/ArtistList"

    Artist:
      allOf:
        - $ref: "#/components/schemas/Resource"
        - type: object
          required: [type, attributes]
          properties:
            type:
              const: artist
            attributes:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                artworkUrl:
                  $ref: "#/components/schemas/ArtworkUrl"
            relationships:
              type: object
              properties:
                tracks:
                  $ref: "#/components/schemas/SongList"
                albums:
                  $ref: "#/components/schemas/AlbumList"

    Playlist:
      allOf:
        - $ref: "#/components/schemas/Resource"
        - type: object
          required: [type, attributes]
          properties:
            type:
              const: playlist
            attributes:
              type: object
              required: [name, trackCount]
              properties:
                name:
                  type: string
                trackCount:
                  type: integer
                  minimum: 0
                curatorName:
                  type: string
                artworkUrl:
                  $ref: "#/components/schemas/ArtworkUrl"
                description:
                  type: string
            relationships:
              type: object
              properties:
                tracks:
                  $ref: "#/components/schemas/SongList"

    StationSeed:
      oneOf:
        - type: object
          required: [kind, type, id]
          properties:
            kind:
              const: resource
            type:
              type: string
              enum: [song, album, artist]
            id:
              $ref: "#/components/schemas/Id"
        - type: object
          required: [kind, name]
          properties:
            kind:
              const: genre
            name:
              type: string
        - type: object
          required: [kind]
          properties:
            kind:
              const: popular
        - type: object
          required: [kind]
          properties:
            kind:
              const: custom

    Station:
      allOf:
        - $ref: "#/components/schemas/Resource"
        - type: object
          required: [type, attributes]
          properties:
            type:
              const: station
            attributes:
              type: object
              required: [name, seed]
              properties:
                name:
                  type: string
                seed:
                  $ref: "#/components/schemas/StationSeed"
                artworkUrl:
                  $ref: "#/components/schemas/ArtworkUrl"
                description:
                  type: string
            relationships:
              type: object
              properties:
                tracks:
                  $ref: "#/components/schemas/SongList"

    SongSingle:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/Song"

    AlbumSingle:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/Album"

    ArtistSingle:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/Artist"

    PlaylistSingle:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/Playlist"

    StationSingle:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/Station"

    SongList:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Song"
        meta:
          $ref: "#/components/schemas/ListMeta"

    AlbumList:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Album"
        meta:
          $ref: "#/components/schemas/ListMeta"

    ArtistList:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Artist"
        meta:
          $ref: "#/components/schemas/ListMeta"

    PlaylistList:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Playlist"
        meta:
          $ref: "#/components/schemas/ListMeta"

    StationList:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Station"
        meta:
          $ref: "#/components/schemas/ListMeta"

    SongCollection:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Song"

    AlbumCollection:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Album"

    ArtistCollection:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Artist"

    PlaylistCollection:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Playlist"

    Search:
      type: object
      properties:
        songs:
          $ref: "#/components/schemas/SongList"
        albums:
          $ref: "#/components/schemas/AlbumList"
        artists:
          $ref: "#/components/schemas/ArtistList"
        playlists:
          $ref: "#/components/schemas/PlaylistList"
        top:
          type: object
          required: [data, meta]
          properties:
            data:
              type: array
              items:
                oneOf:
                  - $ref: "#/components/schemas/Song"
                  - $ref: "#/components/schemas/Album"
                  - $ref: "#/components/schemas/Artist"
                  - $ref: "#/components/schemas/Playlist"
            meta:
              $ref: "#/components/schemas/ListMeta"

    SearchTypeResponse:
      oneOf:
        - $ref: "#/components/schemas/SongList"
        - $ref: "#/components/schemas/AlbumList"
        - $ref: "#/components/schemas/ArtistList"
        - $ref: "#/components/schemas/PlaylistList"

    ExploreSection:
      type: object
      required: [title, type, data]
      properties:
        title:
          type: string
        type:
          $ref: "#/components/schemas/ResourceType"
        data:
          type: array
          items:
            oneOf:
              - $ref: "#/components/schemas/Song"
              - $ref: "#/components/schemas/Album"
              - $ref: "#/components/schemas/Artist"
              - $ref: "#/components/schemas/Playlist"
              - $ref: "#/components/schemas/Station"

    Info:
      type: object
      required: [branding, openmusic]
      properties:
        branding:
          type: object
          required: [name]
          properties:
            name:
              type: string
            description:
              type: string
            logoUrl:
              type: string
              format: uri
            shortName:
              type: string
            themeHex:
              type: string
              pattern: "^#[0-9A-Fa-f]{6}$"
            website:
              type: string
              format: uri
            email:
              type: string
              format: email
            location:
              type: string
              description: ISO 3166-1 alpha-2 country code.
              minLength: 2
              maxLength: 2
            operator:
              type: string
        openmusic:
          type: object
          required: [version, capabilities]
          properties:
            version:
              type: string
              enum: ["1.0"]
            capabilities:
              type: array
              items:
                type: string
                enum: [streaming, stations, explore]
        authentication:
          type: object
          properties:
            methods:
              type: array
              items:
                $ref: "#/components/schemas/AuthenticationMethod"

    AuthenticationMethod:
      oneOf:
        - type: object
          required: [type]
          properties:
            type:
              const: none
        - type: object
          required: [type]
          properties:
            type:
              const: bearer
            createTokenUrl:
              type: string
              format: uri
        - type: object
          required: [type, in, name]
          properties:
            type:
              const: api_key
            in:
              type: string
              enum: [header, query]
            name:
              type: string
            createApiKeyUrl:
              type: string
              format: uri
        - type: object
          required: [type, authorizationUrl, tokenUrl]
          properties:
            type:
              const: oauth2
            authorizationUrl:
              type: string
              format: uri
            tokenUrl:
              type: string
              format: uri

    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [status, message]
          properties:
            status:
              type: integer
              minimum: 100
              maximum: 599
            message:
              type: string
            details:
              type: array
              items:
                $ref: "#/components/schemas/ErrorDetail"
            requestId:
              type: string

    ErrorDetail:
      type: object
      required: [message]
      properties:
        message:
          type: string
        param:
          type: string
        value: {}
