Information
- License: Proprietary
- OpenAPI version:
3.1.0
The Kivo media server's public API.
The contract between the Kivo server and every client: the web app, the Android TV app and third-party tools. It is long-lived; changes are additive.
Paths. Everything is under /api/v1, except /healthz. Segments are
lowercase plural nouns, kebab-case when multiword (/play-events).
An ID in a path is {id}, or {artworkId} and similar when nested.
Actions on a resource are POST /{resource}/{id}/{action}, and
actions on a whole collection POST /{collection}/{action}
(/downloads/check, /device-codes/poll); no other path holds a
verb. A play’s media lives under /api/v1/plays/{id}/.
Methods and status codes. GET reads. POST creates (201 with
the resource) or runs an action (200 with its result, 202 with
Accepted when it starts background work, 204 when there is
nothing to return). PATCH edits with merge semantics, so only fields
present change, and returns the updated resource, except a play’s
heartbeat, which returns 204. DELETE returns 204. There is no
PUT.
JSON. Field names are camelCase. Every resource has id;
references are <thing>Id (libraryId, workId). Kind fields
(kind, type, state and the like) are open strings with documented
values, never closed enums: clients skip values they don’t know.
Kind-specific data is nested under the kind’s name
(episode: {seasonNumber, number, airDate}). Absent optional fields
are omitted, never null. Documented string values are camelCase
(directPlay, inProgress), except task names, which are the
server’s own, and quality names (4k, 1080p), which are names,
not words.
Units. Times are RFC 3339 UTC strings named …At. Durations and
positions are integer milliseconds named …Ms, on the version’s
timeline for playback. Bitrates are bits per second named …Bps.
Sizes are bytes named …Bytes.
Lists. A list is the envelope {items} of one item type, and
a paged list’s page size is limit. A grid’s schema is named
…Page, a feed’s …Feed and a whole list’s …List
(WorkSummaryPage, WorkSummaryFeed, WorkSummaryList). Each kind of list has one
paging style. A grid (a library’s works, collections, authors and
book series, and Favorites) is paged by position: offset items
from the start, with total on every page. A feed (the Unmatched
and Missing files lists, activity, history, play events and the
logs) is read in order: next is an opaque keyset cursor, passed back as after,
and total, where a feed has one, is set on its first page only.
A list that isn’t paged comes back whole, with neither: an item’s
children and files, a collection’s root works, Search, Home, and the
short lists the server holds whole (libraries, users, sessions,
plays, problems, folders, artwork choices and matches). Filters are plain
query parameters, repeated for several values, a range as its
From and To.
Errors. Every non-2xx response is application/problem+json
(RFC 9457) with a stable code and the requestId. Clients translate
codes; detail is for logs. Something the caller may not see is 404,
the same as something that doesn’t exist. A request body over 1 MB
(26 MB for an artwork upload) is 413 with invalidRequest.
Naming. operationIds are verbNoun camelCase. Paths name the
resource listed (/libraries/{id}/authors), and each operation does
one job. An acronym is a word in a name (ApiKey, HdrFormat, ExternalId). A thing is
called by one word everywhere: a movie, show, episode, video or
book is a work, and a count of them is workCount. One schema per concept: WorkSummary is a work’s summary
everywhere (a library’s works, children, search, Home), WorkDetail
its detail page; a collection, an author and a book series have
summaries of their own.
Access. Every operation states its access level in
x-kivo-access; nothing is implicit:
public: no credentials.signedIn: a signed-in session or an API key.viewer: a signed-in session or an API key; reads are filtered by
its user’s Viewer.admin: a viewer whose user is an admin.cast: a viewer, or a cast token. A cast token reaches these
routes only, as an ordinary user even when its user is an admin;
on any other route it is 403 with code forbidden.play: the play’s ID in the path is the credential. These routes
take no cookie or token, answer cross-origin GET and HEAD
and their preflights without credentials (Google Cast), and check
the play’s owner’s current permissions on every request.Two more extensions state an operation’s policy:
x-kivo-receiver: true: the Cast receiver calls it, so it answers
the receiver’s origin across origins, preflights included, without
credentials. Every cast operation is one too.x-kivo-upload: true: its body is a file, which may be up to 26 MB
and take 10 minutes to send.Every request a Kivo app makes says what is asking, a header per field:
Kivo-App: the app, such as kivo-android, kivo-apple, kivo-web or kivo-tizen.Kivo-App-Version: its version.Kivo-Platform: what it runs on, a ClientPlatform value.Kivo-OS-Version: the operating system’s version.Kivo-Device-Model: the device’s model, coarse: Android’s
Build.MODEL, an Apple machine identifier such as iPhone16,2, or the
browser’s family and major version.Each value is printable ASCII, at most 100 characters; the server leaves out one that isn’t. A session keeps the identity it signed in with, and takes a changed one from its next request; a play takes it from its start request. The server shows it to the account and to admins, and sends it nowhere else. The screen’s size and the device’s own name aren’t sent.
Signing in makes a session. The web client’s is a browser session,
which uses the HttpOnly, SameSite=Strict session cookie set at
sign-in (named __Host-kivo_session over TLS). An app’s is an app
session, whose token it sends as Authorization: Bearer <token>. An
Authorization header with another scheme is ignored, and the session
cookie counts. Tokens are never accepted in query strings. Answers are
Cache-Control: no-store, except media, images and fonts. Unsafe methods on a cookie session
need a same-origin Origin header. An app signs in with
POST /api/v1/sessions and a password, or by linking: it creates a
code with POST /api/v1/device-codes, shows the code and linkUrl,
and polls POST /api/v1/device-codes/poll every interval seconds
until a signed-in user approves or denies the code. A browser can link
the same way, as a browser link. A 401, or the live-events socket
closing with 4401, means the session was signed out. Scripts and other
tools send an API key, which an admin creates under Admin, API keys
(POST /api/v1/api-keys), the same way: Authorization: Bearer <key>.
GET /api/v1/users/me is the user behind either.
Casting gives the Cast receiver a cast token: a signed-in session
creates one with POST /api/v1/cast-tokens and passes it to the
receiver in the Cast load request. The receiver sends it as
Authorization: Bearer <token> to the cast routes, starting and
running plays as the session’s user. It ends when that session does,
30 minutes after its last use, or 24 hours after it was made, whichever
comes first; the receiver ends it with
DELETE /api/v1/cast-tokens/current when it closes.
A user has one session per client. An app sends clientId, a random
ID it makes once per install and keeps, when it signs in or creates
a device code. A browser gets one from the server: the HttpOnly,
SameSite=Lax client cookie (named __Host-kivo_client over TLS) set
when it signs in, which lasts 400 days and outlives signing out. Signing
in again from a client revokes that user’s session on it; another
user’s session on the same client stays.
GET /api/v1/server names the server by its stable id, which apps key
their sign-in by. On the local network, the server advertises itself by
DNS-SD over multicast DNS as a _kivo._tcp service named by its
serverName, on its own host name, kivo- and the last six hex digits
of its ID. The service’s TXT record holds id (the server’s ID), v
(its version), path (its base path, empty at the root) and tls (1
over HTTPS, else 0). An app browses for the service, resolves it to an
address and port, and confirms the ID with GET /api/v1/server. The
server needs host networking to be seen.
GET /api/v1/events upgrades to a WebSocket. The server sends
EventStreamMessage frames as JSON text: a snapshot first, then
event hints carrying only IDs and counts, and resync when the client
fell behind. There are no sequence numbers; a client that reconnects
applies the new snapshot and refetches what it keeps live.
The web client’s session cookie; __Host-kivo_session over TLS.
Security scheme type: apiKey
Cookie parameter name: kivo_session
An app session’s token or an API key, in the Authorization header of every request, the live-events WebSocket’s upgrade too, where it may instead be the kivo-bearer.<token> subprotocol; never in a URL.
Security scheme type: http