Files
unshackle/docs/api/endpoints.md
T
imSp4rky 7fdd514e18 feat(api): add job retry/priority/clear, history, config and maintenance endpoints
Persistent job history (capped by serve.history_limit, stored under cache), profiles/config reads, maintenance + env-check endpoints, and 409 conflict code.
2026-07-01 22:42:48 -06:00

18 KiB
Raw Blame History

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 }
  ]
}