Parent directory

API.md

4995 bytes

JSON API

The JSON API is available under /api. The service does not enable CORS.

Authentication

Every /api request must include exactly one Authorization header. Its value must exactly equal the configured static token. Do not use a Bearer prefix.

Missing or repeated headers return 401:

{"error":"missing Authorization header"}

An incorrect token returns 401:

{"error":"invalid auth token"}

Invalid request input returns 400 with an error string. Handler failures return 500 with an error string.

Video object

Video listings contain these fields. Times are JSON timestamps in RFC 3339 format. Nullable fields are null when unset.

{
  "video_id": "YouTube video ID",
  "channel_name": "Channel name",
  "title": "Video title",
  "published_timestamp": "2026-01-02T15:04:05Z",
  "watch_timestamp": null,
  "duration": 1200,
  "progress": 300,
  "short": false,
  "is_live": false,
  "is_discarded": false,
  "downloaded_at": null,
  "download_status": null,
  "download_error": null
}

duration and progress are seconds. channel_id and the server file path are not exposed.

Channel object

{
  "channel_id": "YouTube channel ID",
  "name": "Channel name",
  "subscribed": true,
  "unwatched_count": 3,
  "image_url": "https://...",
  "enable_shorts": true
}

MVP routes

POST /api/fetch

Fetches new videos from every subscribed channel. It has no request body.

Success response:

{"msg":"videos fetched successfully"}

POST /api/videos

Adds one custom video without subscribing to its channel. The request body accepts a YouTube video ID or URL:

{"video_id":"https://www.youtube.com/watch?v=..."}

A YouTube URL with ?t=N saves N seconds of watch progress. The created channel remains unsubscribed, so later fetches do not add its other videos.

Success response (201):

{"msg":"video added"}

GET /api/videos/new

Returns all unwatched, non-discarded videos:

{"videos":[/* Video objects */]}

Videos are ordered by published_timestamp newest first. Videos with the same timestamp are ordered by video_id descending.

GET /api/videos/watched?page=N

Returns watched, non-discarded videos in pages of 100:

{"videos":[/* Video objects */]}

page is a one-based positive integer and defaults to 1. Invalid, zero, and negative values use page 1. A response containing fewer than 100 videos is the final page.

Videos are ordered by watch_timestamp newest first. Videos with the same timestamp are ordered by video_id descending.

POST /api/videos/:video_id/watch

Marks the video as watched at the current server time. It has no request body.

Success response:

{"msg":"marked video as watched"}

POST /api/videos/:video_id/unwatch

Clears the video watch timestamp. It has no request body.

Success response:

{"msg":"cleared video from watch history"}

POST /api/videos/:video_id/progress

Updates the saved watch progress. The request body must contain a progress string in Go duration, mm:ss, or hh:mm:ss format:

{"progress":"2:30"}

Success response:

{"video":{/* Video object */}}

Missing or invalid progress returns 400.

GET /api/videos/:video_id

Returns one video, including its current download status:

{"video":{/* Video object */}}

A missing video returns 404.

POST /api/videos/:video_id/download

Starts an asynchronous server download. The request body must select one of the supported maximum video heights: 480, 720, 1080, 1440, or 2160.

{"format":720}

Success response (202):

{"msg":"download started"}

Poll GET /api/videos/:video_id to read download_status and download_error. Invalid or missing formats return 400.

GET /api/videos/:video_id/file

Returns the completed server download as an attachment. The endpoint requires the same Authorization header as every other API route. It returns 404 if the video has no completed file.

Channel routes

GET /api/channels

Returns subscribed channels ordered by name:

{"channels":[/* Channel objects */]}

POST /api/channels/subscribe

Subscribes to a YouTube channel. The request body accepts a raw channel ID, a channel handle (with or without @), or a YouTube channel URL:

{"channel_id":"https://m.youtube.com/@username"}

Success response:

{/* Channel object */}

POST /api/channels/:channel_id/unsubscribe

Stops future fetches for the channel. Existing videos remain available.

Success response:

{"msg":"unsubscribed from channel successfully"}

POST /api/channels/:channel_id/shorts?enable=true

Sets whether the channel includes Shorts. enable must be a boolean query parameter.

Success response:

{"msg":"shorts setting updated","enable_shorts":true}