Skip to content

Endpoints

GET /api/health

Health check with version and update information. Always reachable without auth.

curl http://localhost:8786/api/health
{
  "status": "ok",
  "version": "4.0.0",
  "update_check": {
    "update_available": false,
    "current_version": "4.0.0",
    "latest_version": null
  }
}

GET /api/services

List all available streaming services (filtered by the effective allowlist for the caller).

curl -H "X-Secret-Key: $KEY" http://localhost:8786/api/services

Returns {"services": [...]}. Each entry has tag, aliases, geofence, title_regex, url (from cli.short_help), help (full docstring), and cli_params describing the service-level Click parameters.


POST /api/search

Search for titles from a streaming service.

Required parameters: | Parameter | Type | Description | | --- | --- | --- | | service | string | Service tag | | query | string | Search query |

Optional parameters: | Parameter | Type | Default | Description | | --- | --- | --- | --- | | profile | string | null | Profile for credentials/cookies | | proxy | string | null | Proxy URI or country code | | no_proxy | boolean | false | Disable all proxy use |

curl -X POST http://localhost:8786/api/search \
  -H "X-Secret-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"service": "EXAMPLE1", "query": "example show"}'
{
  "results": [
    {
      "id": "abc123def456",
      "title": "Example Show",
      "description": null,
      "label": "TV Show",
      "url": "https://example.com/show/abc123def456"
    }
  ],
  "count": 1
}

POST /api/list-titles

Get available titles (seasons/episodes/movies) for a service and title ID. Disabled in --remote-only mode.

Required parameters: | Parameter | Type | Description | | --- | --- | --- | | service | string | Service tag | | title_id | string | Title ID or URL |

curl -X POST http://localhost:8786/api/list-titles \
  -H "X-Secret-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"service": "EXAMPLE1", "title_id": "abc123def456"}'

POST /api/list-tracks

Get video, audio, and subtitle tracks for a title. Disabled in --remote-only mode.

Required parameters: | Parameter | Type | Description | | --- | --- | --- | | service | string | Service tag | | title_id | string | Title ID or URL |

Optional parameters: | Parameter | Type | Default | Description | | --- | --- | --- | --- | | wanted | array | all | Episode filter (e.g., ["S01E01"]) | | profile | string | null | Profile for credentials/cookies | | proxy | string | null | Proxy URI or country code | | no_proxy | boolean | false | Disable all proxy use |

Returns video, audio, and subtitle tracks with codec, bitrate, resolution, language, and DRM information.


POST /api/download

Start a download job. Returns immediately with a job ID (HTTP 202). Disabled in --remote-only mode.

Required parameters: | Parameter | Type | Description | | --- | --- | --- | | service | string | Service tag | | title_id | string | Title ID or URL |

Quality and codec parameters: | Parameter | Type | Default | Description | | --- | --- | --- | --- | | quality | array[int] | best | Resolution(s) (e.g., [1080, 2160]) | | vcodec | string or array | any | Video codec(s): H264, H265/HEVC, VP9, AV1, VC1, VP8 | | acodec | string or array | any | Audio codec(s): AAC, AC3, EC3, AC4, OPUS, FLAC, ALAC, DTS, OGG | | vbitrate | int | highest | Video bitrate in kbps | | abitrate | int | highest | Audio bitrate in kbps | | range | array[string] | ["SDR"] | Color range(s): SDR, HDR10, HDR10+, HLG, DV, HYBRID | | channels | float | any | Audio channels (e.g., 5.1, 7.1) | | no_atmos | boolean | false | Exclude Dolby Atmos tracks | | split_audio | boolean | null | Create separate output per audio codec | | sub_format | string | null | Output subtitle format: SRT, VTT, ASS, SSA, TTML |

Episode selection: | Parameter | Type | Default | Description | | --- | --- | --- | --- | | wanted | array[string] | all | Episodes (e.g., ["S01E01", "S01E02-S01E05"]) | | latest_episode | boolean | false | Download only the most recent episode |

Language parameters: | Parameter | Type | Default | Description | | --- | --- | --- | --- | | lang | array[string] | ["orig"] | Language for video and audio (orig = original) | | v_lang | array[string] | [] | Language override for video tracks only | | a_lang | array[string] | [] | Language override for audio tracks only | | s_lang | array[string] | ["all"] | Language for subtitles | | require_subs | array[string] | [] | Required subtitle languages (skip if missing) | | forced_subs | boolean | false | Include forced subtitle tracks | | exact_lang | boolean | false | Exact language matching (no variants) |

Track selection: | Parameter | Type | Default | Description | | --- | --- | --- | --- | | video_only | boolean | false | Only download video tracks | | audio_only | boolean | false | Only download audio tracks | | subs_only | boolean | false | Only download subtitle tracks | | chapters_only | boolean | false | Only download chapters | | no_video | boolean | false | Skip video tracks | | no_audio | boolean | false | Skip audio tracks | | no_subs | boolean | false | Skip subtitle tracks | | no_chapters | boolean | false | Skip chapters | | audio_description | boolean | false | Include audio description tracks |

Output and tagging: | Parameter | Type | Default | Description | | --- | --- | --- | --- | | tag | string | null | Override group tag | | repack | boolean | false | Add REPACK tag to filename | | tmdb_id | int | null | Use specific TMDB ID for tagging | | imdb_id | string | null | Use specific IMDB ID (e.g., tt1375666) | | animeapi_id | string | null | Anime database ID via AnimeAPI (e.g., mal:12345) | | enrich | boolean | false | Override show title and year from external source | | no_folder | boolean | false | Disable folder creation for TV shows | | no_source | boolean | false | Remove source tag from filename | | no_mux | boolean | false | Do not mux tracks into container | | output_dir | string | null | Override output directory |

Download behavior: | Parameter | Type | Default | Description | | --- | --- | --- | --- | | profile | string | null | Profile for credentials/cookies | | proxy | string | null | Proxy URI or country code | | no_proxy | boolean | false | Disable all proxy use | | no_proxy_download | boolean | false | Bypass proxy for segment downloads only. Manifest, license, and auth still use proxy | | workers | int | null | Max threads per track download | | downloads | int | 1 | Concurrent track downloads | | slow | boolean or string | null | Add randomized delay between titles. true = 60-120s, or "MIN-MAX" string (e.g., "20-40"). Min must be >= 20 | | best_available | boolean | false | Continue if requested quality unavailable | | worst | boolean | false | Select the lowest bitrate track within the specified quality. Requires quality | | skip_dl | boolean | false | Skip download, only get decryption keys | | export | boolean | false | Export manifest, track URLs, keys, and subtitles to JSON in the exports directory | | cdm_only | boolean | null | Only use CDM (true) or only vaults (false) | | no_cache | boolean | false | Bypass title cache | | reset_cache | boolean | false | Clear title cache before fetching |

Example:

curl -X POST http://localhost:8786/api/download \
  -H "X-Secret-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "service": "EXAMPLE1",
    "title_id": "abc123def456",
    "wanted": ["S01E01"],
    "quality": [1080, 2160],
    "vcodec": ["H265"],
    "acodec": ["AAC", "EC3"],
    "range": ["HDR10", "SDR"],
    "split_audio": true,
    "lang": ["en"]
  }'
{
  "job_id": "504db959-80b0-446c-a764-7924b761d613",
  "status": "queued",
  "created_time": "2026-02-27T18:00:00.000000"
}

GET /api/download/jobs

List all download jobs with optional filtering and sorting. Disabled in --remote-only mode.

Query parameters: | Parameter | Type | Default | Description | | --- | --- | --- | --- | | status | string | all | Filter by status: queued, downloading, completed, failed, cancelled | | service | string | all | Filter by service tag | | sort_by | string | created_time | Sort field: created_time, started_time, completed_time, progress, status, service | | sort_order | string | desc | Sort order: asc, desc | | full | string | false | true to include full job details per job (parameters, timestamps, output_files, error fields) |

curl -H "X-Secret-Key: $KEY" "http://localhost:8786/api/download/jobs?status=completed"

GET /api/download/jobs/{job_id}

Get detailed information about a specific download job including progress, parameters, and error details.

