diff --git a/docs/swagger.yml b/docs/swagger.yml
index de809242d3eb0815e177598357116d2cb15a37e0..4ef60de6fceb010bc9eeafc60b0c972be062507c 100644
--- a/docs/swagger.yml
+++ b/docs/swagger.yml
@@ -1,6 +1,24 @@
+# Undocumented endpoints:
+#  /api/v1/settings
+#  /api/v1/activity
+#  /api/v1/playlists
+#  /api/v1/playlist-tracks
+#  /api/v1/search
+#  /api/v1/radios
+#  /api/v1/history
+#  /api/v1/users
+
 openapi: "3.0.0"
 info:
-  description: "Documentation for [Funkwhale](https://funkwhale.audio) API. The API is **not** stable yet."
+  description: |
+    Interactive documentation for [Funkwhale](https://funkwhale.audio) API.
+
+    The API is **not** freezed yet, but we will document breaking changes in our changelog,
+    and try to avoid those as much as possible.
+
+    For more targeted guides regarding API usage, and especially authentication, please
+    refer to [https://docs.funkwhale.audio/api.html](https://docs.funkwhale.audio/api.html)
+
   version: "1.0.0"
   title: "Funkwhale API"
 
@@ -30,11 +48,23 @@ components:
 security:
   - jwt: []
 
+tags:
+  - name: Auth and security
+    description: Login, logout and authorization endpoints
+  - name: Library and metadata
+    description: Information and metadata about musical and audio entities (albums, tracks, artists, etc.)
+  - name: Uploads and audio content
+    description: Manipulation and uploading of audio files
+    externalDocs:
+      url: https://docs.funkwhale.audio/users/managing.html
+  - name: Content curation
+    description: Favorites, playlists, radios
+
 paths:
   /token/:
     post:
       tags:
-        - "auth"
+        - "Auth and security"
       description:
         Obtain a JWT token you can use for authenticating your next requests.
       security: []
@@ -61,7 +91,7 @@ paths:
     get:
       summary: List artists
       tags:
-        - "artists"
+        - "Library and metadata"
       parameters:
         - name: "q"
           in: "query"
@@ -70,7 +100,6 @@ paths:
           schema:
             required: false
             type: "string"
-            example: "carpenter"
         - allOf:
             - $ref: "#/parameters/Ordering"
             - default: "-creation_date"
@@ -106,7 +135,7 @@ paths:
         - $ref: "#/parameters/ObjectId"
 
       tags:
-        - "artists"
+        - "Library and metadata"
       responses:
         200:
           content:
@@ -127,8 +156,7 @@ paths:
         - $ref: "#/parameters/PageSize"
 
       tags:
-        - "artists"
-        - "libraries"
+        - "Library and metadata"
       responses:
         200:
           content:
@@ -145,7 +173,7 @@ paths:
     get:
       summary: List albums
       tags:
-        - "albums"
+        - "Library and metadata"
       parameters:
         - name: "q"
           in: "query"
@@ -154,7 +182,6 @@ paths:
           schema:
             required: false
             type: "string"
-            example: "carpenter"
         - name: "artist"
           in: "query"
           default: null
@@ -198,7 +225,7 @@ paths:
         - $ref: "#/parameters/ObjectId"
 
       tags:
-        - "albums"
+        - "Library and metadata"
       responses:
         200:
           content:
@@ -220,8 +247,7 @@ paths:
         - $ref: "#/parameters/PageSize"
 
       tags:
-        - "albums"
-        - "libraries"
+        - "Library and metadata"
       responses:
         200:
           content:
@@ -238,7 +264,7 @@ paths:
     get:
       summary: List tracks
       tags:
-        - "tracks"
+        - "Library and metadata"
       parameters:
         - name: "q"
           in: "query"
@@ -247,7 +273,6 @@ paths:
           schema:
             required: false
             type: "string"
-            example: "carpenter"
         - name: "artist"
           in: "query"
           default: null
@@ -256,6 +281,13 @@ paths:
             required: false
             type: "integer"
             format: "int64"
+        - name: "favorites"
+          in: "query"
+          default: null
+          description: "filter/exclude tracks favorited by the current user"
+          schema:
+            required: false
+            type: "boolean"
         - name: "album"
           in: "query"
           default: null
@@ -302,12 +334,12 @@ paths:
                           $ref: "#/definitions/Track"
   /tracks/{id}/:
     get:
-      summary: Retrieve a single track
       parameters:
         - $ref: "#/parameters/ObjectId"
+      summary: Retrieve a single track
 
       tags:
-        - "tracks"
+        - "Library and metadata"
       responses:
         200:
           content:
@@ -329,8 +361,7 @@ paths:
         - $ref: "#/parameters/PageSize"
 
       tags:
-        - "tracks"
-        - "libraries"
+        - "Library and metadata"
       responses:
         200:
           content:
@@ -342,12 +373,71 @@ paths:
             application/json:
               schema:
                 $ref: "#/definitions/ResourceNotFound"
+  /listen/{uuid}/:
+    get:
+      summary: Download the audio file matching the given track uuid
+      description: |
+        Given a track uuid (and not ID), return the first found audio file
+        accessible by the user making the request.
+
+        In case of a remote upload, this endpoint will fetch the audio file from the remote
+        and cache it before sending the response.
+
+      parameters:
+        - name: uuid
+          in: path
+          required: true
+          description: Track uuid
+          schema:
+            type: "string"
+            format: "uuid"
+        - name: to
+          in: query
+          required: false
+          description: |
+            If specified, the endpoint will return a transcoded version of the original
+            audio file.
+
+            Since transcoding happens on the fly, it can significantly increase response time,
+            and it's recommended to request transcoding only for files that are not playable
+            by the client.
+
+            This endpoint support bytess-range requests.
+          schema:
+            $ref: "#/properties/transcode_options"
+        - name: upload
+          in: query
+          required: false
+          summary: An upload uuid
+          description: |
+            If specified, will return the audio for the given upload uuid.
+
+            This is useful for tracks that have multiple uploads available.
+
+          schema:
+            type: string
+            format: uuid
 
+      tags:
+        - "Library and metadata"
+      responses:
+        200:
+          content:
+            '*/*':
+              description: "Audio file, as binary data"
+              schema:
+                type: string
+                format: binary
+        404:
+          content:
+            application/json:
+              schema:
+                $ref: "#/definitions/ResourceNotFound"
   /licenses/:
     get:
       summary: List licenses
       tags:
-        - "licenses"
+        - "Library and metadata"
       parameters:
         - $ref: "#/parameters/PageNumber"
         - $ref: "#/parameters/PageSize"
@@ -366,19 +456,19 @@ paths:
                           $ref: "#/definitions/License"
 
   /licenses/{code}/:
+    parameters:
+      - name: code
+        in: path
+        description: License code
+        required: true
+        schema:
+          type: string
+          example: cc0-1.0
     get:
       summary: Retrieve a single license
-      parameters:
-        - name: code
-          in: path
-          description: License code
-          required: true
-          schema:
-            type: string
-            example: cc0-1.0
 
       tags:
-        - "licenses"
+        - "Library and metadata"
       responses:
         200:
           content:
@@ -391,6 +481,263 @@ paths:
               schema:
                 $ref: "#/definitions/ResourceNotFound"
 
+  /libraries/:
+    get:
+      summary: List owned libraries
+      tags:
+        - "Uploads and audio content"
+      parameters:
+        - $ref: "#/parameters/PageNumber"
+        - $ref: "#/parameters/PageSize"
+      responses:
+        200:
+          content:
+            application/json:
+              schema:
+                allOf:
+                  - $ref: "#/definitions/ResultPage"
+                  - type: "object"
+                    properties:
+                      results:
+                        type: "array"
+                        items:
+                          $ref: "#/definitions/OwnedLibrary"
+    post:
+      tags:
+        - "Uploads and audio content"
+      description:
+        Create a new library
+      responses:
+        201:
+          $ref: "#/responses/201"
+        400:
+          $ref: "#/responses/400"
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/definitions/OwnedLibraryCreate"
+
+  /libraries/{uuid}/:
+    parameters:
+      - name: uuid
+        in: path
+        required: true
+        schema:
+          type: "string"
+          format: "uuid"
+    get:
+      summary: Retrieve a library
+      tags:
+        - "Uploads and audio content"
+      responses:
+        200:
+          content:
+            application/json:
+              schema:
+                $ref: "#/definitions/OwnedLibrary"
+    post:
+      summary: Update a library
+      tags:
+        - "Uploads and audio content"
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/definitions/OwnedLibraryCreate"
+      responses:
+        201:
+          content:
+            application/json:
+              schema:
+                $ref: "#/definitions/OwnedLibrary"
+    delete:
+      summary: Delete a library and all associated uploads
+      description: |
+        This will delete the library, all associated uploads, follows, and broadcast
+        the event on the federation.
+      tags:
+        - "Uploads and audio content"
+      responses:
+        204:
+          $ref: "#/responses/204"
+
+  /uploads/:
+    get:
+      summary: List owned uploads
+      tags:
+        - "Uploads and audio content"
+      parameters:
+        - name: "q"
+          in: "query"
+          default: null
+          description: "Search query used to filter uploads"
+          schema:
+            required: false
+            type: "string"
+            example: "Dire straits"
+        - $ref: "#/parameters/PageNumber"
+        - $ref: "#/parameters/PageSize"
+      responses:
+        200:
+          content:
+            application/json:
+              schema:
+                allOf:
+                  - $ref: "#/definitions/ResultPage"
+                  - type: "object"
+                    properties:
+                      results:
+                        type: "array"
+                        items:
+                          $ref: "#/definitions/OwnedUpload"
+    post:
+      tags:
+        - "Uploads and audio content"
+      description:
+        Upload a new file in a library. The event will be broadcasted on federation,
+        according to the library visibility and followers.
+      responses:
+        201:
+          $ref: "#/responses/201"
+        400:
+          $ref: "#/responses/400"
+      requestBody:
+        required: true
+        content:
+          multipart/form-data:
+            schema:
+              type: object
+              properties:
+                library:
+                  type: string
+                  format: uuid
+                  description: "The library in which the audio should be stored"
+                import_reference:
+                  type: string
+                  example: "Import launched via API client on 04/19"
+                source:
+                  type: string
+                  example: "upload://filename.mp3"
+                audio_file:
+                  type: string
+                  format: binary
+
+  /uploads/{uuid}/:
+    parameters:
+      - name: uuid
+        in: path
+        required: true
+        schema:
+          type: "string"
+          format: "uuid"
+    get:
+      summary: Retrieve an upload
+      tags:
+        - "Uploads and audio content"
+      responses:
+        200:
+          content:
+            application/json:
+              schema:
+                $ref: "#/definitions/OwnedUpload"
+    delete:
+      summary: Delete an upload
+      description: |
+        This will delete the upload from the server and broadcast the event
+        on the federation.
+      tags:
+        - "Uploads and audio content"
+      responses:
+        204:
+          $ref: "#/responses/204"
+
+  /favorites/tracks/:
+    get:
+      tags:
+        - "Content curation"
+      parameters:
+        - name: "q"
+          in: "query"
+          default: null
+          description: "Search query used to filter favorites"
+          schema:
+            required: false
+            type: "string"
+        - name: "user"
+          in: "query"
+          default: null
+          description: "Limit results to favorites belonging to the given user"
+          schema:
+            $ref: "#/parameters/ObjectId"
+        - $ref: "#/parameters/PageNumber"
+        - $ref: "#/parameters/PageSize"
+      responses:
+        200:
+          content:
+            application/json:
+              schema:
+                allOf:
+                  - $ref: "#/definitions/ResultPage"
+                  - type: "object"
+                    properties:
+                      results:
+                        type: "array"
+                        items:
+                          $ref: "#/definitions/TrackFavorite"
+    post:
+      summary: Mark the given track as favorite
+      tags:
+        - "Content curation"
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              type: "object"
+              properties:
+                track:
+                  type: "integer"
+                  format: "int64"
+                  example: 98
+      responses:
+        201:
+          content:
+            application/json:
+              schema:
+                type: "object"
+                properties:
+                  id:
+                    type: "integer"
+                    format: "int64"
+                    example: 876
+                  track:
+                    type: "integer"
+                    format: "int64"
+                    example: 98
+                  creation_date:
+                    $ref: "#/properties/creation_date"
+  /favorites/tracks/remove/:
+    post:
+      summary: Remove the given track from favorites
+      tags:
+        - "Content curation"
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              type: "object"
+              properties:
+                track:
+                  type: "integer"
+                  format: "int64"
+                  example: 98
+      responses:
+        204:
+          $ref: "#/responses/204"
 
 parameters:
   ObjectId:
@@ -434,11 +781,65 @@ parameters:
       required: false
       type: "boolean"
 
+responses:
+  201:
+    description: Successfully created
+  204:
+    description: Successfully deleted
+  400:
+    description: Bad request
+
 properties:
   mbid:
     type: "string"
-    formats: "uuid"
+    format: "uuid"
     description: "A musicbrainz ID"
+  creation_date:
+    type: "string"
+    format: "date-time"
+  privacy_level:
+    type: string
+    example: "me"
+    description: |
+     * `me`: private
+     * `instance`: accessible by local users
+     * `everyone`: public (including over federation)
+    enum:
+      - "me"
+      - "instance"
+      - "everyone"
+  fid:
+    type: "string"
+    format: "uri"
+    description: "Federation ID"
+    example: "https://my.instance/federation/music/libraries/3fa85f64-5717-4562-b3fc-2c963f66afa6"
+  audio_mimetype:
+    type: string
+    example: "audio/ogg"
+    enum:
+      - "audio/ogg"
+      - "audio/mpeg"
+      - "audio/x-flac"
+      - "audio/flac"
+  import_status:
+    type: string
+    example: "finished"
+    enum:
+      - "pending"
+      - "finished"
+      - "errored"
+      - "skipped"
+    description: |
+     * `pending`: waiting to be processed by the server
+     * `finished`: successfully processed by the server
+     * `errored`: couldn't be processed by the server (e.g because of a tagging issue)
+     * `skipped`: processed by the server but skipped, because considered as a duplicate of an existing upload
+
+  transcode_options:
+    type: string
+    enum:
+      - "ogg"
+      - "mp3"
 
 definitions:
   ResultPage:
@@ -776,23 +1177,131 @@ definitions:
         example: 128000
         description: "Bitrate of the file, in bytes/s"
       mimetype:
-        type: string
-        example: "audio/ogg"
-        enum:
-          - "audio/ogg"
-          - "audio/mpeg"
-          - "audio/x-flac"
-          - "audio/flac"
+        $ref: "#/properties/audio_mimetype"
       extension:
         type: string
         example: "ogg"
         description: "File extension of the upload"
-
+      filename:
+        type: "string"
+        example: "Myfile.mp3"
       listen_url:
         type: "string"
         format: "uri"
         description: "URL to stream the upload"
 
+  OwnedLibraryCreate:
+    type: "object"
+    properties:
+      password:
+        type: "name"
+        example: "My new library"
+      description:
+        required: false
+        type: "string"
+        example: "Lots of interesting content"
+      privacy_level:
+        $ref: "#/properties/privacy_level"
+
+  OwnedLibrary:
+    type: "object"
+    properties:
+      uuid:
+        type: string
+        format: uuid
+      fid:
+        $ref: "#/properties/fid"
+      name:
+        type: "string"
+        example: "My Creative Commons library"
+      description:
+        type: "string"
+        example: "All content is under CC-BY"
+      creation_date:
+        $ref: "#/properties/creation_date"
+      privacy_level:
+        $ref: "#/properties/privacy_level"
+      uploads_count:
+        type: "integer"
+        format: "int64"
+        example: 34
+      size:
+        type: "integer"
+        format: "int64"
+        example: 678917000
+        description: "Total size of uploads in the library, in bytes"
+
+  OwnedUpload:
+    type: "object"
+    allOf:
+      - $ref: "#/definitions/Upload"
+      - type: "object"
+        properties:
+          import_status:
+            $ref: "#/properties/import_status"
+          track:
+            $ref: "#/definitions/Track"
+          library:
+            $ref: "#/definitions/OwnedLibrary"
+          source:
+            type: "string"
+            example: "upload://myfile.mp3"
+          import_reference:
+            type: "string"
+            example: "Import launched via web UI on 03/18"
+  TrackFavorite:
+    type: "object"
+    properties:
+      id:
+        type: "integer"
+        format: "int64"
+        example: 876
+      track:
+        $ref: "#/definitions/Track"
+      user:
+        $ref: "#/definitions/User"
+      creation_date:
+        $ref: "#/properties/creation_date"
+  User:
+    type: "object"
+    properties:
+      id:
+        type: "integer"
+        format: "int64"
+        example: 23
+      username:
+        type: "string"
+        example: "alice"
+      name:
+        type: "string"
+        example: "Alice Kingsley"
+      avatar:
+        $ref: "#/definitions/Avatar"
+
+  Avatar:
+    type: "object"
+    properties:
+      original:
+        type: "string"
+        format: "uri"
+        description: "Original image URL"
+        example: "http://yourinstance/media/users/avatars/92/49/60/b3c-4736-43b3-bb5c-ed7a99ac6996.jpg"
+      square_crop:
+        type: "string"
+        format: "uri"
+        description: "400x400 thumbnail URL"
+        example: "http://yourinstance/media/__sized__/users/avatars/92/49/60/b3c-4736-43b3-bb5c-ed7a99ac6996-crop-c0-5__0-5-400x400-70.jpg"
+      small_square_crop:
+        type: "string"
+        format: "uri"
+        description: "50x50 thumbnail URL"
+        example: "http://yourinstance/media/__sized__/users/avatars/92/49/60/b3c-4736-43b3-bb5c-ed7a99ac6996-crop-c0-5__0-5-50x50-70.jpg"
+      medium_square_crop:
+        type: "string"
+        format: "uri"
+        description: "200x200 thumbnail URL"
+        example: "http://yourinstance/media/__sized__/users/avatars/92/49/60/b3c-4736-43b3-bb5c-ed7a99ac6996-crop-c0-5__0-5-200x200-70.jpg"
+
   ResourceNotFound:
     type: "object"
     properties: