Developers

API reference

Everything the Kinara apps do goes through this API: 178 endpoints, version 0.2. Your own server publishes the same document at /openapi.yaml, so you can generate a client in any language.

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
    FieldTypeAbout
    settingsBackupSettings
    dirstringThe backups folder in use
    backupsarray of Backup
    nextAtintegerUnix seconds of the next nightly backup (0 when off)
    errorstring
POST/backupsMake a backup now (owners only)Owner
Responses
  • 201
    Made
    application/json Backup
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
NameInTypeAbout
settingsquerystring: 1
Request body
application/zip
Responses
  • 202
    Restarting to restore
  • 400
    Not a Kinara backup
PUT/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 BackupSettings
Responses
  • 200
    Saved
  • 400
    Not a full path
GET/backups/{name}Download a backup (owners only; also takes ?token=)Owner
Parameters
NameInTypeAbout
name requiredpathstring
Responses
  • 200
    The zip file
    application/zip
DELETE/backups/{name}Delete a backup (owners only)Owner
Parameters
NameInTypeAbout
name requiredpathstring
Responses
  • 204
    Deleted
POST/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
NameInTypeAbout
name requiredpathstring
Request body
application/json object
FieldTypeAbout
settingsbooleanAlso restore the settings file (kinara.env). Off by default.
Responses
  • 202
    Restarting to restore
  • 400
    Not a Kinara backup
  • 409
    A restore is already under way
GET/healthServer status (no sign-in needed)No sign-in
Responses
  • 200
    OK
    application/json object
    FieldTypeAbout
    okboolean
    productstringAlways "kinara", so apps scanning the network can recognise the server
    namestringThe server's name (set in Settings, else the computer's name)
    versionstring
    setupDoneboolean
    metadatabooleanWhether a TMDB key is set
    updatestringA 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).
GET/metrics/httpRequest counts and timings per route (owners)Owner
Responses
  • 200
    since and routes
GET/metrics/liveProcessor, GPU, memory and traffic (owners)Owner
Parameters
NameInTypeAbout
rangequerystringHow far back, such as 5m, 1h or 24h
Responses
  • 200
    Samples over the range
  • 501
    Metrics are off
GET/metrics/logsRecent warnings and errors (owners)Owner
Responses
  • 200
    Log entries
GET/metrics/storageDisk use of libraries, cache and data (owners)Owner
Responses
  • 200
    OK
GET/metrics/usageWatching over time (owners)Owner
Responses
  • 200
    OK
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
    FieldTypeAbout
    libraryinteger
    requestsinteger
    partyinteger
    activearray of object
GET/settingsServer settings (owners only)Owner
Responses
  • 200
    OK
    application/json object
    FieldTypeAbout
    listAccountsbooleanShow accounts on the sign-in screen
    allowSignupbooleanLet anyone who reaches the server make an account
    priorityDevicestringThe device that keeps its quality when the server is busy converting
    serverNamestringWhat apps and the Kinara account call this server, at most 40 characters; empty uses the computer's name
    updateCheckbooleanCheck the downloads site twice a day for a newer Kinara (default on)
    autoUpdatebooleanDownload 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)
    groupCollectionsbooleanShow a franchise's movies as one card in movie libraries (default on)
    homeSignInbooleanOn the home network, devices sign in by picking an account, no password (default on; see /auth/home)
    hasHomePinbooleanA PIN is asked for that (read only; set with PUT /settings/home-pin)
PUT/settingsSave server settings (owners only)Owner

Fields left out keep their values.

Responses
  • 200
    Saved
PUT/settings/home-pinSet or remove the PIN asked for signing in on the home network (owners only)Owner
Request body
application/json object
FieldTypeAbout
pinstring4 to 8 digits; empty removes it
Responses
  • 204
    Saved
  • 400
    Not 4 to 8 digits
POST/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
FieldTypeAbout
username requiredstring1 to 32 letters, digits, spaces, dots, dashes or underscores
password requiredstringAt least 8 characters
deviceNamestring
deviceDeviceInfo
Responses
  • 201
    The owner
  • 400
    Bad name or short password
  • 403
    Not from the home network
  • 409
    Already set up
GET/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
    FieldTypeAbout
    tnumber
GET/updateWhether a newer Kinara is out, and any install in progress (owners only)Owner
Responses
POST/update/checkCheck for a newer Kinara now (owners only)Owner
Responses
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
    Started
    application/json UpdateStatus
  • 409
    Nothing to install
  • 429
    Already installing

Sign-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
    FieldTypeAbout
    accountsarray of object
    allowSignupboolean
POST/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
FieldTypeAbout
token requiredstringThe cloud's access token for this server
deviceNamestring
deviceDeviceInfo
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
POST/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
FieldTypeAbout
username requiredstring
pinstringThe server's home sign-in PIN
profileIdintegerWho's watching; default the account's first profile without a PIN
profilePinstringThat profile's PIN
deviceNamestring
deviceDeviceInfo
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
POST/auth/loginSign in a deviceNo sign-in
Request body
application/json object
FieldTypeAbout
username requiredstring
password requiredstring
deviceNamestring
deviceDeviceInfo
Responses
  • 200
    Signed in
    application/json object
    FieldTypeAbout
    tokenstring
    userUser
    profilesarray of ProfileShow "Who's watching?" when there is more than one
  • 401
    Wrong username or password
  • 429
    Too many failed attempts
POST/auth/logoutSign this device outSigned in
Responses
  • 204
    Signed out
POST/auth/quick/approveQuick Connect - sign the code's device in as youFull sign-in
Request body
application/json object
FieldTypeAbout
code requiredstringSpaces are ignored
Responses
  • 200
    Approved
    application/json object
    FieldTypeAbout
    devicestring
  • 404
    No such code waiting
  • 429
    Too many wrong codes from this account or address
POST/auth/quick/cancelQuick Connect - forget a code this device no longer showsNo sign-in
Request body
application/json object
FieldTypeAbout
secret requiredstring
Responses
  • 204
    Forgotten (or already gone)
POST/auth/quick/pollQuick Connect - has the code been approved?No sign-in
Request body
application/json object
FieldTypeAbout
secret requiredstring
Responses
  • 200
    Approved and signed in; same shape as /auth/login. Works once.
  • 202
    Not approved yet
  • 410
    The code ran out
POST/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
FieldTypeAbout
deviceNamestring
deviceDeviceInfo
Responses
  • 200
    The code to show, and the secret to poll with (keep it on this device)
    application/json object
    FieldTypeAbout
    codestringSix digits
    secretstring
    expiresAtintegerUnix seconds
    intervalintegerSeconds between polls
  • 429
    Too many codes asked for from this address
  • 503
    Too many codes waiting on this server
GET/auth/quick/{code}Quick Connect - which device is waiting behind a codeSigned in
Parameters
NameInTypeAbout
code requiredpathstring
Responses
  • 200
    The waiting device
    application/json object
    FieldTypeAbout
    codestring
    devicestring
    expiresAtinteger
  • 404
    No such code waiting
  • 429
    Too many wrong codes from this account or address
POST/auth/signupMake an ordinary account and sign it in (only when the owner allows sign-up)No sign-in
Request body
application/json object
FieldTypeAbout
username requiredstring1 to 32 letters, digits, spaces, dots, dashes or underscores
password requiredstringAt least 8 characters
deviceNamestring
deviceDeviceInfo
Responses
  • 201
    Signed in; same shape as /auth/login
  • 400
    Bad name or short password
  • 403
    Sign-up is off
  • 409
    Name taken
GET/meThe signed-in userSigned in
Responses
  • 200
    OK
    application/json User
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
FieldTypeAbout
id requiredstring8 to 64 of A-Z, a-z, 0-9, - and _
namestring
platformstring
brandstring
modelstring
osstring
appstring
formFactorstring
Responses
  • 204
    Recorded
  • 400
    Missing or unusable id
PUT/me/device/nameName this device ("Living Room TV"), shown in watch party invites.Signed in
Request body
application/json object
FieldTypeAbout
name requiredstring
Responses
  • 200
    The device and its new name
GET/profilesThis account's profiles and the one this device is watching asSigned in
Responses
  • 200
    OK
    application/json object
    FieldTypeAbout
    profilesarray of Profile
    currentinteger
POST/profilesAdd a profile (up to 12)Signed in
Request body
application/json object
FieldTypeAbout
name requiredstring
colorstring
pinstring4 digits
kidsboolean
Responses
  • 201
    Created
    application/json Profile
PATCH/profiles/{id}Change a profile (fields not sent stay as they are; pin "" removes the PIN)Signed in
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 200
    Updated
  • 403
    The profile has a PIN and isn't the one selected
DELETE/profiles/{id}Remove a profile and its history (not the last one)Signed in
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 204
    Removed
POST/profiles/{id}/selectWatch as this profile on this deviceSigned in
Parameters
NameInTypeAbout
id requiredpathinteger
Request body
application/json object
FieldTypeAbout
pinstring
Responses
  • 200
    Selected
  • 403
    Wrong PIN
  • 429
    Too many wrong PINs
GET/usersEvery account (owners only)Owner
Responses
  • 200
    OK
POST/usersAdd an account (owners only)Owner
Request body
application/json object
FieldTypeAbout
usernamestring
passwordstring
isAdminboolean
Responses
  • 201
    The new account
  • 409
    Name taken
PATCH/users/{id}Set a new password (signs the account out everywhere) or change owner rightsOwner
Responses
  • 204
    Saved
  • 409
    That would leave the server without an owner
DELETE/users/{id}Remove an account and its history (not your own)Owner
Responses
  • 204
    Removed

Devices

Signed-in devices and what they report.

PUT/clients/{clientId}Trust or rename a device (admin)Owner
Parameters
NameInTypeAbout
clientId requiredpathstring
Request body
application/json object
FieldTypeAbout
trustedboolean
namestring1 to 80 characters
Responses
  • 200
    The device as GET /devices lists it
    application/json DeviceItem
  • 404
    No such device
GET/devicesSigned-in devices (admin)Owner
Responses
  • 200
    Devices, most recently seen first
    application/json array of DeviceItem
DELETE/devices/{id}Sign a device out (admin). It also stops being the priority device.Owner
Parameters
NameInTypeAbout
id requiredpathstring
Responses
  • 204
    Signed out
  • 404
    No such device

Libraries

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
NameInTypeAbout
pathquerystringAbsolute folder to list
Responses
  • 200
    The folder's subfolders
    application/json object
    FieldTypeAbout
    pathstringEmpty for the top level
    parentstringEmpty at a drive or the root
    librarystring
    videosintegerVideo files directly in this folder
    separatorstring
    truncatedboolean
    foldersarray of object
  • 400
    Not an absolute path
  • 404
    The folder can't be opened
GET/librariesLibraries with counts and scan statusSigned inAPI key: library:read
Responses
  • 200
    OK
    application/json array of Library
POST/librariesAdd a library and start its first scan (admin)Owner
Request body
application/json object
FieldTypeAbout
namestring
path requiredstringAbsolute folder path on the server
kind requiredstring: movies, tv, other
Responses
  • 201
    Created
  • 400
    Invalid folder or kind
PATCH/libraries/{id}Rename a library (admin)Owner
Parameters
NameInTypeAbout
id requiredpathinteger
Request body
application/json object
FieldTypeAbout
namestring
Responses
  • 200
    The library
    application/json Library
  • 400
    Empty or too long name
  • 404
    No such library
DELETE/libraries/{id}Remove a library (admin). Files are not touched.Owner
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 204
    Removed
POST/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
NameInTypeAbout
id requiredpathinteger
Request body
application/json object
FieldTypeAbout
path requiredstringAbsolute folder path on the server
Responses
  • 201
    The library
    application/json Library
  • 400
    Invalid or overlapping folder
  • 404
    No such library
DELETE/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
NameInTypeAbout
id requiredpathinteger
path requiredquerystringThe folder exactly as the library lists it
Responses
  • 200
    The library, plus how many file records were dropped
    application/json object
  • 400
    That is the library's only folder
  • 404
    The folder isn't part of this library
  • 409
    A scan is running
GET/libraries/{id}/problemsFiles that couldn't be read or placed, and titles that didn't match (owners only)Owner
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 200
    {"unreadable": [...], "unplaced": [...], "unmatched": [...]}
POST/libraries/{id}/rematchRetry matching for unmatched items, then scan (admin)Owner
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 202
    Started
POST/libraries/{id}/scanStart a scan (admin, or an API key with library:scan)OwnerAPI key: library:scan
Parameters
NameInTypeAbout
id requiredpathinteger
pathquerystringScan only this folder, relative to one of the library's folders (or a full path inside one).
Responses
  • 202
    Started
  • 400
    The folder isn't inside the library or doesn't exist
  • 409
    A scan is already running

Titles

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
NameInTypeAbout
id requiredpathstring
ord requiredpathinteger
wqueryinteger
tokenquerystring
Responses
  • 200
    Image
  • 404
    No photo
GET/genresGenres with available titles, most titles firstSigned in
Parameters
NameInTypeAbout
kindquerystring: movie, show
Responses
  • 200
    OK
    application/json object
    FieldTypeAbout
    genresarray of object
GET/homeContinue Watching and Recently AddedSigned in
Responses
  • 200
    OK
    application/json object
    FieldTypeAbout
    continueWatchingarray of object
    recentlyAddedarray of Item
GET/images/{id}/{kind}Artwork, resized on requestSigned in
Parameters
NameInTypeAbout
id requiredpathstring
kind requiredpathstring: poster, backdrop, still
wqueryintegerWidth; rounded up to 160, 240, 320, 400, 640, 960, 1280 or 1920
tokenquerystring
Responses
  • 200
    Image
  • 404
    No artwork
GET/images/{id}/{kind}/colorThe artwork's dominant colour, for tinting the app to match itSigned in
Parameters
NameInTypeAbout
id requiredpathstring
kind requiredpathstring: poster, backdrop, still
Responses
  • 200
    "#rrggbb", or "" when the artwork is mostly grey or missing
    application/json object
    FieldTypeAbout
    colorstring
GET/itemsBrowse itemsSigned inAPI key: library:read
Parameters
NameInTypeAbout
libraryIdqueryinteger
kindquerystring: movie, show, season, episode
parentIdquerystring
matchStatequerystring
sortquerystring: title, added, year, length, rating
genrequerystringA TMDB genre such as "Comedy"
watchedquerystring: unwatched, started, watchedFor the current profile. Shows: started = some episodes seen, watched = all
maxRuntimequeryintegerMinutes
qualityquerystring: bestOnly titles with a 4K or HDR file
qquerystringTitle words
collectionsquerystring: 1Movie 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.
limitqueryinteger
offsetqueryinteger
Responses
  • 200
    OK
    application/json object
    FieldTypeAbout
    itemsarray of Item
    nextOffsetintegerPresent when more items may follow
GET/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
    FieldTypeAbout
    lettersarray of object
GET/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
NameInTypeAbout
id requiredpathstring
Responses
PATCH/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
NameInTypeAbout
id requiredpathstring
Request body
application/json object
FieldTypeAbout
titlestring
sortTitlestringFollows the title (without The, A or An) unless given
yearinteger
overviewstring
ratingnumber0 to 10
airDatestringYYYY-MM-DD
genresarray of string
Responses
  • 200
    Saved
  • 400
    Empty title
GET/items/{id}/artwork/{kind}TMDB's artwork for a title, best first (owners only)Owner
Parameters
NameInTypeAbout
id requiredpathstring
kind requiredpathstring: poster, backdrop, still
Responses
  • 200
    choices: path, url (to show it), width, height, language, votes, current
  • 409
    The title isn't matched to TMDB
GET/items/{id}/childrenSeasons of a show, or episodes of a season, in orderSigned inAPI key: library:read
Parameters
NameInTypeAbout
id requiredpathstring
Responses
  • 200
    OK
    application/json object
    FieldTypeAbout
    itemsarray of Item
GET/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
NameInTypeAbout
id requiredpathstring
unwatchedquerystring: 1
Responses
  • 200
    OK
    application/json object
    FieldTypeAbout
    enabledboolean
    countintegerMovies or episodes covered
    defaultstringThe quality to pick unless the person chose one
    qualitiesarray of object
GET/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
NameInTypeAbout
id requiredpathstring
Responses
  • 200
    OK
PUT/items/{id}/images/{kind}Use TMDB artwork: {"path": "/abc.jpg"} from the list (owners only; locks it)Owner
Parameters
NameInTypeAbout
id requiredpathstring
kind requiredpathstring: poster, backdrop, still
Responses
  • 200
    Set