{
  "job_id": "504db959-80b0-446c-a764-7924b761d613",
  "status": "downloading",
  "created_time": "2026-02-27T18:00:00.000000",
  "service": "EXAMPLE1",
  "title_id": "abc123def456",
  "title": "Example Show",
  "progress": 74.9,
  "phase": "downloading video 1080p SDR",
  "current_title": "S01E04",
  "completed_tracks": 4,
  "total_tracks": 13,
  "active_tracks": ["video 1080p SDR"],
  "track_progress": [
    { "label": "video 1080p SDR", "progress": 74.9, "speed": "DASH 265.5 MB/s" }
  ],
  "segments_done": 75.0,
  "segments_total": 100.0,
  "speed": "DASH 265.5 MB/s",
  "parameters": { },
  "started_time": "2026-02-27T18:00:01.000000",
  "completed_time": null,
  "output_files": [
    "/downloads/Example.Show.S01E01.mkv",
    "/downloads/Example.Show.S01E02.mkv",
    "/downloads/Example.Show.S01E03.mkv"
  ],
  "error_message": null,
  "error_details": null
}

Progress fields:

Field Type Description
progress number Overall progress for the title currently downloading, 0–100. Resets per episode on multi-episode jobs
phase string Human-readable current step, e.g. downloading video 1080p SDR, muxing
title string Human-readable show/movie name, available once the download starts
current_title string SxxEyy code of the episode downloading now (episodic jobs only)
completed_tracks / total_tracks int Track counts for the current title
active_tracks array[string] Labels of tracks downloading now
track_progress array Per-track {label, progress, speed} for each concurrently active track
segments_done / segments_total number Segment counts of the track downloading now
speed string Transfer speed of the track downloading now
output_files array[string] Final file paths, appended as each title finishes muxing

The list endpoint (GET /api/download/jobs) returns the same progress fields per job but omits parameters, timestamps, output_files, and error details.


DELETE /api/download/jobs/{job_id}

Cancel a queued or running download job, or remove a finished one.

  • queued / downloading job: cancelled, returns 200 with {"status": "success", "message": "Job cancelled"}.
  • completed / failed / cancelled job: removed from the manager entirely, returns 204 (no body).
  • Unknown job: 404.

POST /api/download/jobs/clear-finished

Remove all completed, failed, and cancelled jobs from the manager. Disabled in --remote-only mode.

{ "removed": 3 }

POST /api/download/jobs/{job_id}/retry

Enqueue a new job reusing a finished job's service, title, and original (unredacted) parameters. Only allowed on completed, failed, or cancelled jobs — returns 409 otherwise, 404 if unknown. The original job is left in place.

Creation-time authorization is re-applied at retry time: the job's service must pass the caller's service allowlist (otherwise 400 invalid service), and jobs whose original parameters carried a cdm override or per-job credentials return 403 if serve.cdm_overrides / serve.allow_job_credentials no longer permit them.

Returns 202 with the same shape as POST /api/download:

{
  "job_id": "b7e9c1f2-...",
  "status": "queued",
  "created_time": "2026-02-27T18:00:00.000000"
}

POST /api/download/jobs/{job_id}/priority

Move a queued job to the front of the download queue so a worker picks it up next. Returns 409 if the job is not queued, 404 if unknown.

{ "job_id": "504db959-...", "position": "front" }

GET /api/profiles

List named credential profiles per service, usable as the profile parameter on search/list/download. Only services whose credentials are configured as a profile-name: credential mapping appear (a default key is included if present); a service with a single plain (unnamed) credential is omitted entirely. Filtered by the caller's service allowlist. Disabled in --remote-only mode.

curl -H "X-Secret-Key: $KEY" http://localhost:8786/api/profiles
{ "profiles": { "EXAMPLE1": ["default", "uk", "us"], "EXAMPLE2": ["alice"] } }

GET /api/config

Read-only, redacted view of the effective server configuration for a UI settings page. Secrets (api_secret, users, credentials, tokens) are never included; secret-looking keys inside dl are masked with ***. Disabled in --remote-only mode.

