ContentsUsing the API
  1. API

    1. Using the API
    2. API reference
    3. Emby-compatible API
  2. Plugin SDK

    1. Plugins and built-in features
    2. Capabilities
    3. Quick start
    4. Package, sign and publish

Using the API

The server has a REST API for managing libraries, scans, items, members, devices, plugins, schedules and more from scripts or other programs. This page covers authentication, the endpoint groups, paging and errors. The API reference lists every endpoint you can call with an API key. Endpoints used by the sign-in screen, the setup wizard and the web player are not part of it, and neither are the Emby-compatible endpoints.

On this page
  1. Authentication
  2. Sign-in limits
  3. Plain HTTP
  4. What you can do
  5. Paging and filters
  6. Errors
  7. Compatibility

All paths start with /api/v1.

Authentication#

  1. In the admin console open System → API keys and choose Create key. The key starts with msk_ and is shown only once, so copy it right away.
  2. Send the key with each request in the Authorization header: Authorization: Bearer <key>.
  3. A key acts as an administrator without a user behind it. Treat it like a password. When you no longer use it, revoke it under System → API keys; it stops working immediately.
  4. A key cannot do everything. These need a signed-in administrator in the admin console and return 403 with a key: managing members and their passwords or PINs, managing API keys, backups and restore, installing, removing or configuring plugins, deleting items, importing from another server, the license, server settings, logs and skip-intro data. Anything that needs a signed-in member, such as the web player's own endpoints, is not available to keys either.

Example:

curl -H "Authorization: Bearer $KRYSPAL_API_KEY" "$KRYSPAL_URL/api/v1/libraries"

A missing, invalid or revoked key gets 401. An operation a key may not call gets 403.

Browser sessions are separate: the web apps sign in with a cookie, and the server only accepts cookie requests that come from its own pages. Scripts should use a key.

Sign-in limits#

Failed sign-ins are limited: 5 failures per minute for one name from one address, and 30 failures per 10 minutes for one address. Beyond that the server answers 429 with a Retry-After header. API keys are not affected by this. There is no separate request-rate limit documented for keys, so do not rely on one either way: poll politely.

Plain HTTP#

The server speaks plain HTTP on its port. If you reach it from outside your network, put a reverse proxy with HTTPS in front of it, or use a VPN. Over HTTPS the sign-in cookie is marked Secure automatically.

What you can do#

Group What it covers Examples
Libraries List, create and look at libraries; refresh and re-scrape GET /libraries, POST /libraries, POST /libraries/{id}/refresh
Scans Start and cancel scans, read status and errors, follow events POST /libraries/{id}/scan, GET /scans/status, GET /scans/events (server-sent events)
Items Browse and filter items; refresh or rescan one item GET /items, GET /items/{id}, POST /items/{id}/refresh
Search Search across the library GET /search?q=
Media Item images, with range and conditional requests GET /images/items/{id}
Members Read the member list (changes need the admin console) GET /users, GET /users/{id}
Devices List devices and sign one out GET /devices, DELETE /devices/{id}
Playback Watch active sessions, stop one, read history GET /admin/playback/sessions, POST /admin/playback/sessions/{id}/stop
Schedules List scheduled tasks and run one now GET /admin/schedules, POST /admin/schedules/{task}/run
System Status, update check, activity log, background jobs (including scheduled task runs) GET /system/status, GET /activity, GET /jobs
Plugins Read the installed plugins and their status GET /plugins/installed

Paging and filters#

List endpoints page with a cursor: pass limit (default 40, at most 200) and send back the nextCursor of the previous page as cursor. The last page has no nextCursor. A total is only counted when you ask for it with includeTotal=true, because counting is slow on large libraries. GET /items takes many filters, such as libraryId, type, year, genreId, isPlayed, hasSubtitles, ids (up to 200) and sort with order. Lists you can repeat are sent as repeated query parameters. Items with equal sort values are ordered by name and then ID, so a page never repeats or skips an item.

curl -H "Authorization: Bearer $KRYSPAL_API_KEY" \
  "$KRYSPAL_URL/api/v1/items?libraryId=3&type=Movie&limit=50&sort=dateCreated&order=desc"

Times are RFC 3339 in UTC.

Errors#

Error responses are JSON:

{ "code": "field_invalid", "message": "Default text", "params": { "field": "name" } }
  • code is a stable, machine-readable code. Make your program decide based on it.
  • message is for people, in the server's language, and may change. Do not match on it.
  • params gives details for the code, such as which field or what the limit is.

Common HTTP statuses:

Status Codes Meaning
400 bad_request, field_required, field_invalid, field_too_long The request or a field is wrong. params.field names the field.
401 unauthorized No key, or the key is invalid or revoked.
403 forbidden, admin_only, license_required No permission, or the feature needs a license. params.feature names the feature.
404 not_found The resource does not exist.
409 conflict and situation-specific codes Conflicts with the current state, for example a job is already running.
429 too_many_requests, plugin_rate_limited Too many requests. Wait for the number of seconds in the Retry-After header.
5xx internal_error, service_unavailable, not_enabled A server error, or the feature is not enabled in the current setup. A temporary 503 carries a Retry-After header: wait that many seconds and try again.

The full list of codes and their params is the ErrorCode entry in the reference.

Compatibility#

  • The API is in beta and may change. Changes are listed in the release notes.
  • Endpoints outside the reference are used by the admin console and the web player. They can change at any time, so do not depend on them.
  • An endpoint for a paid feature returns 403 license_required without a license. It never quietly changes its behavior.
  • Ignore fields you do not recognize, and for an unknown code fall back to the HTTP status.