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
All paths start with /api/v1.
Authentication#
- 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. - Send the key with each request in the
Authorizationheader:Authorization: Bearer <key>. - 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.
- 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" } }
codeis a stable, machine-readable code. Make your program decide based on it.messageis for people, in the server's language, and may change. Do not match on it.paramsgives 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_requiredwithout a license. It never quietly changes its behavior. - Ignore fields you do not recognize, and for an unknown
codefall back to the HTTP status.