curl -H "X-Secret-Key: $KEY" http://localhost:8786/api/config
{
  "config": {
    "dl": { "lang": "orig", "sub_format": "srt", "downloads": 12 },
    "serve": {
      "max_concurrent_downloads": 2,
      "job_retention_hours": 24,
      "history_limit": 100,
      "services": null,
      "remote_only": false,
      "cdm_overrides": null,
      "allow_job_credentials": false
    },
    "directories": {
      "downloads": "/srv/downloads",
      "temp": "/srv/temp",
      "cache": "/srv/cache"
    },
    "services": ["EXAMPLE1", "EXAMPLE2"]
  }
}
Field Description
dl Default dl parameters from the config file (redacted)
serve.services Global service allowlist, or null when all services are allowed
serve.cdm_overrides Permitted per-request CDM device names (list), true, or null
services Available service tags, filtered by the caller's allowlist

GET /api/history

Persistent download history. Every job that reaches a terminal state (completed, failed, cancelled) is appended as one JSON line to api_history.jsonl in the cache directory, surviving restarts and job cleanup. Only the newest serve.history_limit entries are retained (default 100, 0 for unlimited; see Advanced Config). Newest first. Corrupt lines are skipped; a missing file yields an empty list. Entries are filtered by the caller's service allowlist. Disabled in --remote-only mode.

Query parameters: | Parameter | Type | Default | Description | | --- | --- | --- | --- | | limit | int | 100 | Maximum entries returned (must be >= 1) | | service | string | all | Filter by service tag (case-insensitive) |

curl -H "X-Secret-Key: $KEY" "http://localhost:8786/api/history?limit=50&service=EXAMPLE1"
{
  "history": [
    {
      "job_id": "504db959-80b0-446c-a764-7924b761d613",
      "service": "EXAMPLE1",
      "title_id": "abc123def456",
      "title": "Example Show",
      "status": "completed",
      "created_time": "2026-02-27T18:00:00.000000",
      "completed_time": "2026-02-27T18:12:34.000000",
      "output_files": ["/downloads/Example.Show.S01E01.mkv"],
      "parameters": { "wanted": ["S01E01"], "quality": [1080], "skip_dl": false },
      "error_message": null
    }
  ],
  "count": 1
}

error_message is redacted with the same secret-scrubbing applied to job responses. parameters is the redacted download selection the job ran with (quality/vcodec/range/wanted/skip_dl, etc.). completed_time is null for jobs cancelled while still queued.


DELETE /api/history/{job_id}

Remove a single history entry by job_id (the file is rewritten without it). Returns 204 on success, 404 if no matching entry exists or it falls outside the caller's service allowlist. Corrupt lines in the history file are preserved.

curl -X DELETE -H "X-Secret-Key: $KEY" "http://localhost:8786/api/history/504db959-80b0-446c-a764-7924b761d613"

POST /api/maintenance/clear-cache

Delete the contents of the cache directory (the directory is recreated empty). freed_bytes is best effort. This also removes api_history.jsonl, so persistent download history is cleared. Returns 409 with the active job ids if any job is currently downloading, since in-flight jobs may be reading the cache. Disabled in --remote-only mode.

curl -X POST -H "X-Secret-Key: $KEY" http://localhost:8786/api/maintenance/clear-cache
{ "cleared": true, "freed_bytes": 1234567 }

POST /api/maintenance/clear-temp

Same as clear-cache, for the temp directory. Also refuses with 409 while any job is downloading — active workers write segment files under temp.

{ "cleared": true, "freed_bytes": 0 }

POST /api/maintenance/refresh-services

Force-sync (git pull, hard reset to upstream) every service repo configured in directories.services. refreshed is true when all repos synced (or none are configured). Disabled in --remote-only mode.

curl -X POST -H "X-Secret-Key: $KEY" http://localhost:8786/api/maintenance/refresh-services
{
  "refreshed": true,
  "repos": [
    { "spec": "https://github.com/example/services", "updated": true, "changes": ["~ EXAMPLE1 (modified)"] }
  ]
}

An empty changes array means the repo was already up to date. A repo that failed to sync has updated: false (and flips refreshed to false).


GET /api/env/check

Install status of the binaries unshackle env check inspects (FFmpeg, MKVToolNix, shaka-packager, etc.). version is a best-effort probe and may be null even when installed. Disabled in --remote-only mode.

curl -H "X-Secret-Key: $KEY" http://localhost:8786/api/env/check
{
  "checks": [
    { "name": "FFmpeg", "installed": true, "version": "7.1.0", "required": true },
    { "name": "mp4decrypt", "installed": false, "version": null, "required": false }
  ]
}