POST/items/{id}/images/{kind}/uploadUpload artwork, a JPEG or PNG up to 20 MB as the body (owners only; locks it)Owner
Parameters
NameInTypeAbout
id requiredpathstring
kind requiredpathstring: poster, backdrop, still
Request body
image/jpeg
image/png
Responses
  • 200
    Set
  • 400
    Not a JPEG or PNG
POST/items/{id}/matchFix a match by TMDB ID (admin)Owner
Parameters
NameInTypeAbout
id requiredpathstring
Request body
application/json object
FieldTypeAbout
tmdbIdinteger
Responses
  • 200
    Matched
    application/json Item
GET/items/{id}/optimizeThe optimize rule for a title (or its library) and its copies (owners only)Owner
Parameters
NameInTypeAbout
id requiredpathstring
Responses
  • 200
    OK
PUT/items/{id}/prefsSave audio and subtitle choices for the show or movie this item belongs toSigned in
Parameters
NameInTypeAbout
id requiredpathstring
Request body
application/json TrackPrefs
Responses
  • 200
    The merged preferences now in effect
    application/json TrackPrefs
GET/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
NameInTypeAbout
id requiredpathstring
pagequeryinteger
Responses
  • 200
    items, page, totalPages
  • 404
    No such title
POST/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
NameInTypeAbout
id requiredpathstring
Responses
  • 200
    Unlocked
POST/items/{id}/watchedMark watched or unwatched (shows and seasons apply to every episode)Signed in
Parameters
NameInTypeAbout
id requiredpathstring
Request body
application/json object
FieldTypeAbout
watchedboolean
Responses
  • 204
    Done

Playback

Streaming, conversion, progress and seek previews.

DELETE/hls/session/{session}Stop an HLS session and delete its filesSigned in
Parameters
NameInTypeAbout
session requiredpathstring
Responses
  • 204
    Stopped
GET/hls/session/{session}/{file}An HLS session's playlist (index.m3u8) or segment (init.mp4, segNNNNN.m4s)No sign-in
Parameters
NameInTypeAbout
session requiredpathstring
file requiredpathstring
Responses
  • 200
    The file
  • 404
    The session ended
POST/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
NameInTypeAbout
id requiredpathstringVersion ID
Responses
  • 200
    Started
    application/json object
    FieldTypeAbout
    sessionstring
    playliststringLike "/api/v1/hls/session/<session>/index.m3u8"
    startnumberSeconds into the file the stream begins at
  • 503
    Converting as many streams as it can
GET/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
  • 200
    OK
POST/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
FieldTypeAbout
libraryIdinteger
itemIdstringA movie
qualitystring: high, medium, lowDefault medium
Responses
  • 201
    Added
  • 400
    No ffmpeg
DELETE/optimize/{id}Remove a rule (its copies go unless a download still uses them; owners only)Owner
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 204
    Removed
POST/optimize/{id}/retryPut a rule's failed copies back in line (owners only)Owner
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 200
    How many were put back
GET/playbackWhat each device is playing now, with its conversion's speed (admin)OwnerAPI key: activity:read
Responses
  • 200
    List of devices playing
GET/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
NameInTypeAbout
version requiredquerystring
Responses
  • 200
    Advice
    application/json object
    FieldTypeAbout
    actionstring: ok, lower
    reasonstring
    nextobject
PUT/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
NameInTypeAbout
id requiredpathstring
Request body
application/json object
FieldTypeAbout
positionSec requirednumber
versionIdstring
clientTimeintegerUnix seconds when the position was read
Responses
GET/speedtestRandom bytes, for the app to measure the connectionSigned in
Parameters
NameInTypeAbout
bytesqueryinteger64 KB to 32 MB
Responses
  • 200
    Bytes
GET/stream/{id}Direct play a file (supports Range requests)Signed in
Parameters
NameInTypeAbout
id requiredpathstringVersion ID
tokenquerystring
Responses
  • 200
    The whole file
  • 206
    Part of the file
GET/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
NameInTypeAbout
id requiredpathstringVersion ID
startquerynumberSeconds to begin at
video requiredquerystring: copy, h264
maxBitratequeryintegerkbit/s for re-encoded video (default 8000)
maxHeightqueryintegerPixels for re-encoded video (default 1080)
audioqueryintegerSource stream index of the audio track (default the first)
containerquerystring: mkv, mp4mkv (default) or mp4: fragmented MP4 for web browsers, without subtitles
doviquerystring: 1, 0Whether 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 requiredquerystring: copy, aac
tokenquerystring
Responses
  • 200
    The converted stream
  • 400
    Bad options
  • 503
    Too many conversions running
GET/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
NameInTypeAbout
id requiredpathstringVersion ID
atquerynumberSeconds
dirquerystring: forward
Responses
  • 200
    request and start, in seconds
  • 404
    File not found
GET/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
NameInTypeAbout
id requiredpathstringVersion ID
Responses
  • 200
    OK
    application/json object
    FieldTypeAbout
    intervalinteger
    widthinteger
    heightinteger
    colsinteger
    rowsinteger
    countinteger
    sheetsinteger
    sheetUrlstringHas "{n}" where the sheet number goes
  • 404
    Not made yet
GET/trickplay/{id}/{sheet}One sheet of preview thumbnails (JPEG)Signed in
Parameters
NameInTypeAbout
id requiredpathstring
sheet requiredpathstringLike "1.jpg"
tokenquerystring
Responses
  • 200
    Image

Subtitles

Subtitle tracks, search and the automatic finder.

GET/subtitles/autoAutomatic subtitles, the services and today's use, and what's missing (owners only)Owner
Responses
  • 200
    OK
