{"id":31646465,"url":"https://github.com/webysther/jellyfin-sdk-python","last_synced_at":"2026-05-15T01:37:51.297Z","repository":{"id":312038221,"uuid":"1046043749","full_name":"webysther/jellyfin-sdk-python","owner":"webysther","description":"A Python SDK for Jellyfin","archived":false,"fork":false,"pushed_at":"2026-01-23T21:50:03.000Z","size":1563,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-18T07:19:44.471Z","etag":null,"topics":["api","api-client","dataclass","openapi","openapi-specification","openapi3","python","sdk","wrapper-api"],"latest_commit_sha":null,"homepage":"https://url.webysther.org/jellyfin-sdk-docs","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/webysther.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-08-28T05:27:49.000Z","updated_at":"2026-02-03T04:48:50.000Z","dependencies_parsed_at":"2025-08-28T12:27:04.804Z","dependency_job_id":null,"html_url":"https://github.com/webysther/jellyfin-sdk-python","commit_stats":null,"previous_names":["webysther/jellyfin-sdk-python"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/webysther/jellyfin-sdk-python","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webysther%2Fjellyfin-sdk-python","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webysther%2Fjellyfin-sdk-python/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webysther%2Fjellyfin-sdk-python/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webysther%2Fjellyfin-sdk-python/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/webysther","download_url":"https://codeload.github.com/webysther/jellyfin-sdk-python/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webysther%2Fjellyfin-sdk-python/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33050480,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T13:14:54.681Z","status":"online","status_checked_at":"2026-05-14T02:00:06.663Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","api-client","dataclass","openapi","openapi-specification","openapi3","python","sdk","wrapper-api"],"created_at":"2025-10-07T05:50:51.086Z","updated_at":"2026-05-15T01:37:51.290Z","avatar_url":"https://github.com/webysther.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eJellyfin SDK for Python\u003c/h1\u003e\n\n---\n\n\u003cp align=\"center\"\u003e\n\u003cimg alt=\"Logo Banner\" src=\"https://raw.githubusercontent.com/jellyfin/jellyfin-ux/master/branding/SVG/banner-logo-solid.svg?sanitize=true\"/\u003e\n\u003c/p\u003e\n\nA High-level Wrapper for OpenAPI Generated Bindings for Jellyfin API.\n\n\u003e Warning: API changes will occur only in the final classes, bindings and legacy don't change\n\nThe main goal of this project is to be a wrapper for the API but with high level of abstraction using the power of [OpenAPI Specs bindings](https://github.com/OpenAPITools/openapi-generator) and good patterns such as [Inversion of Control](https://en.wikipedia.org/wiki/Inversion_of_control), [Method Chaining](https://en.wikipedia.org/wiki/Method_chaining), [JSONPath](https://en.wikipedia.org/wiki/JSONPath), and more.\n\nMain unique features:\n- Enables targeting a specific Jellyfin server version to ensure compatibility and prevent breaking changes.\n- Supports accessing multiple servers, each potentially running different Jellyfin versions.\n- Allows reducing the level of abstraction to access advanced or unavailable options through lower-level interfaces.\n- Works like [AWS CDK Constructs Level](https://blog.shikisoft.com/aws-cdk-construct-levels/), more abstraction, more simple.\n\n\u003cdiv align=\"center\"\u003e\n\u003cimg width=\"1440\" height=\"932\" alt=\"image\" src=\"https://github.com/user-attachments/assets/bb57410b-7c55-4055-a944-4e8e06ac79ce\" /\u003e\n\u003cimg width=\"1440\" height=\"592\" alt=\"image\" src=\"https://github.com/user-attachments/assets/a01a2ac2-4622-4053-8ed1-ffe6b84544fe\" /\u003e\n\n\n\n\n\u003cem\u003e\u003csmall\u003eHow modules work together\u003c/small\u003e\u003c/em\u003e\n\u003c/div\u003e\n\n\u003csmall\u003e\nThere is a thin layer that builds the high-level abstraction (green box/jellyfin) consuming only the bindings that already contain dataclasses and api built using the OpenAPI Generator (blue box/generated), which in turn also allows use only if the user requests jellyfin_apiclient_python (purple box/legacy) to allow for refactoring and incremental development. Both legacy and generated have classes that allow low-level access, being practically just an envelope method for requests that must communicate with the actual jellyfin API (lilac box).\n\u003c/small\u003e\n\nThis project is mainly inspired by good python library like these:\n- [tmdbsimple](https://github.com/celiao/tmdbsimple)\n- [plexapi](https://github.com/pushingkarmaorg/python-plexapi)\n- [tensorflow](https://github.com/tensorflow/tensorflow)\n\n## Install\n\n```sh\npip install jellyfin-sdk\n```\n\nor\n\n```sh\nuv add jellyfin-sdk\n```\n\n## Usage\n\n### Drop-in replacement for [jellyfin-apiclient-python](https://github.com/jellyfin/jellyfin-apiclient-python)\n\nThis library includes the old legacy client (which is almost unmaintained) to help with migration:\n\n```sh\npip uninstall jellyfin-apiclient-python\npip install jellyfin-sdk[legacy]\n```\n\n```python\n# change from\nfrom jellyfin_apiclient_python import JellyfinClient\nfrom jellyfin_apiclient_python.api import API\n\n# to this\nfrom jellyfin.legacy import JellyfinClient\nfrom jellyfin.legacy.api import API\n```\n\nList of current problems in [legacy project](https://github.com/jellyfin/jellyfin-apiclient-python) already fixed here:\n\n- [Fixed missing exception when user id is missing#70](https://github.com/jellyfin/jellyfin-apiclient-python/pull/70)\n\n```python\nimport jellyfin\n\njellyfin.api(os.getenv(\"URL\"), os.getenv(\"API_KEY\")).users.libraries\n\nValueError: User ID is not set. Use the 'of(user_id)' method to set the user context.\n```\n\n- [Fixed Info and Configuration missmatch #69](https://github.com/jellyfin/jellyfin-apiclient-python/pull/69)\n\n```python\nimport jellyfin\n\njellyfin.api(os.getenv(\"URL\"), os.getenv(\"API_KEY\")).system.info\n```\n\n- [Add Playlist endpoints#64](https://github.com/jellyfin/jellyfin-apiclient-python/pull/64)\n\n```python\nimport jellyfin\nfrom jellyfin.items import ItemCollection, Item\nfrom jellyfin.generated import PlaylistsApi, BaseItemKind\n\napi = jellyfin.api(os.getenv(\"URL\"), os.getenv(\"API_KEY\"))\napi.user = 'justin'\nplaylist = api.items.search.add('include_item_types', [BaseItemKind.PLAYLIST]).recursive().all.first\nplaylist\n\n\u003cItem\n  name=\"Editor's Choice\",\n  original_title=None,\n  server_id='REDACTED',\n  id=UUID('ec0770d5-bde0-794c-bc1e-c5e8ff0588ca'),\n  etag=None,\n  source_type=None,\n  playlist_item_id=None,\n  date_created=None,\n  date_last_media_added=None,\n  extra_type=None\n  ...\n]\u003e\n\n# no high level abstraction, let's use bindings for playlist\nItemCollection(Item(\n    PlaylistsApi().get_playlist_items(playlist.id, api.user.id)\n))\n```\n\n### Easy to debug\n\nDebug is a missing piece in every implementation I look at, even the jellyfin team have a hard time to see what happen.\nTo help to remove the SDK of equation and for code more complex we have a feature to allow show a `curl` command of every request in the server.\n\n```python\nimport jellyfin\nfrom jellyfin.items import ItemCollection\nfrom jellyfin.generated import PlaylistsApi, BaseItemKind\n\napi = jellyfin.api(os.getenv(\"URL\"), os.getenv(\"API_KEY\"))\napi.debug = True\n\nplaylist = api.items.search.add('include_item_types', [BaseItemKind.PLAYLIST]).recursive().all.first\n```\n\nThe `api.debug` enable the client to print all requests in curl in prompt. *Never use this in production*!\n\n```sh\ncurl '-X' GET \\n\n    '-H' 'Accept: application/json' \\n\n    '-H' 'User-Agent: OpenAPI-Generator/10.10/python' \\n\n    '-H' 'Authorization: MediaBrowser Token=\"API_KEY\"' \\n\n    URL/Items?recursive=true\u0026includeItemTypes=Playlist\n```\n\nLots of problems happens in the api server or behavior not in documentation, \nin this way you have data to open a [issue in jellyfin](https://github.com/jellyfin/jellyfin/issues).\n\n### Login\n\n```python\nimport os\n\nos.environ[\"URL\"] = \"https://jellyfin.example.com\"\nos.environ[\"API_KEY\"] = \"MY_TOKEN\"\n```\n\n#### Using SDK\n\n```python\nimport jellyfin\n\napi = jellyfin.api(\n    os.getenv(\"URL\"), \n    os.getenv(\"API_KEY\")\n)\n\nprint(\n    api.system.info.version,\n    api.system.info.server_name\n)\n```\n\n#### Direct with Generated Bindings\n\n```python\nfrom jellyfin.generated.api_10_10 import Configuration, ApiClient, SystemApi\n\nconfiguration = Configuration(\n    host = os.getenv(\"URL\"),\n    api_key={'CustomAuthentication': f'Token=\"{os.getenv(\"API_KEY\")}\"'}, \n    api_key_prefix={'CustomAuthentication': 'MediaBrowser'}\n)\n\nclient = ApiClient(configuration)\nsystem = SystemApi(client)\n\nprint(\n    system.get_system_info().version, \n    system.get_system_info().server_name\n)\n```\n\nIt's possible use the proxy direct, this always will point to current stable\n\n```python\n# specific\nfrom jellyfin.generated.api_10_10 import Configuration, ApiClient, SystemApi\n\n# current stable\nfrom jellyfin.generated import Configuration, ApiClient, SystemApi\n\n# current unstable\nfrom jellyfin.generated.api_10_11 import BackupApi\n\n# inject from high level abstraction\nimport jellyfin\n\napi = jellyfin.api(\n    os.getenv(\"URL\"), \n    os.getenv(\"API_KEY\")\n)\n\n# module loader\napi.generated\n\n\u003cmodule 'jellyfin.generated.api_10_10' from '/code/jellyfin-sdk-python/src/jellyfin/generated/api_10_10/__init__.py'\u003e\n\n# injected from bindings\nclient = api.generated.ApiClient\n\n\u003cclass 'jellyfin.generated.api_10_10.api_client.ApiClient'\u003e\n```\n\n#### Legacy\n\n```python\nfrom jellyfin.legacy import JellyfinClient\nclient = JellyfinClient()\nclient.authenticate(\n    {\"Servers\": [{\n        \"AccessToken\": os.getenv(\"API_KEY\"), \n        \"address\": os.getenv(\"URL\")\n    }]}, \n    discover=False\n)\nsystem_info = client.jellyfin.get_system_info()\n\nprint(\n    system_info.get(\"Version\"), \n    system_info.get(\"ServerName\")\n)\n```\n\n### Jellyfin Server API Version\n\nThis is important because when a new API version is released, breaking changes can affect the entire project. \nTo avoid this, you can set an API target version, similar to how it's done in Android development:\n\n```python\nfrom jellyfin.api import Version\nimport jellyfin\n\n# By default will use the lastest stable\njellyfin.api(\n    os.getenv(\"URL\"), \n    os.getenv(\"API_KEY\")\n)\n\n# now let's test the new API (version 10.11) for breaking changes in same endpoint\njellyfin.api(\n    os.getenv(\"URL\"), \n    os.getenv(\"API_KEY\"), \n    Version.V10_11\n)\n\n# but str is allow to: 10.10, 10.11 and etc\njellyfin.api(\n    os.getenv(\"URL\"), \n    os.getenv(\"API_KEY\"), \n    '10.11'\n)\n\n# let's test a wrong version\njellyfin.api(\n    os.getenv(\"URL\"), \n    os.getenv(\"API_KEY\"), \n    '99'\n)\n\nValueError: Unsupported version: 99. Supported versions are: ['10.10', '10.11']\n```\n\n### List all libraries of an user\n\nWhen using `API_KEY` some endpoints need the user_id (don't me ask why!), almost all issues with jellyfin is around this.\nTo help to identify this not-so-much-edge-cases we raise a exception to help with that:\n\n```python\n\napi = jellyfin.api(\n    os.getenv(\"URL\"), \n    os.getenv(\"API_KEY\")\n)\n\napi.users.libraries\n\nValueError: User ID is not set. Use the 'of(user_id)' method to set the user context.\n\n\napi.users.of('f674245b84ea4d3ea9cf11').libraries\n\n# works also with the user name\napi.users.of('niels').libraries\n\n# when using 'of' the attribute of dataclasses\n# user and user_view can be accessed directly\napi.users.of('niels').id\n```\n\n### List all items\n\nItem can be any object in the server, in fact that's how works, one huge table recursive linked.\n\n```python\napi = jellyfin.api(\n    os.getenv(\"URL\"), \n    os.getenv(\"API_KEY\")\n)\n\napi.items.all\n\n# Same command but without shorthand\nsearch = api.items.search\nsearch\n\n\u003cItemSearch (no filters set)\u003e\n\nsearch.paginate(1000)\n\n\u003cItemSearch filters={\n  start_index=0,\n  limit=1000,\n  enable_total_record_count=True\n}\u003e\n\nsearch.recursive()\n\n\u003cItemSearch filters={\n  start_index=0,\n  limit=1000,\n  enable_total_record_count=True,\n  recursive=True\n}\u003e\n```\n\nAll filter options is available [here](https://webysther.github.io/jellyfin-sdk-python.github.io/api_10_10/docs/ItemsApi/#get_items).\n\nThe pagination uses a Iterator:\n\n```python\napi = jellyfin.api(\n    os.getenv(\"URL\"), \n    os.getenv(\"API_KEY\")\n)\n\nfor item in api.items.search.paginate(100).recursive().all:\n    print(item.name)\n```\n\n### Let's get the User ID by name or ID\n\n```python\napi = jellyfin.api(\n    os.getenv(\"URL\"), \n    os.getenv(\"API_KEY\")\n)\n\nuuid = api.user.by_name('joshua').id\n\napi.user.by_id(uuid).name\n```\n\n### Get item by ID\n\n```python\napi = jellyfin.api(\n    os.getenv(\"URL\"), \n    os.getenv(\"API_KEY\")\n)\n\napi.items.by_id('ID')\n```\n\nThis is just a shorthand for:\n\n```python\napi.items.search.add('ids', ['ID']).all.first\n```\n\n### Upload a Primary Image for a Item\n\n```python\nimport jellyfin\nfrom jellyfin.generated import ImageType\n\napi = jellyfin.api(\n    os.getenv(\"URL\"), \n    os.getenv(\"API_KEY\")\n)\n\napi.image.upload_from_url(\n    'ID', \n    ImageType.PRIMARY,\n    'https://upload.wikimedia.org/wikipedia/commons/6/6a/Jellyfin_v10.6.0_movie_detail%2C_web_client.png'\n)\n```\n\n### Add tags in a collection\n\nEdit item require the `user_id`, but we make this easy:\n\n```python\napi = jellyfin.api(\n    os.getenv(\"URL\"), \n    os.getenv(\"API_KEY\")\n)\n\nitem = api.items.edit('ID', 'joshua')\nitem.tags = ['branding']\nitem.save()\n\nitem = api.items.edit('ID', 'niels')\nitem.tags = ['rules']\nitem.save()\n```\n\nIf you want to set a global user:\n\n```python\napi = jellyfin.api(\n    os.getenv(\"URL\"), \n    os.getenv(\"API_KEY\")\n)\napi.user = 'niels'\n\nitem = api.items.edit('ID')\nitem.tags = ['branding']\nitem.save()\n\nitem = api.items.edit('OTHER_ID')\nitem.tags = ['rules']\nitem.save()\n```\n\nThe user on edit method has precedence over global\n\n### Register as a client\n\nIf necessary register a client to identify ourselves to the server\n\n```python\napi = jellyfin.api(\n    os.getenv(\"URL\"), \n    os.getenv(\"API_KEY\")\n)\napi.register_client()\n\n\u003cApi\n url='https://jellyfin.example.com',\n version='10.10',\n auth='Token=\"***\",\n       Client=\"4b8caf670ca1\",\n       Device=\"Linux Ubuntu 24.04.3 LTS (noble)\",\n       DeviceId=\"a7-17-23-d8-b9-b8\",\n       Version=\"24.04.3\"'\n\u003e\n```\n\nIf you need customize the client information:\n\n```python\napi.register_client('test')\n\n\u003cApi\n url='https://jellyfin.example.com',\n version='10.10',\n auth='Token=\"***\",\n       Client=\"test\",\n       Device=\"Linux Ubuntu 24.04.3 LTS (noble)\",\n       DeviceId=\"a7-17-23-d8-b9-b8\",\n       Version=\"24.04.3\"'\n\u003e\n```\n\nFor more detail look the [docs](https://webysther.github.io/jellyfin-sdk-python.github.io/sdk/#register_client).\n\n### Documentation\n\n- [SDK Reference](https://webysther.github.io/jellyfin-sdk-python.github.io/sdk/)\n- [Jellyfin API 10.10](https://webysther.github.io/jellyfin-sdk-python.github.io/api_10_10/) (Stable)\n- [Jellyfin API 10.11](https://webysther.github.io/jellyfin-sdk-python.github.io/api_10_11/) (Release Candidate)\n\n### Supported Jellyfin Versions\n\n| SDK Version | Jellyfin API Target |\n|:-:|:-:|\n| \u003c1.0.0 | 10.10.x-10.11.x |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebysther%2Fjellyfin-sdk-python","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebysther%2Fjellyfin-sdk-python","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebysther%2Fjellyfin-sdk-python/lists"}