Skip to main content

The Jellyfin API - A Broad Overview

<time datetime="2022-05-21 13:43:34 &#43;0100 &#43;0100">21 May 2022</time><span class="px-2 text-primary-500">&middot;</span><span title="Reading time">7 mins</span>

One of the nicest things about Jellyfin is that its API is totally open for you to do whatever you want. You could make new clients, CLI tools to manage your library, or basically anything involving talking to Jellyfin. This is great, but if you’ve ever tried to make anything, you may have noticed the general lack of documentation about how the Jellyfin API actually works. There’s the daunting API spec, but that doesn’t actually explain where to start or how anything connects together. When making Finamp, I actually used the Emby docs to start with. Having an overview of how the Jellyfin API works would have been invaluable for Finamp’s early development, so I thought I’d write something up for anyone thinking of making something for Jellyfin.

Note that I’m talking from the viewpoint of sending HTTP requests to Jellyfin manually - I’ve never used the official API clients. You may want to check Jellyfin’s GitHub first to see if they’ve done the hard work for you.

Authentication #

Most stuff in the Jellyfin API needs authentication. This is done via the Authorization header.

Authorization lets Jellyfin know what your device is. It consists of the following segments:

  • Client - The name of your client
  • Device - A human readable name for the device
  • DeviceId - A unique identifier for the device
  • Version - The version of the client
  • Token - The auth token

As for formatting, it’s just MediaBrowser key=value, .... Here’s what Jellyfin Web sends:

MediaBrowser Client="Jellyfin Web", Device="Firefox", DeviceId="TW96aWxsYS81LjAgKFgxMTsgTGludXggeDg2XzY0OyBydjo5NC4wKSBHZWNrby8yMDEwMDEwMSBGaXJlZm94Lzk0LjB8MTYzODA1MzA2OTY4Mw11", Version="10.7.6", Token="notputtingmyactualtokenherebutitbasicallylookslikeahash"

Note that there’s also X-Emby-Token, where you just send the token. Jellyfin uses this as a fallback if it doesn’t find the token in Authorization. The only really I’m really mentioning this is that Finamp uses it for some reason.

Getting Authenticated #

For users you’ll want to use the /Users/AuthenticateByName endpoint. You send a username and password, and Jellyfin returns an AuthenticationResult object. If you’re writing your own API client, this is when you’ll slowly realise how much pain you’ve decided to put yourself through as you look at how big some of the objects are in AuthenticationResult. Seriously, I nearly stopped working on Finamp right at the start because manually writing classes was miserable. If only I could get openapi-generator to work with the Emby spec (Jellyfin didn’t have a spec when I started).

One thing that may confuse you at first is how Jellyfin asks for the username and password. The spec shows this:

  "Username": "string",
  "Pw": "string",
  "Password": "string"

What’s with the two password strings? This is a weird historical thing from Emby, which for some reason allowed clients to send a sha1 hash of the password instead of just sending the password in plaintext like a normal API. Maybe this was to try and hide the password when being sent to a server over plain HTTP? Either way, don’t do that in your client, just send the password in plaintext in the Pw string, like so:

  "Username": "AzureDiamond",
  "Pw": "hunter2"

Once you’ve managed to login, you’ll be able to get the access token from the AccessToken string in the response JSON. From there, you can make authenticated requests!

The Library and BaseItemDtos #

The first thing you’re probably going to want to do is get the views on your server. A view is what the Jellyfin API calls a library, such as Movies, TV Shows and so on. For this, you’ll want to use the /Users/{userId}/Views endpoint. You’ll notice that a lot of these library endpoints are for a specific user, so make sure you got the user ID from your AccessToken’s User.Id. This endpoint returns a list of BaseItemDtos for each view in the server.

BaseItemDto will come up a lot. Pretty much everything vaguely related to items is a BaseItemDto. Videos, songs, views, and even stuff like actors are stored as BaseItemDtos. This leads to the schema being absolutely massive since there are like 10 different things that a BaseItemDto could be (and it also has stuff like 3D format lol).

Once you’ve got a view, you’ll want to use the /Users/{userId}/Items endpoint to get the items in the view. This is what the web interface calls to get all of your TV shows when you go into that library. This endpoint has a lot of query parameters available, which are listed in the spec. The most important one is ParentId, which tells Jellyfin what you actually want to get the items for. In this case, we want to get the items for a given view, so we use the view’s ID (from its BaseItemDto).

From there, you can then use the same endpoint to go deeper. I’m not an expert on how it works for stuff like TV and movies, but for music you just use the same endpoint on an album to get its songs. There are a few other endpoints, such as /Genres to get all genres and /Playlists/{playlistId}/Items for getting the items in a playlist, but generally the items endpoint should work.

Playback (in the Case of Music) #

You probably want to play some media in your client. I’m only going to cover music here since that’s what I know, but I’d assume its similar for other types. Basically, you want the /Audio/{itemId}/universal endpoint. This returns a stream for the given music item. There are quite a few query parameters to modify what Jellyfin returns, but here are the important ones:

  • Container - the containers that you want to accept. Finamp sets this to "opus,webm|opus,mp3,aac,m4a|aac,m4a|alac,m4b|aac,flac,webma,webm|webma,wav,ogg", but you should probably do it properly and give a list of supported containers for whatever audio package you’re using.
  • MaxStreamingBitrate - the maximum bitrate the audio should be. In Finamp, I swap out the user’s transcoding bitrate or 999999999 depending on whether or not the user wants to transcode. This could be used to set a maximum bitrate so that, for example, CD FLACs direct play but massive DSFs get transcoded.
  • AudioCodec - The codec that Jellyfin will transcode to. You probably want to set this to "aac", but other codecs like Vorbis should also work.
  • TranscodingContainer - I have this set to "ts", probably because Jellyfin Web did.
  • TranscodingProtocol - How Jellyfin will send transcoded audio. If possible, you should use "hls" for seeking support and a more robust streaming experience in general. Some audio packages may need special parameters to start a HLS player, so make sure you figure that out if you’re adding transcoding support.

If you’ve done everything correctly, you should now be able to stream music from your Jellyfin server!

Images #

Unless you’re making a super minimal client for your Arch/i3 setup, you probably want to show images in your client. Jellyfin has many image types, and I don’t fully understand the system. The endpoint to get images is /Items/{itemId}/Images/{imageType}. For imageType, you probably want Primary, which is stuff like album art in the case of music. You can usually get away by just requesting an item’s primary image, but there are edge cases where this may not work, such as songs that don’t have their own image. For Finamp, I wrote this function to get the most appropriate image ID for a given item. It was adapted from how Jellyfin Web does it, but I’ve forgotten where the actual code for that is. I’ll update this post if I find it :)

Jellyfin Web is Your Friend #

One of the best ways to find out how something works is by checking how Jellyfin’s web client does it. Whenever I implement a non-trivial feature into Finamp, I always try and emulate how the web client does it by checking what it sends to the server and what it returns. This is great for figuring out what endpoint you need and any important query parameters you need. If you don’t know where you’re looking, open DevTools (F12), and go to the network tab.

Screenshot of DevTools&rsquo;s network tab on Jellyfin Web

Conclusion #

Hopefully this was helpful in understanding how Jellyfin works. I’ve only covered a few things since Jellyfin is massive, but once you get the hang of it you should be able to use Jellyfin Web to figure stuff out. If you need more information, the Jellyfin Matrix channels are great for asking questions. Please let me know if I’ve got anything wrong or brushed over any important details!