POST/subtitles/auto/retryLook again for missing subtitles (all, or one file's with versionId; owners only)Owner
Responses
  • 200
    How many were put back in line
GET/subtitles/settingsSubtitle settings (owners only)Owner
Responses
  • 200
    everyone, cleanDvd, username, hasPassword, apiKey (last 4), available
PUT/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
FieldTypeAbout
everyonebooleanEvery account can search and download (default on)
cleanDvdbooleanRedraw DVD subtitles in Kinara's style (default on)
usernamestringOpenSubtitles account
passwordstring
apiKeystringYour own OpenSubtitles API key ("" for Kinara's)
Responses
  • 200
    Saved
GET/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
NameInTypeAbout
id requiredpathstring
Responses
  • 200
    OK
POST/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
NameInTypeAbout
id requiredpathstring
file requiredpathintegerThe subtitle's index (1000000 and up)
Responses
  • 200
    OK
  • 400
    A file next to the video (the owner's own)
POST/subtitles/{versionId}/downloadDownload a search result and keep it with the file, for everyoneSigned in
Parameters
NameInTypeAbout
versionId requiredpathstring
Request body
application/json object
FieldTypeAbout
fileId requiredinteger
languagestring
releasestring
hearingImpairedboolean
forcedboolean
Responses
  • 201
    Downloaded
    application/json ExternalSubtitle
  • 200
    Already downloaded
  • 429
    The day's download limit is used up (the account's share
DELETE/subtitles/{versionId}/files/{index}Remove a downloaded subtitle (owners, or whoever downloaded it)Signed in
Parameters
NameInTypeAbout
versionId requiredpathstring
index requiredpathinteger
Responses
  • 204
    Removed
  • 409
    The file is next to the video; Kinara doesn't delete those
GET/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
NameInTypeAbout
versionId requiredpathstring
index requiredpathstring
tokenquerystring
Responses
  • 200
    WebVTT
    text/vtt
  • 404
    No such track
GET/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
NameInTypeAbout
versionId requiredpathstring
index requiredpathintegerThe subtitle stream index
fromqueryintegerEvents to skip (ones already had)
Responses
  • 200
    The timeline from from on
    application/json object
    FieldTypeAbout
    widthinteger
    heightinteger
    doneboolean
    totalinteger
    errorstring
    eventsarray of object
  • 404
    No such file or track
  • 422
    The track isn't PGS
  • 501
    The server has no ffmpeg
GET/subtitles/{versionId}/{index}/pgs/{n}.pngOne picture of a PGS trackSigned in
Parameters
NameInTypeAbout
versionId requiredpathstring
index requiredpathinteger
n requiredpathinteger
Responses
  • 200
    PNG with transparency
  • 404
    Not made (yet)

Discover 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
    FieldTypeAbout
    sectionsarray of object
    genresarray of object
    updatedAtinteger
GET/discover/genre/{kind}/{genreId}Titles in one genre, pagedSigned in
Parameters
NameInTypeAbout
kind requiredpathstring: movie, show
genreId requiredpathinteger
sortquerystring: popular, rating, new
pagequeryinteger
Responses
GET/discover/{section}One Discover section, paged (20 a page)Signed in
Parameters
NameInTypeAbout
section requiredpathstring
pagequeryinteger
Responses
  • 200
    A page
    application/json TitlePage
  • 404
    No such section
GET/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}}]}
DELETE/ontheway/{id}Clear a status (owners, or webhooks:manage)OwnerAPI key: webhooks:manage
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 204
    Cleared
GET/requestsRequests, newest first. Owners see everyone's; others their own.Signed in
Parameters
NameInTypeAbout
statusquerystring: pending, approved, declined, available
limitqueryinteger
offsetqueryinteger
Responses
  • 200
    The requests, and how many are pending
    application/json object
    FieldTypeAbout
    itemsarray of Request
    pendinginteger
POST/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
FieldTypeAbout
kind requiredstring: movie, show
tmdbId requiredinteger
modestring: all, seasons, pilotShows only
seasonsarray of integerWith mode seasons
notestring
Responses
  • 201
    Made. The request, plus sent.
    application/json object
  • 200
    An identical open request already exists (duplicate true)
  • 409
    Already on the server
DELETE/requests/{id}Cancel your own pending request; owners may remove anySigned in
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 204
    Removed
  • 409
    Not pending any more
POST/requests/{id}/approveApprove a pending request (admin). Emits request.approved.Owner
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 200
    The request
  • 409
    Not pending
POST/requests/{id}/declineDecline a pending or approved request (admin). Emits request.declined.Owner
Parameters
NameInTypeAbout
id requiredpathinteger
Request body
application/json object
FieldTypeAbout
reasonstring
Responses
  • 200
    The request
GET/search/externalSearch TMDB for movies and shows, in the library or notSigned in
Parameters
NameInTypeAbout
q requiredquerystring
pagequeryinteger
Responses
  • 200
    A page; titles the library has carry item
    application/json TitlePage
GET/tmdb-image/{file}TMDB artwork for titles not in the library (Trending), resized on requestSigned in
Parameters
NameInTypeAbout
file requiredpathstringLike "abc123.jpg"
wqueryinteger
tokenquerystring
Responses
  • 200
    Image
GET/tmdb/show/{id}/season/{n}A season's episodes from TMDB (titles, dates, stills), for shows outside the librarySigned in
Parameters
NameInTypeAbout
id requiredpathintegerThe show's TMDB ID
n requiredpathintegerSeason number (0 for specials)
Responses
  • 200
    OK
  • 404
    TMDB has no such season
  • 501
    No TMDB key on the server
