mirror of
https://github.com/gabehf/Koito.git
synced 2026-04-22 12:01:52 -07:00
Add API Documentation
This commit is contained in:
parent
0ec7b458cc
commit
33780a9ee3
2 changed files with 854 additions and 0 deletions
|
|
@ -51,6 +51,7 @@ export default defineConfig({
|
||||||
label: "Reference",
|
label: "Reference",
|
||||||
items: [
|
items: [
|
||||||
{ label: "Configuration Options", slug: "reference/configuration" },
|
{ label: "Configuration Options", slug: "reference/configuration" },
|
||||||
|
{ label: "Web API", slug: "reference/api" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
853
docs/src/content/docs/reference/api.md
Normal file
853
docs/src/content/docs/reference/api.md
Normal file
|
|
@ -0,0 +1,853 @@
|
||||||
|
---
|
||||||
|
title: Web API Reference
|
||||||
|
description: HTTP API reference for integrating with or building tooling around Koito.
|
||||||
|
---
|
||||||
|
|
||||||
|
This document describes the Koito HTTP API as implemented in the source code. It is intended to help developers integrate with Koito or build tooling around it.
|
||||||
|
|
||||||
|
All endpoints are under the base path `/apis/web/v1`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
Some endpoints require authentication; others are publicly accessible depending on your instance configuration.
|
||||||
|
|
||||||
|
### Session (cookie-based)
|
||||||
|
Obtained via `POST /apis/web/v1/login`. The server sets a session cookie used by the web UI.
|
||||||
|
|
||||||
|
### API Key (token-based)
|
||||||
|
Pass the key in the `Authorization` header:
|
||||||
|
```
|
||||||
|
Authorization: Token <your-api-key>
|
||||||
|
```
|
||||||
|
API keys can be managed from the Koito web UI under Settings → API Keys.
|
||||||
|
|
||||||
|
### Endpoint access levels
|
||||||
|
|
||||||
|
| Level | Applies to |
|
||||||
|
|---|---|
|
||||||
|
| **Public** | `GET /config`, `GET /health`, `POST /login`, `POST /logout` |
|
||||||
|
| **Login gate** — public by default; session or API key required if login is enabled | All read endpoints (`stats`, `top-*`, `listen-activity`, etc.) |
|
||||||
|
| **Session or API key** (always enforced) | Write/admin endpoints (`export`, `merge`, `delete`, etc.) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Query Parameters
|
||||||
|
|
||||||
|
Many endpoints share these parameters.
|
||||||
|
|
||||||
|
### Period (`period`)
|
||||||
|
Shortcuts for common time ranges. Accepted values:
|
||||||
|
|
||||||
|
| Value | Range |
|
||||||
|
|---|---|
|
||||||
|
| `day` | Last 24 hours |
|
||||||
|
| `week` | Last 7 days |
|
||||||
|
| `month` | Last 30 days |
|
||||||
|
| `year` | Last 365 days |
|
||||||
|
| `all_time` | All recorded listens |
|
||||||
|
|
||||||
|
### Custom timeframe
|
||||||
|
Fine-grained range selection (takes precedence over `period`):
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `from` | integer (Unix timestamp) | Range start |
|
||||||
|
| `to` | integer (Unix timestamp) | Range end (defaults to now) |
|
||||||
|
| `year` | integer | Calendar year (e.g. `2025`) |
|
||||||
|
| `month` | integer | Calendar month `1–12` (combined with `year`) |
|
||||||
|
| `week` | integer | ISO week number (combined with `year`) |
|
||||||
|
|
||||||
|
### Timezone (`tz`)
|
||||||
|
IANA timezone name (e.g. `Europe/Paris`). Can also be set via a `tz` cookie. Falls back to UTC if unset. Affects period boundaries and listen-activity bucketing.
|
||||||
|
|
||||||
|
### Pagination
|
||||||
|
Used by list endpoints (`top-*`, `listens`):
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Max |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `limit` | integer | `100` | `500` |
|
||||||
|
| `page` | integer | `1` | — |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
### `GET /health`
|
||||||
|
Returns `200 OK` when the server is ready. Returns `503 Service Unavailable` during startup.
|
||||||
|
|
||||||
|
No authentication required.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /config`
|
||||||
|
Returns server configuration visible to clients.
|
||||||
|
|
||||||
|
No authentication required.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"default_theme": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** `default_theme` is an empty string `""` when not explicitly configured.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /stats`
|
||||||
|
Returns aggregate listening statistics.
|
||||||
|
|
||||||
|
**Authentication:** Public if login is disabled (default); otherwise requires session or API key
|
||||||
|
|
||||||
|
**Query parameters:** [Period / timeframe](#common-query-parameters), [`tz`](#timezone-tz)
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"listen_count": 47425,
|
||||||
|
"track_count": 1894,
|
||||||
|
"album_count": 548,
|
||||||
|
"artist_count": 355,
|
||||||
|
"minutes_listened": 190781
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** `minutes_listened` is computed as total seconds listened divided by 60.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /top-artists`
|
||||||
|
Returns the most-listened artists for a time period, sorted by listen count.
|
||||||
|
|
||||||
|
**Authentication:** Public if login is disabled (default); otherwise requires session or API key
|
||||||
|
|
||||||
|
**Query parameters:** [Period / timeframe](#common-query-parameters), [Pagination](#pagination), [`tz`](#timezone-tz)
|
||||||
|
|
||||||
|
**Response:** Paginated list of ranked artists.
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"item": {
|
||||||
|
"id": 12,
|
||||||
|
"musicbrainz_id": null,
|
||||||
|
"name": "Radiohead",
|
||||||
|
"aliases": ["radiohead"],
|
||||||
|
"image": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||||
|
"listen_count": 58,
|
||||||
|
"time_listened": 0,
|
||||||
|
"first_listen": 0,
|
||||||
|
"all_time_rank": 0
|
||||||
|
},
|
||||||
|
"rank": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_record_count": 357,
|
||||||
|
"items_per_page": 10,
|
||||||
|
"has_next_page": true,
|
||||||
|
"current_page": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** `time_listened`, `first_listen`, and `all_time_rank` are always `0` in this endpoint. Use `GET /summary` or `GET /artist?id=<id>` to get actual time listened.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /top-albums`
|
||||||
|
Returns the most-listened albums for a time period.
|
||||||
|
|
||||||
|
**Authentication:** Public if login is disabled (default); otherwise requires session or API key
|
||||||
|
|
||||||
|
**Query parameters:** [Period / timeframe](#common-query-parameters), [Pagination](#pagination), [`tz`](#timezone-tz)
|
||||||
|
|
||||||
|
**Response:** Same paginated structure as `/top-artists`, with album objects:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"item": {
|
||||||
|
"id": 42,
|
||||||
|
"musicbrainz_id": null,
|
||||||
|
"title": "OK Computer",
|
||||||
|
"image": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||||
|
"artists": [{ "id": 12, "name": "Radiohead" }],
|
||||||
|
"is_various_artists": false,
|
||||||
|
"listen_count": 15,
|
||||||
|
"time_listened": 0,
|
||||||
|
"first_listen": 0,
|
||||||
|
"all_time_rank": 0
|
||||||
|
},
|
||||||
|
"rank": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_record_count": 559,
|
||||||
|
"items_per_page": 10,
|
||||||
|
"has_next_page": true,
|
||||||
|
"current_page": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** `time_listened`, `first_listen`, and `all_time_rank` are always `0` in this endpoint.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /top-tracks`
|
||||||
|
Returns the most-listened tracks for a time period.
|
||||||
|
|
||||||
|
**Authentication:** Public if login is disabled (default); otherwise requires session or API key
|
||||||
|
|
||||||
|
**Query parameters:** [Period / timeframe](#common-query-parameters), [Pagination](#pagination), `artist_id` (integer, filter by artist), `album_id` (integer, filter by album), [`tz`](#timezone-tz)
|
||||||
|
|
||||||
|
**Response:** Same paginated structure, with track objects:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"item": {
|
||||||
|
"id": 7,
|
||||||
|
"title": "Karma Police",
|
||||||
|
"artists": [{ "id": 12, "name": "Radiohead" }],
|
||||||
|
"musicbrainz_id": null,
|
||||||
|
"listen_count": 32,
|
||||||
|
"duration": 0,
|
||||||
|
"image": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||||
|
"album_id": 42,
|
||||||
|
"time_listened": 0,
|
||||||
|
"first_listen": 0,
|
||||||
|
"all_time_rank": 0
|
||||||
|
},
|
||||||
|
"rank": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_record_count": 1888,
|
||||||
|
"items_per_page": 10,
|
||||||
|
"has_next_page": true,
|
||||||
|
"current_page": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** `duration`, `time_listened`, `first_listen`, and `all_time_rank` are always `0` in this endpoint. Use `GET /track?id=<id>` or `GET /summary` to get actual values.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /listen-activity`
|
||||||
|
Returns listen counts bucketed by time step, suitable for charts and heatmaps.
|
||||||
|
|
||||||
|
**Authentication:** Public if login is disabled (default); otherwise requires session or API key
|
||||||
|
|
||||||
|
**Query parameters:**
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `step` | string | `day` | Bucket size: `day`, `week`, `month`, `year` |
|
||||||
|
| `range` | integer | — | Number of steps to return (going back from today). **Required** when using `step`; omitting it returns `null`. |
|
||||||
|
| `year` | integer | — | Return data for a specific calendar year |
|
||||||
|
| `month` | integer | — | Combined with `year`, return a specific month |
|
||||||
|
| `artist_id` | integer | — | Filter by artist |
|
||||||
|
| `album_id` | integer | — | Filter by album |
|
||||||
|
| `track_id` | integer | — | Filter by track |
|
||||||
|
| `tz` | string | — | [Timezone](#timezone-tz) |
|
||||||
|
|
||||||
|
> When `year` (and optionally `month`) is specified, `range` and `step` are ignored and the full calendar period is returned.
|
||||||
|
|
||||||
|
**Response:** Array of buckets ordered by `start_time`.
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{ "start_time": "2026-04-01T00:00:00+02:00", "listens": 75 },
|
||||||
|
{ "start_time": "2026-04-02T00:00:00+02:00", "listens": 42 },
|
||||||
|
{ "start_time": "2026-04-03T00:00:00+02:00", "listens": 0 }
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Days with zero listens are included as `{ "listens": 0 }` to ensure a continuous series.
|
||||||
|
|
||||||
|
> **Note:** `start_time` timestamps reflect the server's local timezone (e.g., `+01:00` or `+02:00` for Europe/Paris with DST), not UTC `Z`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /listens`
|
||||||
|
Returns a paginated list of individual listen records (scrobbles), newest first.
|
||||||
|
|
||||||
|
**Authentication:** Public if login is disabled (default); otherwise requires session or API key
|
||||||
|
|
||||||
|
**Query parameters:** [Period / timeframe](#common-query-parameters), [Pagination](#pagination), `track_id` (integer, filter by track), [`tz`](#timezone-tz)
|
||||||
|
|
||||||
|
> **Note:** A time filter (`period`, `from`/`to`, `year`, etc.) is required. Without one, the server internally uses zero timestamps (`0001-01-01`) as the range, which matches no records.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"time": "2026-04-18T21:14:00Z",
|
||||||
|
"track": {
|
||||||
|
"id": 7,
|
||||||
|
"title": "Karma Police",
|
||||||
|
"artists": [{ "id": 12, "name": "Radiohead" }],
|
||||||
|
"musicbrainz_id": null,
|
||||||
|
"listen_count": 0,
|
||||||
|
"duration": 0,
|
||||||
|
"image": null,
|
||||||
|
"album_id": 0,
|
||||||
|
"time_listened": 0,
|
||||||
|
"first_listen": 0,
|
||||||
|
"all_time_rank": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_record_count": 47377,
|
||||||
|
"items_per_page": 100,
|
||||||
|
"has_next_page": true,
|
||||||
|
"current_page": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** Track objects within listen records only contain `id`, `title`, and `artists`; all other stat fields (`listen_count`, `duration`, `time_listened`, etc.) are `0` or `null`. Use `GET /track?id=<id>` to fetch full track metadata.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /now-playing`
|
||||||
|
Returns the track currently being scrobbled, if any.
|
||||||
|
|
||||||
|
**Authentication:** Public if login is disabled (default); otherwise requires session or API key
|
||||||
|
|
||||||
|
**Response (nothing playing):**
|
||||||
|
```json
|
||||||
|
{ "currently_playing": false, "track": {} }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (playing):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"currently_playing": true,
|
||||||
|
"track": {
|
||||||
|
"id": 1883,
|
||||||
|
"title": "Slow Decline",
|
||||||
|
"artists": [{ "id": 355, "name": "Lumi Noir" }],
|
||||||
|
"musicbrainz_id": null,
|
||||||
|
"listen_count": 37,
|
||||||
|
"duration": 221,
|
||||||
|
"image": "d8da11cc-3768-4301-ab98-d2ada7a43a6d",
|
||||||
|
"album_id": 558,
|
||||||
|
"time_listened": 8177,
|
||||||
|
"first_listen": 1776338132,
|
||||||
|
"all_time_rank": 252
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /artist`
|
||||||
|
Returns a single artist by ID.
|
||||||
|
|
||||||
|
**Authentication:** Public if login is disabled (default); otherwise requires session or API key
|
||||||
|
|
||||||
|
**Query parameters:**
|
||||||
|
|
||||||
|
| Parameter | Type | Required |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | integer | Yes |
|
||||||
|
|
||||||
|
**Response:** Artist object (same shape as items in `/top-artists`, but with all stat fields fully populated: `time_listened`, `first_listen`, and `all_time_rank` are real values).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /album`
|
||||||
|
Returns a single album by ID.
|
||||||
|
|
||||||
|
**Authentication:** Public if login is disabled (default); otherwise requires session or API key
|
||||||
|
|
||||||
|
**Query parameters:**
|
||||||
|
|
||||||
|
| Parameter | Type | Required |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | integer | Yes |
|
||||||
|
|
||||||
|
**Response:** Album object (same shape as items in `/top-albums`, but with all stat fields fully populated: `time_listened`, `first_listen`, and `all_time_rank` are real values).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /track`
|
||||||
|
Returns a single track by ID.
|
||||||
|
|
||||||
|
**Authentication:** Public if login is disabled (default); otherwise requires session or API key
|
||||||
|
|
||||||
|
**Query parameters:**
|
||||||
|
|
||||||
|
| Parameter | Type | Required |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | integer | Yes |
|
||||||
|
|
||||||
|
**Response:** Track object (same shape as items in `/top-tracks`, but with all stat fields fully populated: `duration`, `time_listened`, `first_listen`, and `all_time_rank` are real values).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /search`
|
||||||
|
Searches artists, albums, and tracks by name.
|
||||||
|
|
||||||
|
**Authentication:** Public if login is disabled (default); otherwise requires session or API key
|
||||||
|
|
||||||
|
**Query parameters:**
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `q` | string | Search query. Prefix with `id:` to look up all entities with a specific numeric ID. |
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"artists": [ /* Artist objects */ ],
|
||||||
|
"albums": [ /* Album objects */ ],
|
||||||
|
"tracks": [ /* Track objects */ ]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** When searching by name, stat fields (`listen_count`, `time_listened`, `first_listen`, `all_time_rank`, `duration`) are `0` in results. When searching by ID (`id:<n>`), full stats are returned. "No results" searches may still return partial matches (e.g., `q=zzz` may match tracks titled "Zzz").
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /summary`
|
||||||
|
Returns a rich summary for a time period, including top 5 artists/albums/tracks and aggregate statistics. Unlike `/top-*` endpoints, `time_listened` is populated here. Note that `first_listen` and `all_time_rank` remain `0` even in summary responses.
|
||||||
|
|
||||||
|
**Authentication:** Public if login is disabled (default); otherwise requires session or API key
|
||||||
|
|
||||||
|
**Query parameters:** [Period / timeframe](#common-query-parameters), [`tz`](#timezone-tz)
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"top_artists": [ /* top 5 RankedItem<Artist> — time_listened is populated */ ],
|
||||||
|
"top_albums": [ /* top 5 RankedItem<Album> — time_listened is populated */ ],
|
||||||
|
"top_tracks": [ /* top 5 RankedItem<Track> — time_listened is populated */ ],
|
||||||
|
"minutes_listened": 1748,
|
||||||
|
"avg_minutes_listened_per_day": 291.0,
|
||||||
|
"plays": 418,
|
||||||
|
"avg_plays_per_day": 69.67,
|
||||||
|
"unique_tracks": 139,
|
||||||
|
"unique_albums": 67,
|
||||||
|
"unique_artists": 59,
|
||||||
|
"new_tracks": 22,
|
||||||
|
"new_albums": 8,
|
||||||
|
"new_artists": 6
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`new_*` fields count items heard for the first time within the requested period.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /interest`
|
||||||
|
Returns listen counts distributed across N time buckets for a specific artist, album, or track. Used to show how interest in an item has evolved over time.
|
||||||
|
|
||||||
|
**Authentication:** Public if login is disabled (default); otherwise requires session or API key
|
||||||
|
|
||||||
|
**Query parameters:**
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `buckets` | integer | Yes | Number of time buckets to split history into |
|
||||||
|
| `artist_id` | integer | — | Filter by artist |
|
||||||
|
| `album_id` | integer | — | Filter by album |
|
||||||
|
| `track_id` | integer | — | Filter by track |
|
||||||
|
|
||||||
|
**Response:** Array of buckets.
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"bucket_start": "2025-01-01T00:00:00Z",
|
||||||
|
"bucket_end": "2025-04-01T00:00:00Z",
|
||||||
|
"listen_count": 120
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /aliases`
|
||||||
|
Returns all aliases for a given artist, album, or track.
|
||||||
|
|
||||||
|
**Authentication:** Public if login is disabled (default); otherwise requires session or API key
|
||||||
|
|
||||||
|
**Query parameters:** Exactly one of `artist_id`, `album_id`, or `track_id` (integer).
|
||||||
|
|
||||||
|
**Response:** Array of alias objects.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /artists`
|
||||||
|
Returns all artists associated with an album or track.
|
||||||
|
|
||||||
|
**Authentication:** Public if login is disabled (default); otherwise requires session or API key
|
||||||
|
|
||||||
|
**Query parameters:** Exactly one of `album_id` or `track_id` (integer).
|
||||||
|
|
||||||
|
**Response:** Array of artist objects (same shape as the artist objects within `/top-artists` items — no rank or pagination wrapper).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /export`
|
||||||
|
Exports the full listen history as a JSON file (Koito's own format). This is the same file available from the web UI.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Response:** JSON file download.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Authentication Endpoints
|
||||||
|
|
||||||
|
### `POST /login`
|
||||||
|
Authenticates a user and sets a session cookie.
|
||||||
|
|
||||||
|
No authentication required.
|
||||||
|
|
||||||
|
**Form parameters:**
|
||||||
|
|
||||||
|
| Parameter | Required | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `username` | Yes | — |
|
||||||
|
| `password` | Yes | — |
|
||||||
|
| `remember_me` | No | `"true"` extends session to 30 days (default 24 hours) |
|
||||||
|
|
||||||
|
**Response:** `204 No Content`. Sets `koito_session` cookie.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /logout`
|
||||||
|
Clears the current session cookie.
|
||||||
|
|
||||||
|
No authentication required.
|
||||||
|
|
||||||
|
**Response:** `204 No Content`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Write / Admin Endpoints
|
||||||
|
|
||||||
|
All endpoints in this section require **session or API key** authentication (always enforced, regardless of login configuration).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /listen`
|
||||||
|
Manually submits a listen by track ID.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Form parameters:**
|
||||||
|
|
||||||
|
| Parameter | Required | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `track_id` | Yes | Integer ID of the track |
|
||||||
|
| `unix` | Yes | Unix timestamp (must not be in the future) |
|
||||||
|
| `client` | No | Client name (defaults to `"Koito Web UI"`) |
|
||||||
|
|
||||||
|
**Response:** `201 Created`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `DELETE /listen`
|
||||||
|
Deletes a specific listen record.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Query parameters:**
|
||||||
|
|
||||||
|
| Parameter | Required | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `track_id` | Yes | Integer ID of the track |
|
||||||
|
| `unix` | Yes | Unix timestamp of the listen to delete |
|
||||||
|
|
||||||
|
**Response:** `204 No Content`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `DELETE /artist`
|
||||||
|
Deletes an artist by ID.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Query parameters:** `id` (integer, required).
|
||||||
|
|
||||||
|
**Response:** `204 No Content`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `DELETE /album`
|
||||||
|
Deletes an album by ID.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Query parameters:** `id` (integer, required).
|
||||||
|
|
||||||
|
**Response:** `204 No Content`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `DELETE /track`
|
||||||
|
Deletes a track by ID.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Query parameters:** `id` (integer, required).
|
||||||
|
|
||||||
|
**Response:** `204 No Content`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /merge/tracks`
|
||||||
|
Merges one track into another. All listens from `from_id` are reassigned to `to_id`, then `from_id` is deleted.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Query parameters:**
|
||||||
|
|
||||||
|
| Parameter | Required | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `from_id` | Yes | Track to merge from (will be deleted) |
|
||||||
|
| `to_id` | Yes | Track to merge into |
|
||||||
|
|
||||||
|
**Response:** `204 No Content`. Returns `500` if `from_id` does not exist.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /merge/albums`
|
||||||
|
Merges one album into another.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Query parameters:**
|
||||||
|
|
||||||
|
| Parameter | Required | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `from_id` | Yes | Album to merge from (will be deleted) |
|
||||||
|
| `to_id` | Yes | Album to merge into |
|
||||||
|
| `replace_image` | No | `"true"` to replace the target album's image with the source's |
|
||||||
|
|
||||||
|
**Response:** `204 No Content`. Returns `500` if `from_id` does not exist.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /merge/artists`
|
||||||
|
Merges one artist into another.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Query parameters:**
|
||||||
|
|
||||||
|
| Parameter | Required | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `from_id` | Yes | Artist to merge from (will be deleted) |
|
||||||
|
| `to_id` | Yes | Artist to merge into |
|
||||||
|
| `replace_image` | No | `"true"` to replace the target artist's image with the source's |
|
||||||
|
|
||||||
|
**Response:** `204 No Content`. Returns `500` if `from_id` does not exist.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `PATCH /album`
|
||||||
|
Updates album metadata.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Query parameters:**
|
||||||
|
|
||||||
|
| Parameter | Required | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | Yes | Album ID |
|
||||||
|
| `is_various_artists` | No | `"true"` or `"false"` |
|
||||||
|
|
||||||
|
**Response:** `204 No Content`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `PATCH /mbzid`
|
||||||
|
Sets the MusicBrainz ID for an artist, album, or track.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Form parameters:** `mbz_id` (UUID, required) + exactly one of `artist_id`, `album_id`, or `track_id`.
|
||||||
|
|
||||||
|
**Response:** `204 No Content`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /replace-image`
|
||||||
|
Replaces the cover image for an artist or album, either from a URL or a file upload.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Form parameters:** Exactly one of `artist_id` or `album_id`, plus either `image_url` (string — must be a direct image URL with an image content-type, no redirects) or `image` (multipart file upload).
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{ "success": true, "image": "<new-image-uuid>" }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /aliases`
|
||||||
|
Adds an alias to an artist, album, or track.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Form parameters:** `alias` (string, required) + exactly one of `artist_id`, `album_id`, or `track_id`.
|
||||||
|
|
||||||
|
**Response:** `201 Created`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /aliases/delete`
|
||||||
|
Removes an alias from an artist, album, or track.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Form parameters:** `alias` (string, required) + exactly one of `artist_id`, `album_id`, or `track_id`.
|
||||||
|
|
||||||
|
**Response:** `204 No Content`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /aliases/primary`
|
||||||
|
Sets the primary (display) alias for an artist, album, or track.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Form parameters:** `alias` (string, required) + exactly one of `artist_id`, `album_id`, or `track_id`.
|
||||||
|
|
||||||
|
**Response:** `204 No Content`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /artists/primary`
|
||||||
|
Sets whether an artist is the primary artist for an album or track.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Form parameters:**
|
||||||
|
|
||||||
|
| Parameter | Required | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `artist_id` | Yes | — |
|
||||||
|
| `is_primary` | Yes | `"true"` or `"false"` |
|
||||||
|
| `album_id` or `track_id` | Yes | Exactly one |
|
||||||
|
|
||||||
|
**Response:** `204 No Content`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /user/me`
|
||||||
|
Returns the currently authenticated user.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Response:** User object.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `PATCH /user`
|
||||||
|
Updates the current user's credentials.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Form parameters:** At least one of `username` or `password`.
|
||||||
|
|
||||||
|
**Response:** `204 No Content`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /user/apikeys`
|
||||||
|
Returns all API keys for the current user.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Response:** Array of API key objects.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /user/apikeys`
|
||||||
|
Generates a new API key.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Form parameters:** `label` (string, required).
|
||||||
|
|
||||||
|
**Response:** `201 Created` with the new API key object (the key value is only returned once).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `PATCH /user/apikeys`
|
||||||
|
Updates the label of an API key.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Form parameters:** `id` (integer, required), `label` (string, required).
|
||||||
|
|
||||||
|
**Response:** `200 OK`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `DELETE /user/apikeys`
|
||||||
|
Deletes an API key.
|
||||||
|
|
||||||
|
**Authentication:** Session or API key (always enforced)
|
||||||
|
|
||||||
|
**Query parameters:** `id` (integer, required).
|
||||||
|
|
||||||
|
**Response:** `204 No Content`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ListenBrainz-Compatible Scrobbling API
|
||||||
|
|
||||||
|
Koito implements a subset of the ListenBrainz API, allowing any ListenBrainz-compatible scrobbler to submit listens.
|
||||||
|
|
||||||
|
Base path: `/apis/listenbrainz/1`
|
||||||
|
|
||||||
|
### `POST /submit-listens`
|
||||||
|
Submits one or more listens.
|
||||||
|
|
||||||
|
**Authentication:** API key (`Authorization: Token <key>`)
|
||||||
|
|
||||||
|
**Request body:** Standard ListenBrainz [`submit-listens` payload](https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#post--1-submit-listens).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /validate-token`
|
||||||
|
Validates an API key.
|
||||||
|
|
||||||
|
**Authentication:** API key (`Authorization: Token <key>`)
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "Token valid.",
|
||||||
|
"valid": true,
|
||||||
|
"user_name": "youruser"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Images
|
||||||
|
|
||||||
|
### `GET /images/{size}/{filename}`
|
||||||
|
Serves cached cover art. `size` is a dimension hint (e.g. `300`). `filename` is the UUID from a track/album/artist `image` field plus an extension.
|
||||||
|
|
||||||
|
No authentication required.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error responses
|
||||||
|
|
||||||
|
All endpoints return errors in this shape:
|
||||||
|
```json
|
||||||
|
{ "error": "human-readable error message" }
|
||||||
|
```
|
||||||
|
|
||||||
|
Rate limiting on `POST /login` returns `429 Too Many Requests` (hardcoded at 10 requests/minute) with the same shape.
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue