Getting started
The JSON API every Kinara app uses: the phone, TV, Roku and web apps do nothing you can't do with it. Your own server publishes this document at /openapi.yaml, so it always matches the version you run.
Base address. http://<server>:8420/api/v1 on your network (the server's HTTPS port and the Kinara account addresses work the same).
Signing in. POST /auth/login with a user name and password returns a token; send it as Authorization: Bearer <token>. Stream, image and subtitle URLs also take ?token=<token>, so plain players such as VLC can open them. Other ways in: POST /auth/home (pick an account on the home network, no password), POST /auth/quick/* (a code approved on another device) and POST /auth/cloud (a Kinara account, from anywhere). A session stays valid while it's used.
API keys. For scripts and services, an owner makes a key in the manager (Integrations, API keys). A key (kn_live_...) is sent the same way as a token, reaches only the operations marked with a key scope here, and only those its scopes allow.
Who may call what. Each operation says so in x-kinara-access: public (no sign-in), user (any signed-in account), user-full (signed in with a password or Kinara account, not a home-network sign-in) or owner.
Errors. Failures answer {"error": "<what went wrong>"} with a fitting status, sometimes with a code to act on (home_only, password_needed, wrong_pin, profile_pin). 401 means the token is missing or ended; 403, not allowed for this account; 429, too many tries (sign-in and PIN attempts are limited per address and per account).
Webhooks. The server can POST events to your own URL (Integrations, Webhooks). Each delivery carries X-Kinara-Event, X-Kinara-Delivery, X-Kinara-Timestamp and X-Kinara-Signature: sha256= and the hex HMAC-SHA256 of "<timestamp>.<body>" with the webhook's secret. GET /integrations/events lists every event.
# Sign in, then list your libraries
TOKEN=$(curl -s http://192.168.1.20:8420/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"you","password":"your password","deviceName":"My script"}' | jq -r .token)
curl -s http://192.168.1.20:8420/api/v1/libraries -H "Authorization: Bearer $TOKEN"
Server
Health, setup, settings, updates, backups and metrics.
GET/backupsBackups, their settings and the next nightly one (owners only)Owner
Responses
- 200
OK
application/json object
| Field | Type | About |
|---|
settings | BackupSettings | |
dir | string | The backups folder in use |
backups | array of Backup | |
nextAt | integer | Unix seconds of the next nightly backup (0 when off) |
error | string | |
Link to thisPOST/backupsMake a backup now (owners only)Owner
POST/backups/restoreRestore an uploaded backup (owners only)Owner
The request body is the backup's zip file. ?settings=1 also restores kinara.env. Otherwise as /backups/{name}/restore.
Parameters
| Name | In | Type | About |
|---|
settings | query | string: 1 | |
Request body
application/zip
Responses
- 202
Restarting to restore
- 400
Not a Kinara backup
Link to thisPUT/backups/settingsChange the backup settings (owners only)Owner
Fields left out keep their values. A folder is checked for writing first.
Request body
application/json BackupSettingsResponses
- 200
Saved
- 400
Not a full path
Link to thisGET/backups/{name}Download a backup (owners only; also takes ?token=)Owner
Parameters
| Name | In | Type | About |
|---|
name required | path | string | |
Responses
- 200
The zip file
application/zip
Link to thisDELETE/backups/{name}Delete a backup (owners only)Owner
Parameters
| Name | In | Type | About |
|---|
name required | path | string | |
Responses
Link to thisPOST/backups/{name}/restoreRestore a backup from the list (owners only)Owner
Checks the backup, makes a backup of the library as it is now (reason before-restore, so this can be undone), then restarts the server, which puts the backup in place before opening the database. The current files are kept in the data folder's restore-old folder.
Parameters
| Name | In | Type | About |
|---|
name required | path | string | |
Request body
application/json object
| Field | Type | About |
|---|
settings | boolean | Also restore the settings file (kinara.env). Off by default. |
Responses
Link to thisGET/healthServer status (no sign-in needed)No sign-in
Responses
- 200
OK
application/json object
| Field | Type | About |
|---|
ok | boolean | |
product | string | Always "kinara", so apps scanning the network can recognise the server |
name | string | The server's name (set in Settings, else the computer's name) |
version | string | |
setupDone | boolean | |
metadata | boolean | Whether a TMDB key is set |
update | string | A newer version that's out, when there is one. Only on the server's own network (the tray and the menu bar item read it). |
Link to thisGET/metrics/httpRequest counts and timings per route (owners)Owner
GET/metrics/liveProcessor, GPU, memory and traffic (owners)Owner
Parameters
| Name | In | Type | About |
|---|
range | query | string | How far back, such as 5m, 1h or 24h |
Responses
- 200
Samples over the range
- 501
Metrics are off
Link to thisGET/metrics/logsRecent warnings and errors (owners)Owner
GET/metrics/storageDisk use of libraries, cache and data (owners)Owner
GET/metrics/usageWatching over time (owners)Owner
GET/pulseWhat changed (for refreshing screens)Signed in
Cheap to call every few seconds. Each number changes when its part of the server does, so an app refetches only what moved: library (a scan or match changed titles), requests, party (watch parties). active lists titles with an incoming status in progress.
Responses
- 200
OK
application/json object
| Field | Type | About |
|---|
library | integer | |
requests | integer | |
party | integer | |
active | array of object | |
Link to thisGET/settingsServer settings (owners only)Owner
Responses
- 200
OK
application/json object
| Field | Type | About |
|---|
listAccounts | boolean | Show accounts on the sign-in screen |
allowSignup | boolean | Let anyone who reaches the server make an account |
priorityDevice | string | The device that keeps its quality when the server is busy converting |
serverName | string | What apps and the Kinara account call this server, at most 40 characters; empty uses the computer's name |
updateCheck | boolean | Check the downloads site twice a day for a newer Kinara (default on) |
autoUpdate | boolean | Download a new release as soon as it's out and install it at night when nothing is playing, where no prompt is needed (default on) |
groupCollections | boolean | Show a franchise's movies as one card in movie libraries (default on) |
homeSignIn | boolean | On the home network, devices sign in by picking an account, no password (default on; see /auth/home) |
hasHomePin | boolean | A PIN is asked for that (read only; set with PUT /settings/home-pin) |
Link to thisPUT/settingsSave server settings (owners only)Owner
Fields left out keep their values.
Responses
Link to thisPUT/settings/home-pinSet or remove the PIN asked for signing in on the home network (owners only)Owner
Request body
application/json object
| Field | Type | About |
|---|
pin | string | 4 to 8 digits; empty removes it |
Responses
- 204
Saved
- 400
Not 4 to 8 digits
Link to thisPOST/setupFirst run only - make the owner account and sign it inNo sign-in
Works only while the server has no accounts (health says setupDone false) and only from the home network. Afterwards it answers 409.
Request body
application/json object
| Field | Type | About |
|---|
username required | string | 1 to 32 letters, digits, spaces, dots, dashes or underscores |
password required | string | At least 8 characters |
deviceName | string | |
device | DeviceInfo | |
Responses
Link to thisGET/timeThe server clock in Unix milliseconds, for apps lining their clocks up for watch parties. No sign-in needed.No sign-in
Responses
- 200
Server time
application/json object
Link to thisGET/updateWhether a newer Kinara is out, and any install in progress (owners only)Owner
POST/update/checkCheck for a newer Kinara now (owners only)Owner
POST/update/installDownload and install the newer Kinara (owners only; Windows and macOS installs)Owner
Downloads the installer for this computer, checks its size and SHA-256 (and on macOS that Macroboom LLC signed it and Apple notarized it), then starts it. On Windows it runs silently (in sign-in mode Windows asks for permission on that PC); on a Mac, macOS asks for an administrator's password there. The server restarts as the new version; the installer is deleted afterwards. Follow progress with GET /update.
Responses
- 202
- 409
Nothing to install
- 429
Already installing
Link to thisSign-in and accounts
Signing in (password, home network, Kinara account, codes), accounts and profiles.
GET/auth/accountsAccounts the sign-in screen can offer, and whether new ones can be madeNo sign-in
accounts is empty when the owner turned the list off. allowSignup says whether POST /auth/signup is open.
Responses
- 200
OK
application/json object
| Field | Type | About |
|---|
accounts | array of object | |
allowSignup | boolean | |
Link to thisPOST/auth/cloudSign in with a Kinara accountNo sign-in
Turns an access token from the Kinara cloud (POST /v1/servers/{id}/access on cloud.kinara.media) into a session on this server. Works from anywhere, which is how the apps sign in away from home. Limited per address.
Request body
application/json object
| Field | Type | About |
|---|
token required | string | The cloud's access token for this server |
deviceName | string | |
device | DeviceInfo | |
Responses
- 200
Signed in: token, user and profiles, like POST /auth/login
- 401
The token isn't valid for this server
- 429
Too many tries from this address
- 501
This server isn't linked to a Kinara account
Link to thisPOST/auth/homeSign in on the home network by picking an account (no password)No sign-in
Only straight from the home network (not through the tunnel, the relay, or a reverse proxy not named in KINARA_TRUSTED_PROXIES), and only while the owner leaves homeSignIn on. The session is home only: refused away from home (401, code home_only), and it can't manage the server (403, code owner_sign_in) or approve another device's sign-in (403, code password_needed). GET /auth/accounts says whether it's offered (homeSignIn) and whether the server PIN is asked (homePin).
Request body
application/json object
| Field | Type | About |
|---|
username required | string | |
pin | string | The server's home sign-in PIN |
profileId | integer | Who's watching; default the account's first profile without a PIN |
profilePin | string | That profile's PIN |
deviceName | string | |
device | DeviceInfo | |
Responses
- 200
Signed in; the same answer as /auth/login
- 401
Unknown account
- 403
Not the home network (code not_home), turned off (home_sign_in_off), or a profile PIN is needed (profile_pin, with profiles) or wrong (wrong_profile_pin)
- 429
Too many failed attempts
Link to thisPOST/auth/loginSign in a deviceNo sign-in
Request body
application/json object
| Field | Type | About |
|---|
username required | string | |
password required | string | |
deviceName | string | |
device | DeviceInfo | |
Responses
Link to thisPOST/auth/logoutSign this device outSigned in
POST/auth/quick/approveQuick Connect - sign the code's device in as youFull sign-in
Request body
application/json object
| Field | Type | About |
|---|
code required | string | Spaces are ignored |
Responses
Link to thisPOST/auth/quick/cancelQuick Connect - forget a code this device no longer showsNo sign-in
Request body
application/json object
| Field | Type | About |
|---|
secret required | string | |
Responses
Link to thisPOST/auth/quick/pollQuick Connect - has the code been approved?No sign-in
Request body
application/json object
| Field | Type | About |
|---|
secret required | string | |
Responses
Link to thisPOST/auth/quick/startQuick Connect - get a code to sign this device in from a phone or the web appNo sign-in
For TVs and other devices where typing a password is a chore. Show the code; someone signed in elsewhere enters it (POST /auth/quick/approve) and the next poll signs this device in as them. Codes last 10 minutes. No Kinara account is involved.
Request body
application/json object
Responses
Link to thisGET/auth/quick/{code}Quick Connect - which device is waiting behind a codeSigned in
Parameters
| Name | In | Type | About |
|---|
code required | path | string | |
Responses
Link to thisPOST/auth/signupMake an ordinary account and sign it in (only when the owner allows sign-up)No sign-in
Request body
application/json object
| Field | Type | About |
|---|
username required | string | 1 to 32 letters, digits, spaces, dots, dashes or underscores |
password required | string | At least 8 characters |
deviceName | string | |
device | DeviceInfo | |
Responses
Link to thisGET/meThe signed-in userSigned in
PUT/me/deviceThe app describes itself (the same object as device on sign-in), so sessions from before it sent one get its name and details. Apps send it when they start. A name the owner gave the device is kept.Signed in
Request body
application/json object
| Field | Type | About |
|---|
id required | string | 8 to 64 of A-Z, a-z, 0-9, - and _ |
name | string | |
platform | string | |
brand | string | |
model | string | |
os | string | |
app | string | |
formFactor | string | |
Responses
- 204
Recorded
- 400
Missing or unusable id
Link to thisPUT/me/device/nameName this device ("Living Room TV"), shown in watch party invites.Signed in
Request body
application/json object
| Field | Type | About |
|---|
name required | string | |
Responses
Link to thisGET/profilesThis account's profiles and the one this device is watching asSigned in
Responses
- 200
OK
application/json object
| Field | Type | About |
|---|
profiles | array of Profile | |
current | integer | |
Link to thisPOST/profilesAdd a profile (up to 12)Signed in
Request body
application/json object
| Field | Type | About |
|---|
name required | string | |
color | string | |
pin | string | 4 digits |
kids | boolean | |
Responses
Link to thisPATCH/profiles/{id}Change a profile (fields not sent stay as they are; pin "" removes the PIN)Signed in
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
Link to thisDELETE/profiles/{id}Remove a profile and its history (not the last one)Signed in
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
Link to thisPOST/profiles/{id}/selectWatch as this profile on this deviceSigned in
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Request body
application/json object
Responses
- 200
Selected
- 403
Wrong PIN
- 429
Too many wrong PINs
Link to thisGET/usersEvery account (owners only)Owner
POST/usersAdd an account (owners only)Owner
Request body
application/json object
| Field | Type | About |
|---|
username | string | |
password | string | |
isAdmin | boolean | |
Responses
- 201
The new account
- 409
Name taken
Link to thisPATCH/users/{id}Set a new password (signs the account out everywhere) or change owner rightsOwner
DELETE/users/{id}Remove an account and its history (not your own)Owner
Devices
Signed-in devices and what they report.
PUT/clients/{clientId}Trust or rename a device (admin)Owner
Parameters
| Name | In | Type | About |
|---|
clientId required | path | string | |
Request body
application/json object
| Field | Type | About |
|---|
trusted | boolean | |
name | string | 1 to 80 characters |
Responses
- 200
The device as GET /devices lists it
application/json DeviceItem - 404
No such device
Link to thisGET/devicesSigned-in devices (admin)Owner
Responses
- 200
Devices, most recently seen first
application/json array of
DeviceItem
Link to thisDELETE/devices/{id}Sign a device out (admin). It also stops being the priority device.Owner
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
Responses
- 204
Signed out
- 404
No such device
Link to thisLibraries
Library folders and scans.
GET/fs/foldersFolders on the server's disks, for the folder picker (admin)Owner
With no path, lists places to start: drives on Windows; the root, home and the usual mount points elsewhere. Hidden and system folders are left out. Each folder says which library already has it (or a folder around it).
Parameters
| Name | In | Type | About |
|---|
path | query | string | Absolute folder to list |
Responses
Link to thisGET/librariesLibraries with counts and scan statusSigned inAPI key: library:read
Responses
- 200
OK
application/json array of
Library
Link to thisPOST/librariesAdd a library and start its first scan (admin)Owner
Request body
application/json object
| Field | Type | About |
|---|
name | string | |
path required | string | Absolute folder path on the server |
kind required | string: movies, tv, other | |
Responses
- 201
Created
- 400
Invalid folder or kind
Link to thisPATCH/libraries/{id}Rename a library (admin)Owner
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Request body
application/json object
Responses
- 200
- 400
Empty or too long name
- 404
No such library
Link to thisDELETE/libraries/{id}Remove a library (admin). Files are not touched.Owner
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
Link to thisPOST/libraries/{id}/foldersAdd a folder to a library and scan it (admin)Owner
The folder can't be one a library already has, or sit inside or around one. If a scan is running, another runs when it ends.
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Request body
application/json object
| Field | Type | About |
|---|
path required | string | Absolute folder path on the server |
Responses
Link to thisDELETE/libraries/{id}/foldersTake a folder out of a library (admin)Owner
Kinara forgets the files in it; nothing on disk is touched. Titles left with no files are removed unless someone has watched them. A library's only folder can't be removed: remove the library instead.
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
path required | query | string | The folder exactly as the library lists it |
Responses
Link to thisGET/libraries/{id}/problemsFiles that couldn't be read or placed, and titles that didn't match (owners only)Owner
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
Link to thisPOST/libraries/{id}/rematchRetry matching for unmatched items, then scan (admin)Owner
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
Link to thisPOST/libraries/{id}/scanStart a scan (admin, or an API key with library:scan)OwnerAPI key: library:scan
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
path | query | string | Scan only this folder, relative to one of the library's folders (or a full path inside one). |
Responses
Link to thisTitles
Movies, shows, seasons, episodes and franchises: browsing, search, details, artwork and editing.
GET/cast/{id}/{ord}A cast member's photo, resized on requestSigned in
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
ord required | path | integer | |
w | query | integer | |
token | query | string | |
Responses
Link to thisGET/genresGenres with available titles, most titles firstSigned in
Parameters
| Name | In | Type | About |
|---|
kind | query | string: movie, show | |
Responses
- 200
OK
application/json object
| Field | Type | About |
|---|
genres | array of object | |
Link to thisGET/homeContinue Watching and Recently AddedSigned in
Responses
- 200
OK
application/json object
| Field | Type | About |
|---|
continueWatching | array of object | |
recentlyAdded | array of Item | |
Link to thisGET/images/{id}/{kind}Artwork, resized on requestSigned in
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
kind required | path | string: poster, backdrop, still | |
w | query | integer | Width; rounded up to 160, 240, 320, 400, 640, 960, 1280 or 1920 |
token | query | string | |
Responses
Link to thisGET/images/{id}/{kind}/colorThe artwork's dominant colour, for tinting the app to match itSigned in
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
kind required | path | string: poster, backdrop, still | |
Responses
- 200
"#rrggbb", or "" when the artwork is mostly grey or missing
application/json object
| Field | Type | About |
|---|
color | string | |
Link to thisGET/itemsBrowse itemsSigned inAPI key: library:read
Parameters
| Name | In | Type | About |
|---|
libraryId | query | integer | |
kind | query | string: movie, show, season, episode | |
parentId | query | string | |
matchState | query | string | |
sort | query | string: title, added, year, length, rating | |
genre | query | string | A TMDB genre such as "Comedy" |
watched | query | string: unwatched, started, watched | For the current profile. Shows: started = some episodes seen, watched = all |
maxRuntime | query | integer | Minutes |
quality | query | string: best | Only titles with a 4K or HDR file |
q | query | string | Title words |
collections | query | string: 1 | Movie listings without q: with the owner's groupCollections setting
on, a franchise with two or more of the listed movies is one item of
kind "collection" (id "c" plus TMDB's collection ID, count = its
movies) in their place. GET /items/{id} for it answers with the
franchise (item) and its movies (items), oldest first. Also for
/items/letters.
|
limit | query | integer | |
offset | query | integer | |
Responses
- 200
OK
application/json object
| Field | Type | About |
|---|
items | array of Item | |
nextOffset | integer | Present when more items may follow |
Link to thisGET/items/lettersWhere each letter starts in the A to Z list, for a jump barSigned in
Takes the same filters as /items (sort is always title). "#" is titles starting with a digit or symbol.
Responses
- 200
OK
application/json object
| Field | Type | About |
|---|
letters | array of object | |
Link to thisGET/items/{id}Item detailsSigned inAPI key: library:read
Movies and episodes include their files. Episodes include their season and show. Shows include the next episode. A movie in a franchise with two or more movies on the server includes collection (id, title, count). For a franchise ("c119"): item, and items (its movies, oldest first).
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
Responses
- 200
OK
application/json object
Link to thisPATCH/items/{id}Edit a title by hand (owners only)Owner
Fields left out stay. Every field changed is locked, so matching and refreshing from TMDB leave it as edited. Answers like GET /items/{id}/edit.
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
Request body
application/json object
| Field | Type | About |
|---|
title | string | |
sortTitle | string | Follows the title (without The, A or An) unless given |
year | integer | |
overview | string | |
rating | number | 0 to 10 |
airDate | string | YYYY-MM-DD |
genres | array of string | |
Responses
Link to thisGET/items/{id}/artwork/{kind}TMDB's artwork for a title, best first (owners only)Owner
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
kind required | path | string: poster, backdrop, still | |
Responses
- 200
choices: path, url (to show it), width, height, language, votes, current
- 409
The title isn't matched to TMDB
Link to thisGET/items/{id}/childrenSeasons of a show, or episodes of a season, in orderSigned inAPI key: library:read
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
Responses
- 200
OK
application/json object
| Field | Type | About |
|---|
items | array of Item | |
Link to thisGET/items/{id}/downloadDownload qualities for a title, with expected sizesSigned in
For a movie or an episode, or a season or show (all its episodes, or only unwatched ones with ?unwatched=1). "original" is the file as it is; the others are prepared first as an MP4 any phone plays (H.264 and stereo AAC), and need ffmpeg on the server ("available").
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
unwatched | query | string: 1 | |
Responses
- 200
OK
application/json object
| Field | Type | About |
|---|
enabled | boolean | |
count | integer | Movies or episodes covered |
default | string | The quality to pick unless the person chose one |
qualities | array of object | |
Link to thisGET/items/{id}/editA title's editable details (owners only)Owner
title, sortTitle, year, overview, rating, airDate, genres; locked (fields edited by hand, which matching leaves alone); art (the kinds of artwork this title has: poster and backdrop, or still for an episode); images (current artwork addresses); canPick (TMDB artwork can be listed).
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
Responses
Link to thisPUT/items/{id}/images/{kind}Use TMDB artwork: {"path": "/abc.jpg"} from the list (owners only; locks it)Owner
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
kind required | path | string: poster, backdrop, still | |
Responses
Link to thisPOST/items/{id}/images/{kind}/uploadUpload artwork, a JPEG or PNG up to 20 MB as the body (owners only; locks it)Owner
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
kind required | path | string: poster, backdrop, still | |
Request body
image/jpeg
image/png
Responses
- 200
Set
- 400
Not a JPEG or PNG
Link to thisPOST/items/{id}/matchFix a match by TMDB ID (admin)Owner
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
Request body
application/json object
| Field | Type | About |
|---|
tmdbId | integer | |
Responses
Link to thisGET/items/{id}/optimizeThe optimize rule for a title (or its library) and its copies (owners only)Owner
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
Responses
Link to thisPUT/items/{id}/prefsSave audio and subtitle choices for the show or movie this item belongs toSigned in
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
Request body
application/json TrackPrefsResponses
- 200
The merged preferences now in effect
application/json TrackPrefs
Link to thisGET/items/{id}/similarMore like this, for a library titleSigned in
TMDB's recommendations for the title (an episode or season answers for its show), each marked like a Discover card. Needs a TMDB key on the server.
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
page | query | integer | |
Responses
- 200
items, page, totalPages
- 404
No such title
Link to thisPOST/items/{id}/unlockGive fields back to TMDB (owners only)Owner
Unlocks them ({"fields": ["title", "poster"]} or ["all"]) and refreshes the title from TMDB now.
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
Responses
Link to thisPOST/items/{id}/watchedMark watched or unwatched (shows and seasons apply to every episode)Signed in
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
Request body
application/json object
| Field | Type | About |
|---|
watched | boolean | |
Responses
Link to thisGET/searchFind movies and shows by titleSigned in
Parameters
| Name | In | Type | About |
|---|
q required | query | string | |
limit | query | integer | |
Responses
- 200
Best matches first
application/json object
| Field | Type | About |
|---|
items | array of Item | |
Link to thisPlayback
Streaming, conversion, progress and seek previews.
DELETE/hls/session/{session}Stop an HLS session and delete its filesSigned in
Parameters
| Name | In | Type | About |
|---|
session required | path | string | |
Responses
Link to thisGET/hls/session/{session}/{file}An HLS session's playlist (index.m3u8) or segment (init.mp4, segNNNNN.m4s)No sign-in
Parameters
| Name | In | Type | About |
|---|
session required | path | string | |
file required | path | string | |
Responses
- 200
The file
- 404
The session ended
Link to thisPOST/hls/{id}Start an HLS conversion of a file (iPhones, iPads, Safari)Signed in
Takes the same query as /transcode/{id} (video, audioCodec, audio, start, maxBitrate, maxHeight). Answers once the first segment is ready. The playlist and its segments are fetched without a token: the session ID is the key. A session ends after about 90 s without requests, or when stopped.
Parameters
| Name | In | Type | About |
|---|
id required | path | string | Version ID |
Responses
Link to thisGET/optimizeOptimized versions, by rule, with the room they take (owners only)Owner
A rule asks for a smaller copy (an MP4 with H.264 and stereo AAC) of every movie and episode in a library, or of one title, made ahead of time in the background. Ready copies are listed as extra versions of their title (ID "o" plus a number, "optimized" set to the quality), so apps short on bandwidth play them as they are. Titles already that small are skipped ("small").
Responses
Link to thisPOST/optimizeOptimize a library or a title (owners only)Owner
Asking again for the same library or title changes its quality.
Request body
application/json object
| Field | Type | About |
|---|
libraryId | integer | |
itemId | string | A movie |
quality | string: high, medium, low | Default medium |
Responses
Link to thisDELETE/optimize/{id}Remove a rule (its copies go unless a download still uses them; owners only)Owner
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
Link to thisPOST/optimize/{id}/retryPut a rule's failed copies back in line (owners only)Owner
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
- 200
How many were put back
Link to thisGET/playbackWhat each device is playing now, with its conversion's speed (admin)OwnerAPI key: activity:read
Responses
- 200
List of devices playing
Link to thisGET/playback/adviceWhether a player should step its quality downSigned in
Players ask every few seconds while the server converts for them. "lower" means the server can't convert fast enough (or another device has priority): start again at next.
Parameters
| Name | In | Type | About |
|---|
version required | query | string | |
Responses
- 200
Advice
application/json object
| Field | Type | About |
|---|
action | string: ok, lower | |
reason | string | |
next | object | |
Link to thisPUT/progress/{id}Report playback position for a movie or episodeSigned in
Send every 10 seconds, on pause, and on stop. Past 92% of the file's length the item is marked watched. Reports older than the stored one (by clientTime) are ignored, so offline reports can be replayed safely.
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
Request body
application/json object
| Field | Type | About |
|---|
positionSec required | number | |
versionId | string | |
clientTime | integer | Unix seconds when the position was read |
Responses
Link to thisGET/speedtestRandom bytes, for the app to measure the connectionSigned in
Parameters
| Name | In | Type | About |
|---|
bytes | query | integer | 64 KB to 32 MB |
Responses
Link to thisGET/stream/{id}Direct play a file (supports Range requests)Signed in
Parameters
| Name | In | Type | About |
|---|
id required | path | string | Version ID |
token | query | string | |
Responses
- 200
The whole file
- 206
Part of the file
Link to thisGET/transcode/{id}Convert a file on the fly for players that can't play it as isSigned in
A Matroska stream written as ffmpeg produces it. It can't be seeked; to jump, request a new stream with a different start. Present on a version as transcodeUrl when the server has ffmpeg.
Parameters
| Name | In | Type | About |
|---|
id required | path | string | Version ID |
start | query | number | Seconds to begin at |
video required | query | string: copy, h264 | |
maxBitrate | query | integer | kbit/s for re-encoded video (default 8000) |
maxHeight | query | integer | Pixels for re-encoded video (default 1080) |
audio | query | integer | Source stream index of the audio track (default the first) |
container | query | string: mkv, mp4 | mkv (default) or mp4: fragmented MP4 for web browsers, without subtitles |
dovi | query | string: 1, 0 | Whether the player shows Dolby Vision. 1: copied Dolby Vision goes out as is (tagged dvh1 in MP4/HLS). 0: profile 7 and 8.1 are copied as their HDR10 base layer, profile 5 is converted with libplacebo. Left out: profile 7 into MP4/HLS is stripped to HDR10, otherwise as before. |
audioCodec required | query | string: copy, aac | |
token | query | string | |
Responses
Link to thisGET/transcode/{id}/startWhere a converted stream can startSigned in
For a converted stream that keeps the original picture: the keyframe near ?at= (the next one with dir=forward, else the one before). Ask for the stream at "request" and count the clock from "start", so the position shown matches the picture.
Parameters
| Name | In | Type | About |
|---|
id required | path | string | Version ID |
at | query | number | Seconds |
dir | query | string: forward | |
Responses
Link to thisGET/trickplay/{id}Preview thumbnails for skipping through a fileSigned in
Frame n (time n * interval) is on sheet floor(n / (cols * rows)) + 1, at column n % cols and row floor(n / cols) % rows. Asking for a file without previews moves it to the front of the queue.
Parameters
| Name | In | Type | About |
|---|
id required | path | string | Version ID |
Responses
- 200
OK
application/json object
| Field | Type | About |
|---|
interval | integer | |
width | integer | |
height | integer | |
cols | integer | |
rows | integer | |
count | integer | |
sheets | integer | |
sheetUrl | string | Has "{n}" where the sheet number goes |
- 404
Not made yet
Link to thisGET/trickplay/{id}/{sheet}One sheet of preview thumbnails (JPEG)Signed in
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
sheet required | path | string | Like "1.jpg" |
token | query | string | |
Responses
Link to thisSubtitles
Subtitle tracks, search and the automatic finder.
GET/subtitles/autoAutomatic subtitles, the services and today's use, and what's missing (owners only)Owner
POST/subtitles/auto/retryLook again for missing subtitles (all, or one file's with versionId; owners only)Owner
GET/subtitles/settingsSubtitle settings (owners only)Owner
Responses
- 200
everyone, cleanDvd, username, hasPassword, apiKey (last 4), available
Link to thisPUT/subtitles/settingsChange subtitle settings (owners only)Owner
Fields left out keep their values. A changed account is tried at once; account says how that went.
Request body
application/json object
| Field | Type | About |
|---|
everyone | boolean | Every account can search and download (default on) |
cleanDvd | boolean | Redraw DVD subtitles in Kinara's style (default on) |
username | string | OpenSubtitles account |
password | string | |
apiKey | string | Your own OpenSubtitles API key ("" for Kinara's) |
Responses
Link to thisGET/subtitles/{id}/autoA file's subtitle files and the automatic search for itSigned in
Players ask while playing: it also puts the file at the front of the automatic subtitle line. "wanted" lists the languages being looked for (state wanted, found or missing), "searching" says the server is looking at this file now. Subtitles found are checked against the film's speech and served with their timing corrected.
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
Responses
Link to thisPOST/subtitles/{id}/files/{file}/wrongSay a downloaded subtitle is wrongSigned in
It's never picked for this file again, removed when the server fetched it, and the next one is looked for straight away.
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
file required | path | integer | The subtitle's index (1000000 and up) |
Responses
Link to thisPOST/subtitles/{versionId}/downloadDownload a search result and keep it with the file, for everyoneSigned in
Parameters
| Name | In | Type | About |
|---|
versionId required | path | string | |
Request body
application/json object
| Field | Type | About |
|---|
fileId required | integer | |
language | string | |
release | string | |
hearingImpaired | boolean | |
forced | boolean | |
Responses
Link to thisDELETE/subtitles/{versionId}/files/{index}Remove a downloaded subtitle (owners, or whoever downloaded it)Signed in
Parameters
| Name | In | Type | About |
|---|
versionId required | path | string | |
index required | path | integer | |
Responses
Link to thisGET/subtitles/{versionId}/searchSearch OpenSubtitles for a movie's or episode's subtitlesSigned in
By the title's TMDB ID (an episode's show, season and episode) and the file's OpenSubtitles hash; best first (made for this file, by a person, most downloaded). Owners can turn this off for other accounts.
Parameters
| Name | In | Type | About |
|---|
versionId required | path | string | |
languages | query | string | Comma separated (en, eng or English); default en |
Responses
Link to thisGET/subtitles/{versionId}/{index}A subtitle track as WebVTTSigned in
Text tracks inside the file, or added ones, converted to WebVTT (with any sync correction applied). Takes ?token=.
Parameters
| Name | In | Type | About |
|---|
versionId required | path | string | |
index required | path | string | |
token | query | string | |
Responses
Link to thisGET/subtitles/{versionId}/{index}/pgsA Blu-ray (PGS) subtitle track as timed pictures, for the app to draw over the videoSigned in
The first request starts reading the track (just that track is copied out of the file; nothing is decoded), so done is false at first. Ask again every couple of seconds with from set to the events already had, until done. Times are seconds in the file; x and y place each picture in a width x height frame. The token may be given as ?token=.
Parameters
| Name | In | Type | About |
|---|
versionId required | path | string | |
index required | path | integer | The subtitle stream index |
from | query | integer | Events to skip (ones already had) |
Responses
- 200
The timeline from
from on
application/json object
| Field | Type | About |
|---|
width | integer | |
height | integer | |
done | boolean | |
total | integer | |
error | string | |
events | array of object | |
- 404
No such file or track
- 422
The track isn't PGS
- 501
The server has no ffmpeg
Link to thisGET/subtitles/{versionId}/{index}/pgs/{n}.pngOne picture of a PGS trackSigned in
Parameters
| Name | In | Type | About |
|---|
versionId required | path | string | |
index required | path | integer | |
n required | path | integer | |
Responses
- 200
PNG with transparency
- 404
Not made (yet)
Link to thisDiscover and requests
Trending titles, TMDB lookups and requests for titles not on the server.
GET/discoverThe Discover page, kept for an hourSigned in
Sections in order (empty ones left out): trending, new-movies, new-episodes, popular-movies, popular-shows, coming-soon, classics, acclaimed-shows. Genres are TMDB's for movies and shows.
Responses
- 200
Sections and genres
application/json object
| Field | Type | About |
|---|
sections | array of object | |
genres | array of object | |
updatedAt | integer | |
Link to thisGET/discover/genre/{kind}/{genreId}Titles in one genre, pagedSigned in
Parameters
| Name | In | Type | About |
|---|
kind required | path | string: movie, show | |
genreId required | path | integer | |
sort | query | string: popular, rating, new | |
page | query | integer | |
Responses
Link to thisGET/discover/{section}One Discover section, paged (20 a page)Signed in
Parameters
| Name | In | Type | About |
|---|
section required | path | string | |
page | query | integer | |
Responses
Link to thisGET/onthewayTitles on their way that the asker may see (owners see all)Signed inAPI key: webhooks:manage
Responses
- 200
{"items": [{id, kind, tmdbId, title, year?, season?, episode?, state, progress?, message?, etaSec?, updatedAt, source, item?: {id}}]}
Link to thisDELETE/ontheway/{id}Clear a status (owners, or webhooks:manage)OwnerAPI key: webhooks:manage
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
Link to thisGET/requestsRequests, newest first. Owners see everyone's; others their own.Signed in
Parameters
| Name | In | Type | About |
|---|
status | query | string: pending, approved, declined, available | |
limit | query | integer | |
offset | query | integer | |
Responses
- 200
The requests, and how many are pending
application/json object
| Field | Type | About |
|---|
items | array of Request | |
pending | integer | |
Link to thisPOST/requestsAsk for a movie or show that isn't on the serverSigned in
From a trusted device it is approved straight away (auto, sent). An identical open request is returned with 200 and duplicate: true. Emits request.created, and request.approved when trusted.
Request body
application/json object
| Field | Type | About |
|---|
kind required | string: movie, show | |
tmdbId required | integer | |
mode | string: all, seasons, pilot | Shows only |
seasons | array of integer | With mode seasons |
note | string | |
Responses
Link to thisDELETE/requests/{id}Cancel your own pending request; owners may remove anySigned in
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
- 204
Removed
- 409
Not pending any more
Link to thisPOST/requests/{id}/approveApprove a pending request (admin). Emits request.approved.Owner
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
- 200
The request
- 409
Not pending
Link to thisPOST/requests/{id}/declineDecline a pending or approved request (admin). Emits request.declined.Owner
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Request body
application/json object
| Field | Type | About |
|---|
reason | string | |
Responses
Link to thisGET/search/externalSearch TMDB for movies and shows, in the library or notSigned in
Parameters
| Name | In | Type | About |
|---|
q required | query | string | |
page | query | integer | |
Responses
- 200
A page; titles the library has carry
itemapplication/json TitlePage
Link to thisGET/tmdb-image/{file}TMDB artwork for titles not in the library (Trending), resized on requestSigned in
Parameters
| Name | In | Type | About |
|---|
file required | path | string | Like "abc123.jpg" |
w | query | integer | |
token | query | string | |
Responses
Link to thisGET/tmdb/show/{id}/season/{n}A season's episodes from TMDB (titles, dates, stills), for shows outside the librarySigned in
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | The show's TMDB ID |
n required | path | integer | Season number (0 for specials) |
Responses
Link to thisGET/tmdb/{kind}/{id}A movie or show from TMDB, whether or not it's in the librarySigned in
Details, top cast and (for shows) seasons, for a title page opened from Trending. item is set when the library has it; apps then open the library page instead. Cached on the server for 6 hours. Needs a TMDB key. request (a RequestMarker) is set when someone has an open request for it, and onTheWay when the owner's tools reported it is on its way.
Parameters
| Name | In | Type | About |
|---|
kind required | path | string: movie, show | |
id required | path | integer | The TMDB ID |
Responses
Link to thisGET/tmdb/{kind}/{id}/similarMore like this, for a title not on the serverSigned in
Parameters
| Name | In | Type | About |
|---|
kind required | path | string: movie, show | |
id required | path | integer | TMDB ID |
page | query | integer | |
Responses
- 200
items, page, totalPages
Link to thisGET/trendingWhat's trending on TMDB this week, marked with what's in the librarySigned in
Lists of titles (today, movies, shows) from TMDB, refreshed hourly. Each has item set when it's in the library; inLibrary collects those. Needs a TMDB key.
Responses
Link to thisLists
Watchlists and other lists.
GET/listsThis profile's lists ("My List" first, made on first use) with countsSigned in
POST/listsMake a named listSigned in
Request body
application/json object
Responses
Link to thisPATCH/lists/{id}Rename a listSigned in
DELETE/lists/{id}Remove a named list ("My List" can't be removed)Signed in
Responses
- 204
Removed
- 400
That's My List
Link to thisGET/lists/{id}/itemsThe titles on a list, newest first ("default" for My List)Signed in
Parameters
| Name | In | Type | About |
|---|
id required | path | string | A list ID, or "default" |
Responses
Link to thisPUT/lists/{id}/items/{item}Add a title to a list (again is fine)Signed in
DELETE/lists/{id}/items/{item}Take a title off a listSigned in
Downloads
Copies prepared for watching offline on phones.
GET/downloadsThis account's downloads on all its devices (?device=this for just this one)Signed in
Responses
- 200
OK
application/json object
| Field | Type | About |
|---|
enabled | boolean | |
downloads | array of Download | |
Link to thisPOST/downloadsDownload a title on this deviceSigned in
A movie or an episode, or every episode of a season or show (unwatched ones only with "unwatched"). Asking again for a title this device already has replaces its quality. Smaller qualities are prepared one at a time in the background; follow them on GET /downloads. Watch progress made offline goes back with PUT /progress/{id} and clientTime.
Request body
application/json object
| Field | Type | About |
|---|
itemId required | string | |
versionId | string | The file (a movie or an episode only) |
quality | string: original, high, medium, low | Default medium (original without ffmpeg) |
audio | integer | Audio stream index (default the profile's language |
unwatched | boolean | |
Responses
Link to thisGET/downloads/allEveryone's downloads, the room they take and the settings (owners only)Owner
Responses
- 200
OK
application/json object
| Field | Type | About |
|---|
settings | DownloadSettings | |
canConvert | boolean | |
usedBytes | integer | |
freeBytes | integer | |
dir | string | |
downloads | array of Download | |
Link to thisPUT/downloads/settingsChange the download settings (owners only)Owner
Request body
application/json DownloadSettingsResponses
Link to thisGET/downloads/{id}A download with what's needed to play it offlineSigned in
Adds the file's details and markers ("version"), the audio track kept ("audio"), and the text subtitles to save as WebVTT ("subtitles"). Picture subtitles don't come along.
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
Link to thisDELETE/downloads/{id}Remove a download (the server removes its copy once nobody wants it)Signed in
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
Link to thisGET/downloads/{id}/fileFetch a ready download (byte ranges; also takes ?token=)Signed in
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
- 200
The file
- 206
Part of the file
- 409
Not ready yet
Link to thisPOST/downloads/{id}/savedSay this device has all of the fileSigned in
The server removes its prepared copy a few days after every device that wanted it has it.
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
Link to thisWatch parties
Playing in sync on several devices.
POST/partiesStart a watch party around what this device is playing. Playback carries on as it was; invite lists device IDs to invite at once.Signed in
Request body
application/json object
| Field | Type | About |
|---|
itemId required | string | |
title | string | |
position | number | Seconds |
playing | boolean | |
invite | array of string | |
Responses
Link to thisGET/parties/devicesDevices online now that can be invited to a watch party.Signed in
Responses
- 200
[{device, name, user, label, icon}]
Link to thisPOST/parties/joinJoin with a 4-letter code, or by id to accept an invite.Signed in
Request body
application/json object
| Field | Type | About |
|---|
code | string | |
id | string | |
Responses
- 200
The party
- 404
No party has that code
- 409
The party is full
- 410
The party has ended
Link to thisGET/parties/mineThis device, its watch party (or null) and the parties inviting it.Signed in
POST/parties/{id}/controlPlay, pause, seek or load (a new title) for everyone. Starts are scheduled a second ahead on the server clock so every device starts on the same frame; seeks and loads wait until every device is ready.Signed in
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
Request body
application/json object
| Field | Type | About |
|---|
action required | string: play, pause, seek, load | |
position | number | |
itemId | string | |
title | string | |
autoplay | boolean | |
Responses
Link to thisPOST/parties/{id}/declineTurn down an invite.Signed in
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
Responses
Link to thisPOST/parties/{id}/inviteInvite devices by ID.Signed in
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
Request body
application/json object
| Field | Type | About |
|---|
devices | array of string | |
Responses
Link to thisPOST/parties/{id}/leaveLeave. The longest-standing member becomes host; an empty party ends.Signed in
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
Responses
Link to thisPOST/parties/{id}/reportA member reporting how it's doing; answers with the party.Signed in
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
Request body
application/json object
| Field | Type | About |
|---|
itemId | string | |
position | number | |
playing | boolean | |
stalled | boolean | |
ready | boolean | |
driftMs | number | |
Responses
Link to thisGET/parties/{id}/waitLong-poll: answers when the party's revision passes rev (200), after 25 seconds with nothing new (204), or when the party ended (410).Signed in
Parameters
| Name | In | Type | About |
|---|
id required | path | string | |
rev | query | integer | |
Responses
- 200
The party
- 204
Nothing new
- 410
The party ended
Link to thisRemote access
The tunnel, the Kinara account link and blocked devices.
GET/cloudKinara account link status (owners)Owner
Whether the server is linked to a Kinara account, the account, how it's reachable from outside (direct, relay), the certificate and the router port.
Responses
Link to thisPOST/cloud/linkStart linking to a Kinara account (owners)Owner
Gives a code to approve at cloud.kinara.media/link. The owner who starts it is who the account signs in as on this server.
Responses
Link to thisDELETE/cloud/linkStop waiting for a link code (owners)Owner
POST/cloud/unlinkUnlink from the Kinara account (owners)Owner
GET/remoteRemote access status (owners)Owner
The tunnel's state, its pairing ticket and QR code, devices connected through it, and blocked devices.
Responses
Link to thisPOST/remote/blockBlock a device from the tunnel (owners)Owner
Request body
application/json object
| Field | Type | About |
|---|
nodeId required | string | 64 lowercase hex characters |
label | string | |
Responses
Link to thisPUT/remote/settingsTurn remote access on or off (owners)Owner
Request body
application/json object
| Field | Type | About |
|---|
enabled | boolean | |
udpPort | integer | 0 picks one automatically |
Responses
Link to thisPOST/remote/unblockLet a blocked device in again (owners)Owner
Request body
application/json object
| Field | Type | About |
|---|
nodeId required | string | |
Responses
Link to thisIntegrations
Webhooks, incoming updates, title status and API keys.
POST/hooks/in/{token}Receive an update at an incoming hook's address (no sign-in; the address is the secret)No sign-in
Parameters
| Name | In | Type | About |
|---|
token required | path | string | |
Responses
Link to thisGET/integrations/eventsEvery webhook event and API key scope (owners)OwnerAPI key: webhooks:manage
Responses
- 200
{"items": [{"id", "group", "label", "description", "soon"?}], "scopes": [{"id", "label", "description"}]}
Link to thisGET/integrations/incomingIncoming hooks (owners, or webhooks:manage)OwnerAPI key: webhooks:manage
Responses
- 200
{"items": [Hook]}. Hook: {id, name, token, path, enabled, format: kinara|custom, mapping: {field: path}, stateMap: [{from, to}], progressScale: auto|fraction|percent, onDone: scan|nothing, expireHours, visibility: owners|everyone, lastReceivedAt, createdAt}
Link to thisPOST/integrations/incomingAdd an incoming hook (a token is made)OwnerAPI key: webhooks:manage
PUT/integrations/incoming/{id}Change an incoming hook (fields left out keep their value)OwnerAPI key: webhooks:manage
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
Link to thisDELETE/integrations/incoming/{id}Remove a hook, its log and the statuses it reportedOwnerAPI key: webhooks:manage
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
Link to thisGET/integrations/incoming/{id}/receivedWhat the hook was sent, newest first (last 200 kept)OwnerAPI key: webhooks:manage
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
limit | query | integer | |
Responses
- 200
{"items": [{id, at, ok, summary, body}]}
Link to thisPOST/integrations/incoming/{id}/tokenGive a hook a new address; the old one stops workingOwnerAPI key: webhooks:manage
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
Link to thisGET/integrations/keysAPI keys (owners)Owner
Responses
- 200
{"items": [{id, name, prefix, scopes, network: local|any, createdAt, lastUsedAt?, expiresAt?}]}
Link to thisPOST/integrations/keysMake a key: {name, scopes[], network, expiresDays: 0|30|90|365}. The answer has the whole key ("key") once.Owner
DELETE/integrations/keys/{id}Revoke a keyOwner
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
Link to thisGET/integrations/webhooksOutgoing webhooks (owners, or webhooks:manage)OwnerAPI key: webhooks:manage
Responses
- 200
{"items": [Webhook]}. Webhook: {id, name, url, enabled, secret, events[], libraries: "all"|number[], accounts: "all"|number[], headers: [{name, value}], retries (0..8), timeoutSec (5..30), includePaths, health: {state: ok|failing|never|paused, lastAt?, lastStatus, lastError?}, createdAt}
Link to thisPOST/integrations/webhooksAdd a webhook (a secret is made when none is sent)OwnerAPI key: webhooks:manage
Request body
application/json object
Responses
- 201
The webhook
- 400
What's wrong with it
Link to thisPUT/integrations/webhooks/{id}Change a webhook (fields left out keep their value)OwnerAPI key: webhooks:manage
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
Link to thisDELETE/integrations/webhooks/{id}Remove a webhook with its queue and logOwnerAPI key: webhooks:manage
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
Link to thisGET/integrations/webhooks/{id}/deliveriesRecent tries, newest first (kept 7 days)OwnerAPI key: webhooks:manage
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
limit | query | integer | |
Responses
- 200
{"items": [Delivery]}
Link to thisPOST/integrations/webhooks/{id}/deliveries/{delivery}/redeliverSend a logged delivery again now, with a new delivery ID; answers with the resultOwnerAPI key: webhooks:manage
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
delivery required | path | integer | |
Responses
Link to thisPOST/integrations/webhooks/{id}/secretMake a new secret; the old one stops workingOwnerAPI key: webhooks:manage
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
- 200
{"secret": "whsec_..."}
Link to thisPOST/integrations/webhooks/{id}/testSend a "test" event now; answers with the deliveryOwnerAPI key: webhooks:manage
Parameters
| Name | In | Type | About |
|---|
id required | path | integer | |
Responses
- 200
Delivery: {id, deliveryId, at, event, status (number|null), ms, attempt, error?, request, response? (first 4 KB)}
Link to thisGET/statusHow the server is set up (owners only)Owner
Version, warnings, whether metadata, converting and HLS are on, what ffmpeg can do, and what the background worker is doing.
Responses
Link to thisPOST/statusReport on a title on its way (an API key with status:write, or an owner)OwnerAPI key: status:write
The Kinara format, the same as an incoming hook: tmdbId (with kind), tvdbId, imdbId, or title and year; season, episode; state (queued, in_progress, done, failed) or progress (0 to 1 or 0 to 100); message; eta (seconds); id (your own reference). "done" clears the status and scans the title's library.
Request body
application/json object
Responses
- 202
Matched: {"matched": {"kind", "title", "year", "tmdbId"}, "state", "id"}
- 200
No title matched yet; the status is kept: {"matched": null, "kept": true}
- 400
Unreadable
- 429
More than 2 a second for one title or 60 a minute for one sender; see Retry-After
Link to this
Data types
The objects the endpoints above send and receive.
Backup
| Field | Type | About |
|---|
name | string | |
size | integer | |
version | string | The Kinara version that made it |
schema | integer | |
createdAt | integer | |
host | string | The computer it was made on |
reason | string: scheduled, manual, before-restore, before-update | |
counts | object | |
files | array of string | |
BackupSettings
| Field | Type | About |
|---|
auto | boolean | A backup every night at about 3:30 (default on) |
keep | integer | How many nightly and manual backups to keep (default 7) |
dir | string | The backups folder, a full path on the server ("" for backups in the data folder) |
CastMember
| Field | Type | About |
|---|
order | integer | |
name | string | |
role | string | |
photoUrl | string | Add ?w=<width> |
DeviceInfoOptional on sign-in (login, signup, cloud). Describes the app install so the owner sees what the device is and can trust it. Keep id the same for the life of the install: signing in again keeps the device's name and trust. Unknown fields are ignored. deviceName is still accepted and wins over name.
| Field | Type | About |
|---|
id | string | 8 to 64 of A-Z a-z 0-9 - _, made by the app on install |
name | string | |
platform | string: android, android-tv, ios, tvos, roku, web | |
brand | string | |
model | string | The web app sends "Chrome on Windows" and the like |
os | string | |
app | string | |
formFactor | string: tablet | Send "tablet" on tablets |
DeviceItem
| Field | Type | About |
|---|
id | string | The session's device ID (sign out with DELETE /devices/{id}) |
name | string | |
userId | integer | |
username | string | |
createdAt | integer | |
lastSeenAt | integer | |
current | boolean | |
nodeId | string | |
clientId | string | The app install; "s-" plus the session ID for apps that sent none |
platform | string | |
brand | string | |
model | string | |
os | string | |
app | string | |
icon | string: roku, shield, google-tv, fire-tv, android-tv, apple-tv, tv, iphone, ipad, android-phone, android-tablet, web-chrome, web-firefox, web-safari, web-edge, web-opera, web | |
trusted | boolean | Requests from this device are approved straight away |
label | string | |
Download
| Field | Type | About |
|---|
id | integer | |
itemId | string | |
versionId | string | |
quality | string: original, high, medium, low | |
state | string: queued, preparing, ready, failed, expired | expired means the prepared copy was removed to save room; fetching it prepares it again |
progress | number | Share prepared |
place | integer | Copies ahead of this one in line (queued only) |
size | integer | Bytes |
estimate | integer | Expected bytes |
error | string | |
fileUrl | string | Where to fetch it once ready (byte ranges; takes ?token=) |
deviceId | string | |
deviceName | string | |
thisDevice | boolean | Made on the device asking |
username | string | Whose it is (owner's list only) |
createdAt | integer | |
readyAt | integer | |
savedAt | integer | When the device said it has all of it |
item | Item | |
showTitle | string | |
showId | string | |
DownloadSettings
| Field | Type | About |
|---|
enabled | boolean | Apps can download titles to watch offline (default on) |
maxGB | integer | Room prepared copies may take on the server (default 100) |
perUser | integer | Downloads each account can have |
Error
| Field | Type | About |
|---|
error | string | |
ExternalSubtitle
| Field | Type | About |
|---|
index | integer | 1000000 and up: use it where a track index goes (/subtitles/{versionId}/{index}.vtt) |
language | string | ISO 639-2 |
title | string | What else the file name says |
format | string: subrip, ass, ssa, webvtt | |
forced | boolean | |
hearingImpaired | boolean | |
source | string: sidecar, download | |
url | string | The WebVTT |
removable | boolean | A download this account may delete |
Item
| Field | Type | About |
|---|
id | string | |
libraryId | integer | |
kind | string: movie, show, season, episode | |
parentId | string | |
title | string | |
sortTitle | string | |
year | integer | |
seasonNumber | integer | -1 when not a season or episode |
episodeNumber | integer | -1 when not an episode |
overview | string | |
rating | number | TMDB average |
runtimeMin | integer | |
airDate | string | |
matchState | string: pending, matched, unmatched, manual | |
matchConfidence | number | |
addedAt | integer | |
updatedAt | integer | |
posterUrl | string | Add ?w=<width> for a resized copy |
backdropUrl | string | |
watch | WatchState | |
Library
| Field | Type | About |
|---|
id | integer | |
name | string | |
path | string | The first folder |
folders | array of string | Every folder in the library, oldest first |
kind | string: movies, tv, other | |
lastScanAt | integer | |
stats | object | |
scan | object | Present after a scan has run since the server started. |
NextUp
| Field | Type | About |
|---|
episode | Item | |
inProgress | boolean | |
reason | string | |
gaps | array of object | |
Profile
| Field | Type | About |
|---|
id | integer | |
name | string | |
color | string | |
hasPin | boolean | |
kids | boolean | |
audioLang | string | ISO 639-2 code such as eng; empty = file default |
subtitleLang | string | |
subtitleMode | string: False, forced, always | |
Request
| Field | Type | About |
|---|
id | integer | |
kind | string: movie, show | |
tmdbId | integer | |
title | string | |
year | integer | |
posterUrl | string | |
mode | string: all, seasons, pilot | |
seasons | array of integer | |
status | string: pending, approved, declined, available | |
auto | boolean | Approved straight away (trusted device) |
note | string | |
declineReason | string | |
requestedBy | object | |
device | object | |
createdAt | integer | |
decidedAt | integer | |
RequestMarkerOn titles outside the library when there is a pending or approved request for them
| Field | Type | About |
|---|
id | integer | |
status | string: pending, approved | |
mine | boolean | |
mode | string | |
seasons | array of integer | |
Stream
| Field | Type | About |
|---|
index | integer | |
type | string: video, audio, subtitle | |
codec | string | |
profile | string | |
language | string | |
title | string | |
channels | integer | |
default | boolean | |
forced | boolean | |
SubtitleResult
| Field | Type | About |
|---|
fileId | integer | |
language | string | |
release | string | |
fileName | string | |
downloads | integer | |
rating | number | |
hearingImpaired | boolean | |
forced | boolean | |
ai | boolean | An AI translation |
machine | boolean | A machine translation |
trusted | boolean | |
fps | number | |
hashMatch | boolean | Made for this exact file |
uploader | string | |
TitleItemA TMDB title (trending, discover, search)
| Field | Type | About |
|---|
tmdbId | integer | |
kind | string: movie, show | |
title | string | |
year | integer | |
overview | string | |
rating | number | |
genres | array of string | |
posterUrl | string | |
backdropUrl | string | |
item | object | The library's copy |
request | RequestMarker | |
rank | integer | Place in a trending list |
TitlePage
| Field | Type | About |
|---|
items | array of TitleItem | |
page | integer | |
totalPages | integer | |
TrackPrefsA profile's choices for a show or movie, merged over the profile's defaults.
| Field | Type | About |
|---|
audioLang | string | |
subtitleLang | string | |
subtitleMode | string: , False, forced, always | |
UpdateStatus
| Field | Type | About |
|---|
current | string | This server's version |
latest | string | The newest release (once checked) |
available | boolean | latest is newer than current |
date | string | When latest came out (YYYY-MM-DD) |
notes | string | Link to what's new |
checkedAt | integer | Unix seconds of the last check that answered (0 never) |
error | string | Why the last check failed |
kind | string: windows, macos, deb, docker, other | How this copy was installed |
canInstall | boolean | Kinara can install the update itself (Windows and macOS installs) |
how | string | What to do when it can't: the apt or docker command, or where to download |
state | string: idle, downloading, verifying, ready, installing, failed | ready: downloaded and checked ahead, waiting to be installed |
needsPrompt | boolean | Installing on its own was tried and needs permission on the server's computer |
unattended | boolean | This kind of install can go in with nobody there (Windows can try; a Mac always asks) |
message | string | Why an install failed |
downloaded | integer | Bytes so far |
size | integer | The installer's size |
User
| Field | Type | About |
|---|
id | integer | |
username | string | |
isAdmin | boolean | |
createdAt | integer | Unix seconds |
profileId | integer | Who's watching on this device |
homeOnly | boolean | Signed in on the home network without a password (POST /auth/home) |
Version
| Field | Type | About |
|---|
id | string | |
itemId | string | |
relPath | string | |
part | integer | |
size | integer | |
container | string | |
durationSec | number | |
bitrate | integer | |
width | integer | |
height | integer | |
videoCodec | string | |
hdr | string: , hdr10, hlg, dolby_vision | |
dvProfile | integer | |
streams | array of Stream | |
subtitles | array of ExternalSubtitle | Subtitle files outside the video (next to it, or downloaded from
OpenSubtitles). Not in streams, whose order matches the player's
tracks. Fetch one as WebVTT from its url, like a text track.
|
streamUrl | string | |
transcodeUrl | string | Converts the file (when the server has ffmpeg) |
trickplayUrl | string | Preview thumbnails (once made) |
intro | object | Where the opening titles are, when found |
endCredits | object | Where the end credits are, when found (players offer the next episode from start) |
WatchState
| Field | Type | About |
|---|
itemId | string | |
positionSec | number | |
completed | boolean | |
playCount | integer | |
lastPlayedAt | integer | |
No endpoints match.