GET/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
NameInTypeAbout
kind requiredpathstring: movie, show
id requiredpathintegerThe TMDB ID
Responses
  • 200
    OK
  • 400
    Not movie or show
  • 404
    TMDB has no such title
  • 501
    No TMDB key on the server
  • 502
    TMDB couldn't be reached
GET/tmdb/{kind}/{id}/similarMore like this, for a title not on the serverSigned in
Parameters
NameInTypeAbout
kind requiredpathstring: movie, show
id requiredpathintegerTMDB ID
pagequeryinteger
Responses
  • 200
    items, page, totalPages

Lists

Watchlists and other lists.

GET/listsThis profile's lists ("My List" first, made on first use) with countsSigned in
Responses
  • 200
    OK
POST/listsMake a named listSigned in
Request body
application/json object
FieldTypeAbout
namestring
Responses
  • 201
    The new list
  • 400
    Name must be 1 to 80 characters
PATCH/lists/{id}Rename a listSigned in
Responses
  • 204
    Renamed
DELETE/lists/{id}Remove a named list ("My List" can't be removed)Signed in
Responses
  • 204
    Removed
  • 400
    That's My List
GET/lists/{id}/itemsThe titles on a list, newest first ("default" for My List)Signed in
Parameters
NameInTypeAbout
id requiredpathstringA list ID, or "default"
Responses
  • 200
    {"listId": 1, "items": [...]}
PUT/lists/{id}/items/{item}Add a title to a list (again is fine)Signed in
Responses
  • 204
    Added
DELETE/lists/{id}/items/{item}Take a title off a listSigned in
Responses
  • 204
    Removed

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
    FieldTypeAbout
    enabledboolean
    downloadsarray of Download
POST/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
FieldTypeAbout
itemId requiredstring
versionIdstringThe file (a movie or an episode only)
qualitystring: original, high, medium, lowDefault medium (original without ffmpeg)
audiointegerAudio stream index (default the profile's language
unwatchedboolean
Responses
  • 201
    Added
    application/json object
    FieldTypeAbout
    downloadsarray of Download
  • 403
    Downloads are turned off
  • 409
    The account has as many downloads as the owner allows
GET/downloads/allEveryone's downloads, the room they take and the settings (owners only)Owner
Responses
  • 200
    OK
    application/json object
    FieldTypeAbout
    settingsDownloadSettings
    canConvertboolean
    usedBytesinteger
    freeBytesinteger
    dirstring
    downloadsarray of Download
PUT/downloads/settingsChange the download settings (owners only)Owner
Request body
application/json DownloadSettings
Responses
  • 200
    Saved
GET/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
NameInTypeAbout
id requiredpathinteger
Responses
  • 200
    OK
DELETE/downloads/{id}Remove a download (the server removes its copy once nobody wants it)Signed in
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 204
    Removed
GET/downloads/{id}/fileFetch a ready download (byte ranges; also takes ?token=)Signed in
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 200
    The file
  • 206
    Part of the file
  • 409
    Not ready yet
POST/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
NameInTypeAbout
id requiredpathinteger
Responses
  • 204
    Noted

Watch 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
FieldTypeAbout
itemId requiredstring
titlestring
positionnumberSeconds
playingboolean
invitearray of string
Responses
  • 201
    The party
GET/parties/devicesDevices online now that can be invited to a watch party.Signed in
Responses
  • 200
    [{device, name, user, label, icon}]
POST/parties/joinJoin with a 4-letter code, or by id to accept an invite.Signed in
Request body
application/json object
FieldTypeAbout
codestring
idstring
Responses
  • 200
    The party
  • 404
    No party has that code
  • 409
    The party is full
  • 410
    The party has ended
GET/parties/mineThis device, its watch party (or null) and the parties inviting it.Signed in
Responses
  • 200
    {me, party, invites}
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
NameInTypeAbout
id requiredpathstring
Request body
application/json object
FieldTypeAbout
action requiredstring: play, pause, seek, load
positionnumber
itemIdstring
titlestring
autoplayboolean
Responses
  • 200
    The party
POST/parties/{id}/declineTurn down an invite.Signed in
Parameters
NameInTypeAbout
id requiredpathstring
Responses
  • 204
    Declined
POST/parties/{id}/inviteInvite devices by ID.Signed in
Parameters
NameInTypeAbout
id requiredpathstring
Request body
application/json object
FieldTypeAbout
devicesarray of string
Responses
  • 200
    The party
POST/parties/{id}/leaveLeave. The longest-standing member becomes host; an empty party ends.Signed in
Parameters
NameInTypeAbout
id requiredpathstring
Responses
  • 204
    Left
POST/parties/{id}/reportA member reporting how it's doing; answers with the party.Signed in
Parameters
NameInTypeAbout
id requiredpathstring
Request body
application/json object
FieldTypeAbout
itemIdstring
positionnumber
playingboolean
stalledboolean
readyboolean
driftMsnumber
Responses
  • 200
    The party
GET/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
NameInTypeAbout
id requiredpathstring
revqueryinteger
Responses
  • 200
    The party
  • 204
    Nothing new
  • 410
    The party ended

Remote 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
  • 200
    OK
POST/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
  • 200
    The code and where to enter it
  • 501
    No Kinara cloud set up (KINARA_CLOUD_URL)
POST/cloud/unlinkUnlink from the Kinara account (owners)Owner
Responses
  • 204
    Unlinked
GET/remoteRemote access status (owners)Owner

The tunnel's state, its pairing ticket and QR code, devices connected through it, and blocked devices.

Responses
  • 200
    OK
POST/remote/blockBlock a device from the tunnel (owners)Owner
Request body
application/json object
FieldTypeAbout
nodeId requiredstring64 lowercase hex characters
labelstring
Responses
  • 204
    Blocked
PUT/remote/settingsTurn remote access on or off (owners)Owner
Request body
application/json object
FieldTypeAbout
enabledboolean
udpPortinteger0 picks one automatically
Responses
  • 204
    Saved
  • 409
    Remote access isn't available on this server
POST/remote/unblockLet a blocked device in again (owners)Owner
Request body
application/json object
FieldTypeAbout
nodeId requiredstring
Responses
  • 204
    Unblocked

Integrations

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
NameInTypeAbout
token requiredpathstring
Responses
  • 202
    Matched a title
  • 200
    No title matched yet; kept
  • 400
    Unreadable
  • 403
    The hook is turned off
  • 404
    No such hook
  • 429
    Too many updates; see Retry-After
GET/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"}]}
GET/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}
POST/integrations/incomingAdd an incoming hook (a token is made)OwnerAPI key: webhooks:manage
Responses
  • 201
    The hook
PUT/integrations/incoming/{id}Change an incoming hook (fields left out keep their value)OwnerAPI key: webhooks:manage
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 200
    The hook
DELETE/integrations/incoming/{id}Remove a hook, its log and the statuses it reportedOwnerAPI key: webhooks:manage
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 204
    Removed
GET/integrations/incoming/{id}/receivedWhat the hook was sent, newest first (last 200 kept)OwnerAPI key: webhooks:manage
Parameters
NameInTypeAbout
id requiredpathinteger
limitqueryinteger
Responses
  • 200
    {"items": [{id, at, ok, summary, body}]}
POST/integrations/incoming/{id}/tokenGive a hook a new address; the old one stops workingOwnerAPI key: webhooks:manage
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 200
    The hook
GET/integrations/keysAPI keys (owners)Owner
Responses
  • 200
    {"items": [{id, name, prefix, scopes, network: local|any, createdAt, lastUsedAt?, expiresAt?}]}
POST/integrations/keysMake a key: {name, scopes[], network, expiresDays: 0|30|90|365}. The answer has the whole key ("key") once.Owner
Responses
  • 201
    The key
DELETE/integrations/keys/{id}Revoke a keyOwner
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 204
    Revoked
GET/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}
POST/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
PUT/integrations/webhooks/{id}Change a webhook (fields left out keep their value)OwnerAPI key: webhooks:manage
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 200
    The webhook
DELETE/integrations/webhooks/{id}Remove a webhook with its queue and logOwnerAPI key: webhooks:manage
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 204
    Removed
GET/integrations/webhooks/{id}/deliveriesRecent tries, newest first (kept 7 days)OwnerAPI key: webhooks:manage
Parameters
NameInTypeAbout
id requiredpathinteger
limitqueryinteger
Responses
  • 200
    {"items": [Delivery]}
POST/integrations/webhooks/{id}/deliveries/{delivery}/redeliverSend a logged delivery again now, with a new delivery ID; answers with the resultOwnerAPI key: webhooks:manage
Parameters
NameInTypeAbout
id requiredpathinteger
delivery requiredpathinteger
Responses
  • 200
    The new Delivery
POST/integrations/webhooks/{id}/secretMake a new secret; the old one stops workingOwnerAPI key: webhooks:manage
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 200
    {"secret": "whsec_..."}
POST/integrations/webhooks/{id}/testSend a "test" event now; answers with the deliveryOwnerAPI key: webhooks:manage
Parameters
NameInTypeAbout
id requiredpathinteger
Responses
  • 200
    Delivery: {id, deliveryId, at, event, status (number|null), ms, attempt, error?, request, response? (first 4 KB)}
GET/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
  • 200
    OK
POST/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

Data types

The objects the endpoints above send and receive.

Backup
FieldTypeAbout
namestring
sizeinteger
versionstringThe Kinara version that made it
schemainteger
createdAtinteger
hoststringThe computer it was made on
reasonstring: scheduled, manual, before-restore, before-update
countsobject
filesarray of string
BackupSettings
FieldTypeAbout
autobooleanA backup every night at about 3:30 (default on)
keepintegerHow many nightly and manual backups to keep (default 7)
dirstringThe backups folder, a full path on the server ("" for backups in the data folder)
CastMember
FieldTypeAbout
orderinteger
namestring
rolestring
photoUrlstringAdd ?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.
FieldTypeAbout
idstring8 to 64 of A-Z a-z 0-9 - _, made by the app on install
namestring
platformstring: android, android-tv, ios, tvos, roku, web
brandstring
modelstringThe web app sends "Chrome on Windows" and the like
osstring
appstring
formFactorstring: tabletSend "tablet" on tablets
DeviceItem
FieldTypeAbout
idstringThe session's device ID (sign out with DELETE /devices/{id})
namestring
userIdinteger
usernamestring
createdAtinteger
lastSeenAtinteger
currentboolean
nodeIdstring
clientIdstringThe app install; "s-" plus the session ID for apps that sent none
platformstring
brandstring
modelstring
osstring
appstring
iconstring: 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
trustedbooleanRequests from this device are approved straight away
labelstring
Download
FieldTypeAbout
idinteger
itemIdstring
versionIdstring
qualitystring: original, high, medium, low
statestring: queued, preparing, ready, failed, expiredexpired means the prepared copy was removed to save room; fetching it prepares it again
progressnumberShare prepared
placeintegerCopies ahead of this one in line (queued only)
sizeintegerBytes
estimateintegerExpected bytes
errorstring
fileUrlstringWhere to fetch it once ready (byte ranges; takes ?token=)
deviceIdstring
deviceNamestring
thisDevicebooleanMade on the device asking
usernamestringWhose it is (owner's list only)
createdAtinteger
readyAtinteger
savedAtintegerWhen the device said it has all of it
itemItem
showTitlestring
showIdstring
DownloadSettings
FieldTypeAbout
enabledbooleanApps can download titles to watch offline (default on)
maxGBintegerRoom prepared copies may take on the server (default 100)
perUserintegerDownloads each account can have
Error
FieldTypeAbout
errorstring
ExternalSubtitle
FieldTypeAbout
indexinteger1000000 and up: use it where a track index goes (/subtitles/{versionId}/{index}.vtt)
languagestringISO 639-2
titlestringWhat else the file name says
formatstring: subrip, ass, ssa, webvtt
forcedboolean
hearingImpairedboolean
sourcestring: sidecar, download
urlstringThe WebVTT
removablebooleanA download this account may delete
Item
FieldTypeAbout
idstring
libraryIdinteger
kindstring: movie, show, season, episode
parentIdstring
titlestring
sortTitlestring
yearinteger
seasonNumberinteger-1 when not a season or episode
episodeNumberinteger-1 when not an episode
overviewstring
ratingnumberTMDB average
runtimeMininteger
airDatestring
matchStatestring: pending, matched, unmatched, manual
matchConfidencenumber
addedAtinteger
updatedAtinteger
posterUrlstringAdd ?w=<width> for a resized copy
backdropUrlstring
watchWatchState
Library
FieldTypeAbout
idinteger
namestring
pathstringThe first folder
foldersarray of stringEvery folder in the library, oldest first
kindstring: movies, tv, other
lastScanAtinteger
statsobject
scanobjectPresent after a scan has run since the server started.
NextUp
FieldTypeAbout
episodeItem
inProgressboolean
reasonstring
gapsarray of object
Profile
FieldTypeAbout
idinteger
namestring
colorstring
hasPinboolean
kidsboolean
audioLangstringISO 639-2 code such as eng; empty = file default
subtitleLangstring
subtitleModestring: False, forced, always
Request
FieldTypeAbout
idinteger
kindstring: movie, show
tmdbIdinteger
titlestring
yearinteger
posterUrlstring
modestring: all, seasons, pilot
seasonsarray of integer
statusstring: pending, approved, declined, available
autobooleanApproved straight away (trusted device)
notestring
declineReasonstring
requestedByobject
deviceobject
createdAtinteger
decidedAtinteger
RequestMarkerOn titles outside the library when there is a pending or approved request for them
FieldTypeAbout
idinteger
statusstring: pending, approved
mineboolean
modestring
seasonsarray of integer
Stream
FieldTypeAbout
indexinteger
typestring: video, audio, subtitle
codecstring
profilestring
languagestring
titlestring
channelsinteger
defaultboolean
forcedboolean
SubtitleResult
FieldTypeAbout
fileIdinteger
languagestring
releasestring
fileNamestring
downloadsinteger
ratingnumber
hearingImpairedboolean
forcedboolean
aibooleanAn AI translation
machinebooleanA machine translation
trustedboolean
fpsnumber
hashMatchbooleanMade for this exact file
uploaderstring
TitleItemA TMDB title (trending, discover, search)
FieldTypeAbout
tmdbIdinteger
kindstring: movie, show
titlestring
yearinteger
overviewstring
ratingnumber
genresarray of string
posterUrlstring
backdropUrlstring
itemobjectThe library's copy
requestRequestMarker
rankintegerPlace in a trending list
TitlePage
FieldTypeAbout
itemsarray of TitleItem
pageinteger
totalPagesinteger
TrackPrefsA profile's choices for a show or movie, merged over the profile's defaults.
FieldTypeAbout
audioLangstring
subtitleLangstring
subtitleModestring: , False, forced, always
UpdateStatus
FieldTypeAbout
currentstringThis server's version
lateststringThe newest release (once checked)
availablebooleanlatest is newer than current
datestringWhen latest came out (YYYY-MM-DD)
notesstringLink to what's new
checkedAtintegerUnix seconds of the last check that answered (0 never)
errorstringWhy the last check failed
kindstring: windows, macos, deb, docker, otherHow this copy was installed
canInstallbooleanKinara can install the update itself (Windows and macOS installs)
howstringWhat to do when it can't: the apt or docker command, or where to download
statestring: idle, downloading, verifying, ready, installing, failedready: downloaded and checked ahead, waiting to be installed
needsPromptbooleanInstalling on its own was tried and needs permission on the server's computer
unattendedbooleanThis kind of install can go in with nobody there (Windows can try; a Mac always asks)
messagestringWhy an install failed
downloadedintegerBytes so far
sizeintegerThe installer's size
User
FieldTypeAbout
idinteger
usernamestring
isAdminboolean
createdAtintegerUnix seconds
profileIdintegerWho's watching on this device
homeOnlybooleanSigned in on the home network without a password (POST /auth/home)
Version
FieldTypeAbout
idstring
itemIdstring
relPathstring
partinteger
sizeinteger
containerstring
durationSecnumber
bitrateinteger
widthinteger
heightinteger
videoCodecstring
hdrstring: , hdr10, hlg, dolby_vision
dvProfileinteger
streamsarray of Stream
subtitlesarray of ExternalSubtitleSubtitle 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.
streamUrlstring
transcodeUrlstringConverts the file (when the server has ffmpeg)
trickplayUrlstringPreview thumbnails (once made)
introobjectWhere the opening titles are, when found
endCreditsobjectWhere the end credits are, when found (players offer the next episode from start)
WatchState
FieldTypeAbout
itemIdstring
positionSecnumber
completedboolean
playCountinteger
lastPlayedAtinteger