Files
SaveAny-Bot/en/usage/api/index.html
2026-03-11 11:38:19 +00:00

188 lines
39 KiB
HTML

<!doctype html><html lang=en dir=ltr><head><meta charset=UTF-8><meta name=viewport content="width=device-width,initial-scale=1"><meta name=description content='
HTTP API
#
SaveAny-Bot provides an HTTP API that allows you to programmatically create download/transfer tasks, query task status, cancel tasks, and more — without going through Telegram.
Enabling the API
#
Add or modify the following section in config.toml:
[api]
enable = true
host = "0.0.0.0" # Bind address, default 0.0.0.0
port = 8080 # Listen port, default 8080
token = "your-token" # Auth token — strongly recommended
You can also override these settings with environment variables (prefix SAVEANY_):'><meta name=theme-color media="(prefers-color-scheme: light)" content="#ffffff"><meta name=theme-color media="(prefers-color-scheme: dark)" content="#343a40"><meta name=color-scheme content="light dark"><meta property="og:url" content="https://sabot.unv.app/en/usage/api/"><meta property="og:site_name" content="Save Any Bot"><meta property="og:title" content="HTTP API"><meta property="og:description" content='HTTP API # SaveAny-Bot provides an HTTP API that allows you to programmatically create download/transfer tasks, query task status, cancel tasks, and more — without going through Telegram.
Enabling the API # Add or modify the following section in config.toml:
[api] enable = true host = "0.0.0.0" # Bind address, default 0.0.0.0 port = 8080 # Listen port, default 8080 token = "your-token" # Auth token — strongly recommended You can also override these settings with environment variables (prefix SAVEANY_):'><meta property="og:locale" content="en"><meta property="og:type" content="article"><meta property="article:section" content="usage"><meta property="article:modified_time" content="2026-03-11T19:37:25+08:00"><title>HTTP API | Save Any Bot</title><link rel=icon href=/favicon.png><link rel=manifest href=/manifest.json><link rel=canonical href=https://sabot.unv.app/en/usage/api/><link rel=alternate hreflang=zh href=https://sabot.unv.app/usage/api/ title="HTTP API"><link rel=stylesheet href=/book.min.a22f4c7d8c2bdc5e3d6e34ba11cb59ab50ea5772594e71305bfd5a595dc78b7e.css integrity="sha256-oi9MfYwr3F49bjS6EctZq1DqV3JZTnEwW/1aWV3Hi34=" crossorigin=anonymous></head><body dir=ltr><input type=checkbox class="hidden toggle" id=menu-control>
<input type=checkbox class="hidden toggle" id=toc-control><main class="container flex"><aside class=book-menu><div class=book-menu-content><nav><h2 class=book-brand><a class="flex align-center" href=/en/><img src=/logo.png alt=Logo class=book-icon><span>Save Any Bot</span></a></h2><ul class=book-languages><li><input type=checkbox id=languages class=toggle>
<label for=languages class=flex><a role=button class="flex flex-auto"><img src=/svg/translate.svg class=book-icon alt=Languages>
English</a></label><ul><li><a href=/usage/api/>简体中文</a></li></ul></li></ul><ul><li><a href=https://github.com/krau/SaveAny-Bot target=_blank rel=noopener>🔗 GitHub</a></li></ul><ul><li><span>Deployment Guide</span><ul><li><a href=/en/deployment/configuration/>Configuration Guide</a><ul><li><a href=/en/deployment/configuration/storages/>Storage Configuration</a></li></ul></li><li><a href=/en/deployment/installation/>Installation and Updates</a></li></ul></li><li><a href=/en/usage/>Usage</a><ul><li><a href=/en/usage/silent/>Silent Mode</a></li><li><a href=/en/usage/rules/>Storage Rules</a></li><li><a href=/en/usage/watch/>Watch Chats</a></li><li><a href=/en/usage/directlinks/>Direct Download Links</a></li><li><a href=/en/usage/aria2/>Aria2 Download</a></li><li><a href=/en/usage/ytdlp/>yt-dlp Video Download</a></li><li><a href=/en/usage/transfer/>Storage Transfer</a></li><li><a href=/en/usage/parsers/>Save Files Outside Telegram</a></li><li><a href=/en/usage/api/ class=active>HTTP API</a></li></ul></li><li><a href=/en/help/>Frequently Asked Questions</a><ul></ul></li><li><a href=/en/contribute/>Contributing</a><ul></ul></li></ul></nav><script>(function(){var e=document.querySelector("aside .book-menu-content");addEventListener("beforeunload",function(){localStorage.setItem("menu.scrollTop",e.scrollTop)}),e.scrollTop=localStorage.getItem("menu.scrollTop")})()</script></div></aside><div class=book-page><header class=book-header><div class="flex align-center justify-between"><label for=menu-control><img src=/svg/menu.svg class=book-icon alt=Menu></label><h3>HTTP API</h3><label for=toc-control><img src=/svg/toc.svg class=book-icon alt="Table of Contents"></label></div><aside class="hidden clearfix"><nav id=TableOfContents><ul><li><a href=#enabling-the-api>Enabling the API</a></li><li><a href=#authentication>Authentication</a></li><li><a href=#error-response-format>Error Response Format</a></li><li><a href=#endpoints>Endpoints</a><ul><li><a href=#get-health--health-check>GET /health — Health Check</a></li><li><a href=#get-apiv1storages--list-storages>GET /api/v1/storages — List Storages</a></li><li><a href=#get-apiv1task-types--list-supported-task-types>GET /api/v1/task-types — List Supported Task Types</a></li><li><a href=#post-apiv1tasks--create-task>POST /api/v1/tasks — Create Task</a></li><li><a href=#get-apiv1tasks--list-all-tasks>GET /api/v1/tasks — List All Tasks</a></li><li><a href=#get-apiv1taskstask_id--get-task>GET /api/v1/tasks/{task_id} — Get Task</a></li><li><a href=#delete-apiv1taskstask_id--cancel-task>DELETE /api/v1/tasks/{task_id} — Cancel Task</a></li></ul></li><li><a href=#task-statuses>Task Statuses</a></li><li><a href=#webhook-callbacks>Webhook Callbacks</a></li></ul></nav></aside></header><article class="markdown book-article"><h1 id=http-api>HTTP API
<a class=anchor href=#http-api>#</a></h1><p>SaveAny-Bot provides an HTTP API that allows you to programmatically create download/transfer tasks, query task status, cancel tasks, and more — without going through Telegram.</p><h2 id=enabling-the-api>Enabling the API
<a class=anchor href=#enabling-the-api>#</a></h2><p>Add or modify the following section in <code>config.toml</code>:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-toml data-lang=toml><span style=display:flex><span>[<span style=color:#a6e22e>api</span>]
</span></span><span style=display:flex><span><span style=color:#a6e22e>enable</span> = <span style=color:#66d9ef>true</span>
</span></span><span style=display:flex><span><span style=color:#a6e22e>host</span> = <span style=color:#e6db74>&#34;0.0.0.0&#34;</span> <span style=color:#75715e># Bind address, default 0.0.0.0</span>
</span></span><span style=display:flex><span><span style=color:#a6e22e>port</span> = <span style=color:#ae81ff>8080</span> <span style=color:#75715e># Listen port, default 8080</span>
</span></span><span style=display:flex><span><span style=color:#a6e22e>token</span> = <span style=color:#e6db74>&#34;your-token&#34;</span> <span style=color:#75715e># Auth token — strongly recommended</span>
</span></span></code></pre></div><p>You can also override these settings with environment variables (prefix <code>SAVEANY_</code>):</p><table><thead><tr><th>Environment Variable</th><th>Config Key</th></tr></thead><tbody><tr><td><code>SAVEANY_API_ENABLE</code></td><td><code>api.enable</code></td></tr><tr><td><code>SAVEANY_API_HOST</code></td><td><code>api.host</code></td></tr><tr><td><code>SAVEANY_API_PORT</code></td><td><code>api.port</code></td></tr><tr><td><code>SAVEANY_API_TOKEN</code></td><td><code>api.token</code></td></tr></tbody></table><blockquote class="book-hint warning">If `token` is empty, the API server will be accessible **without any authentication**, which is a security risk.</blockquote><h2 id=authentication>Authentication
<a class=anchor href=#authentication>#</a></h2><p>When <code>token</code> is configured, all API requests must include a Bearer token in the HTTP header:</p><pre tabindex=0><code>Authorization: Bearer &lt;your-token&gt;
</code></pre><p>On authentication failure, the server returns <code>401</code>:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{ <span style=color:#f92672>&#34;error&#34;</span>: <span style=color:#e6db74>&#34;unauthorized&#34;</span>, <span style=color:#f92672>&#34;message&#34;</span>: <span style=color:#e6db74>&#34;invalid token&#34;</span> }
</span></span></code></pre></div><h2 id=error-response-format>Error Response Format
<a class=anchor href=#error-response-format>#</a></h2><p>All errors use a consistent JSON format:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;error&#34;</span>: <span style=color:#e6db74>&#34;error_code&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;message&#34;</span>: <span style=color:#e6db74>&#34;human readable description&#34;</span>
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>Common error codes:</p><table><thead><tr><th>Error Code</th><th>HTTP Status</th><th>Meaning</th></tr></thead><tbody><tr><td><code>unauthorized</code></td><td>401</td><td>Authentication failed</td></tr><tr><td><code>method_not_allowed</code></td><td>405</td><td>Wrong HTTP method</td></tr><tr><td><code>invalid_request</code></td><td>400</td><td>Malformed request body or parameters</td></tr><tr><td><code>task_creation_failed</code></td><td>400</td><td>Failed to create task</td></tr><tr><td><code>task_not_found</code></td><td>404</td><td>Task ID does not exist</td></tr><tr><td><code>cancel_failed</code></td><td>500</td><td>Failed to cancel task</td></tr><tr><td><code>internal_error</code></td><td>500</td><td>Internal server error</td></tr></tbody></table><hr><h2 id=endpoints>Endpoints
<a class=anchor href=#endpoints>#</a></h2><h3 id=get-health--health-check>GET /health — Health Check
<a class=anchor href=#get-health--health-check>#</a></h3><p>No authentication required.</p><p><strong>Response <code>200 OK</code>:</strong></p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{ <span style=color:#f92672>&#34;status&#34;</span>: <span style=color:#e6db74>&#34;ok&#34;</span> }
</span></span></code></pre></div><hr><h3 id=get-apiv1storages--list-storages>GET /api/v1/storages — List Storages
<a class=anchor href=#get-apiv1storages--list-storages>#</a></h3><p>Returns all currently loaded storage backends.</p><p><strong>Response <code>200 OK</code>:</strong></p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;storages&#34;</span>: [
</span></span><span style=display:flex><span> { <span style=color:#f92672>&#34;name&#34;</span>: <span style=color:#e6db74>&#34;local&#34;</span>, <span style=color:#f92672>&#34;type&#34;</span>: <span style=color:#e6db74>&#34;local&#34;</span> },
</span></span><span style=display:flex><span> { <span style=color:#f92672>&#34;name&#34;</span>: <span style=color:#e6db74>&#34;MyMinio&#34;</span>, <span style=color:#f92672>&#34;type&#34;</span>: <span style=color:#e6db74>&#34;s3&#34;</span> }
</span></span><span style=display:flex><span> ]
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><hr><h3 id=get-apiv1task-types--list-supported-task-types>GET /api/v1/task-types — List Supported Task Types
<a class=anchor href=#get-apiv1task-types--list-supported-task-types>#</a></h3><p><strong>Response <code>200 OK</code>:</strong></p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;types&#34;</span>: [
</span></span><span style=display:flex><span> <span style=color:#e6db74>&#34;directlinks&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#e6db74>&#34;ytdlp&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#e6db74>&#34;aria2&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#e6db74>&#34;parseditem&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#e6db74>&#34;tgfiles&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#e6db74>&#34;tphpics&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#e6db74>&#34;transfer&#34;</span>
</span></span><span style=display:flex><span> ]
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><hr><h3 id=post-apiv1tasks--create-task>POST /api/v1/tasks — Create Task
<a class=anchor href=#post-apiv1tasks--create-task>#</a></h3><p><strong>Request headers:</strong></p><pre tabindex=0><code>Content-Type: application/json
Authorization: Bearer &lt;token&gt;
</code></pre><p><strong>Request body:</strong></p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;type&#34;</span>: <span style=color:#e6db74>&#34;&lt;task_type&gt;&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;storage&#34;</span>: <span style=color:#e6db74>&#34;&lt;storage_name&gt;&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;path&#34;</span>: <span style=color:#e6db74>&#34;&lt;subpath&gt;&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;webhook&#34;</span>: <span style=color:#e6db74>&#34;&lt;callback_url&gt;&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;params&#34;</span>: { }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><table><thead><tr><th>Field</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td><code>type</code></td><td>string</td><td>Yes</td><td>Task type — see below</td></tr><tr><td><code>storage</code></td><td>string</td><td>Yes</td><td>Target storage name, must match a name in your config</td></tr><tr><td><code>path</code></td><td>string</td><td>No</td><td>Subdirectory path within the storage</td></tr><tr><td><code>webhook</code></td><td>string</td><td>No</td><td>Callback URL invoked when the task reaches a terminal state</td></tr><tr><td><code>params</code></td><td>object</td><td>Yes</td><td>Type-specific parameters — see below</td></tr></tbody></table><p><strong>Response <code>201 Created</code>:</strong></p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;task_id&#34;</span>: <span style=color:#e6db74>&#34;abc123xyz&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;type&#34;</span>: <span style=color:#e6db74>&#34;directlinks&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;status&#34;</span>: <span style=color:#e6db74>&#34;queued&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;created_at&#34;</span>: <span style=color:#e6db74>&#34;2026-03-11T10:00:00Z&#34;</span>
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><h4 id=task-types-and-params>Task Types and params
<a class=anchor href=#task-types-and-params>#</a></h4><h5 id=directlinks--direct-url-download>directlinks — Direct URL Download
<a class=anchor href=#directlinks--direct-url-download>#</a></h5><p>Download one or more files from direct HTTP/HTTPS URLs.</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;type&#34;</span>: <span style=color:#e6db74>&#34;directlinks&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;storage&#34;</span>: <span style=color:#e6db74>&#34;local&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;path&#34;</span>: <span style=color:#e6db74>&#34;downloads&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;params&#34;</span>: {
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;urls&#34;</span>: [
</span></span><span style=display:flex><span> <span style=color:#e6db74>&#34;https://example.com/file.zip&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#e6db74>&#34;https://example.com/other.zip&#34;</span>
</span></span><span style=display:flex><span> ]
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><table><thead><tr><th>params field</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td><code>urls</code></td><td>[]string</td><td>Yes</td><td>List of download URLs, at least 1</td></tr></tbody></table><h5 id=ytdlp--yt-dlp-media-download>ytdlp — yt-dlp Media Download
<a class=anchor href=#ytdlp--yt-dlp-media-download>#</a></h5><blockquote class="book-hint warning">Requires yt-dlp to be installed on the system.</blockquote><p>Download videos or audio via yt-dlp, supporting YouTube, Bilibili, and 1000+ other sites.</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;type&#34;</span>: <span style=color:#e6db74>&#34;ytdlp&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;storage&#34;</span>: <span style=color:#e6db74>&#34;local&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;path&#34;</span>: <span style=color:#e6db74>&#34;videos&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;params&#34;</span>: {
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;urls&#34;</span>: [<span style=color:#e6db74>&#34;https://www.youtube.com/watch?v=xxx&#34;</span>],
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;flags&#34;</span>: [<span style=color:#e6db74>&#34;--extract-audio&#34;</span>, <span style=color:#e6db74>&#34;--audio-format&#34;</span>, <span style=color:#e6db74>&#34;mp3&#34;</span>]
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><table><thead><tr><th>params field</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td><code>urls</code></td><td>[]string</td><td>Yes</td><td>List of media URLs, at least 1</td></tr><tr><td><code>flags</code></td><td>[]string</td><td>No</td><td>Extra yt-dlp command-line flags</td></tr></tbody></table><h5 id=aria2--aria2-download>aria2 — Aria2 Download
<a class=anchor href=#aria2--aria2-download>#</a></h5><blockquote class="book-hint warning">Requires Aria2 to be enabled and configured (RPC) in the config file.</blockquote><p>Download files via the Aria2 download manager, supporting HTTP/HTTPS, FTP, BitTorrent (magnet links, torrent files), and more.</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;type&#34;</span>: <span style=color:#e6db74>&#34;aria2&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;storage&#34;</span>: <span style=color:#e6db74>&#34;local&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;path&#34;</span>: <span style=color:#e6db74>&#34;downloads&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;params&#34;</span>: {
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;urls&#34;</span>: [<span style=color:#e6db74>&#34;magnet:?xt=urn:btih:...&#34;</span>],
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;options&#34;</span>: { <span style=color:#f92672>&#34;split&#34;</span>: <span style=color:#e6db74>&#34;4&#34;</span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><table><thead><tr><th>params field</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td><code>urls</code></td><td>[]string</td><td>Yes</td><td>List of download URIs, at least 1</td></tr><tr><td><code>options</code></td><td>map[string]string</td><td>No</td><td>Aria2 download options</td></tr></tbody></table><h5 id=parseditem--parser-plugin-download>parseditem — Parser Plugin Download
<a class=anchor href=#parseditem--parser-plugin-download>#</a></h5><p>Hand a URL off to a registered JS plugin or built-in parser for processing and downloading.</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;type&#34;</span>: <span style=color:#e6db74>&#34;parseditem&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;storage&#34;</span>: <span style=color:#e6db74>&#34;local&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;path&#34;</span>: <span style=color:#e6db74>&#34;parsed&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;params&#34;</span>: {
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;url&#34;</span>: <span style=color:#e6db74>&#34;https://some-site.com/page&#34;</span>
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><table><thead><tr><th>params field</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td><code>url</code></td><td>string</td><td>Yes</td><td>The URL to parse</td></tr></tbody></table><p>Returns <code>400 task_creation_failed</code> if no parser is able to handle the URL.</p><h5 id=tgfiles--telegram-message-file-download>tgfiles — Telegram Message File Download
<a class=anchor href=#tgfiles--telegram-message-file-download>#</a></h5><p>Download files from Telegram messages via message links. Supported link formats:</p><ul><li><code>https://t.me/username/123</code> — public channel or group</li><li><code>https://t.me/c/123456789/123</code> — private channel by numeric ID</li><li><code>https://t.me/c/123456789/111/456</code> — topic message (thread ID / message ID)</li><li><code>https://t.me/username/111/456</code> — topic under a username-based chat</li></ul><p>If the message is part of a media group (album), all files in the group are downloaded by default. Append <code>?single</code> to the link to force downloading only the single specified message.</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;type&#34;</span>: <span style=color:#e6db74>&#34;tgfiles&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;storage&#34;</span>: <span style=color:#e6db74>&#34;local&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;path&#34;</span>: <span style=color:#e6db74>&#34;telegram&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;params&#34;</span>: {
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;message_links&#34;</span>: [
</span></span><span style=display:flex><span> <span style=color:#e6db74>&#34;https://t.me/username/123&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#e6db74>&#34;https://t.me/c/1234567890/456&#34;</span>
</span></span><span style=display:flex><span> ]
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><table><thead><tr><th>params field</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td><code>message_links</code></td><td>[]string</td><td>Yes</td><td>List of Telegram message links, at least 1</td></tr></tbody></table><h5 id=tphpics--telegraph-article-images>tphpics — Telegraph Article Images
<a class=anchor href=#tphpics--telegraph-article-images>#</a></h5><p>Download all images from a Telegra.ph article.</p><p>Supported URL prefixes: <code>https://telegra.ph/</code>, <code>http://telegra.ph/</code>, <code>https://telegraph.co/</code>, <code>http://telegraph.co/</code></p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;type&#34;</span>: <span style=color:#e6db74>&#34;tphpics&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;storage&#34;</span>: <span style=color:#e6db74>&#34;local&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;path&#34;</span>: <span style=color:#e6db74>&#34;telegraph&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;params&#34;</span>: {
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;telegraph_url&#34;</span>: <span style=color:#e6db74>&#34;https://telegra.ph/Some-Article-01-01&#34;</span>
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><table><thead><tr><th>params field</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td><code>telegraph_url</code></td><td>string</td><td>Yes</td><td>URL of the Telegra.ph article</td></tr></tbody></table><h5 id=transfer--storage-to-storage-transfer>transfer — Storage-to-Storage Transfer
<a class=anchor href=#transfer--storage-to-storage-transfer>#</a></h5><p>Transfer files directly between two storage backends without going through Telegram. The source storage must support both listing and reading.</p><blockquote class="book-hint info">For `transfer` tasks, the top-level `storage` field is still required for validation, but the actual storages used are determined by `source_storage` and `target_storage` inside `params`.</blockquote><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;type&#34;</span>: <span style=color:#e6db74>&#34;transfer&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;storage&#34;</span>: <span style=color:#e6db74>&#34;local&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;params&#34;</span>: {
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;source_storage&#34;</span>: <span style=color:#e6db74>&#34;MyS3&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;source_path&#34;</span>: <span style=color:#e6db74>&#34;backups/&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;target_storage&#34;</span>: <span style=color:#e6db74>&#34;LocalDisk&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;target_path&#34;</span>: <span style=color:#e6db74>&#34;restored/&#34;</span>
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><table><thead><tr><th>params field</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td><code>source_storage</code></td><td>string</td><td>Yes</td><td>Source storage name</td></tr><tr><td><code>source_path</code></td><td>string</td><td>Yes</td><td>Path within the source storage; must contain at least one file</td></tr><tr><td><code>target_storage</code></td><td>string</td><td>Yes</td><td>Target storage name</td></tr><tr><td><code>target_path</code></td><td>string</td><td>Yes</td><td>Destination path within the target storage</td></tr></tbody></table><hr><h3 id=get-apiv1tasks--list-all-tasks>GET /api/v1/tasks — List All Tasks
<a class=anchor href=#get-apiv1tasks--list-all-tasks>#</a></h3><p>Returns all tasks created via the API. Task records are stored in memory only and are cleared on restart.</p><p><strong>Response <code>200 OK</code>:</strong></p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;tasks&#34;</span>: [
</span></span><span style=display:flex><span> {
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;task_id&#34;</span>: <span style=color:#e6db74>&#34;abc123xyz&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;type&#34;</span>: <span style=color:#e6db74>&#34;directlinks&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;status&#34;</span>: <span style=color:#e6db74>&#34;running&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;title&#34;</span>: <span style=color:#e6db74>&#34;file.zip&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;storage&#34;</span>: <span style=color:#e6db74>&#34;local&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;path&#34;</span>: <span style=color:#e6db74>&#34;downloads&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;error&#34;</span>: <span style=color:#e6db74>&#34;&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;created_at&#34;</span>: <span style=color:#e6db74>&#34;2026-03-11T10:00:00Z&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;updated_at&#34;</span>: <span style=color:#e6db74>&#34;2026-03-11T10:00:05Z&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;progress&#34;</span>: {
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;total_bytes&#34;</span>: <span style=color:#ae81ff>10485760</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;downloaded_bytes&#34;</span>: <span style=color:#ae81ff>5242880</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;percent&#34;</span>: <span style=color:#ae81ff>50.0</span>
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> }
</span></span><span style=display:flex><span> ],
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;total&#34;</span>: <span style=color:#ae81ff>1</span>
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p>The <code>progress</code> field is only included when <code>total_bytes > 0</code>. The <code>error</code> field is only included when non-empty.</p><hr><h3 id=get-apiv1taskstask_id--get-task>GET /api/v1/tasks/{task_id} — Get Task
<a class=anchor href=#get-apiv1taskstask_id--get-task>#</a></h3><p><strong>Path parameter:</strong> <code>task_id</code> — the ID returned when the task was created.</p><p><strong>Response <code>200 OK</code>:</strong> Same structure as a single task object from the list above.</p><p><strong>Error responses:</strong></p><ul><li><code>400 invalid_request</code> — no task ID in path</li><li><code>404 task_not_found</code> — task does not exist</li></ul><hr><h3 id=delete-apiv1taskstask_id--cancel-task>DELETE /api/v1/tasks/{task_id} — Cancel Task
<a class=anchor href=#delete-apiv1taskstask_id--cancel-task>#</a></h3><p><strong>Path parameter:</strong> <code>task_id</code></p><p><strong>Response <code>200 OK</code>:</strong></p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{ <span style=color:#f92672>&#34;message&#34;</span>: <span style=color:#e6db74>&#34;task cancelled successfully&#34;</span> }
</span></span></code></pre></div><p><strong>Error responses:</strong></p><ul><li><code>400 invalid_request</code> — no task ID in path</li><li><code>404 task_not_found</code> — task does not exist</li><li><code>500 cancel_failed</code> — cancellation failed</li></ul><hr><h2 id=task-statuses>Task Statuses
<a class=anchor href=#task-statuses>#</a></h2><table><thead><tr><th>Status</th><th>Meaning</th></tr></thead><tbody><tr><td><code>queued</code></td><td>Task is queued and waiting to run</td></tr><tr><td><code>running</code></td><td>Task is currently executing</td></tr><tr><td><code>completed</code></td><td>Task finished successfully</td></tr><tr><td><code>failed</code></td><td>Task encountered an error</td></tr><tr><td><code>cancelled</code></td><td>Task was cancelled via the DELETE endpoint</td></tr></tbody></table><hr><h2 id=webhook-callbacks>Webhook Callbacks
<a class=anchor href=#webhook-callbacks>#</a></h2><p>When a <code>webhook</code> URL is provided in the create request, SaveAny-Bot sends a <code>POST</code> request to that URL when the task reaches a terminal state (<code>completed</code>, <code>failed</code>, or <code>cancelled</code>).</p><p><strong>Callback request headers:</strong></p><pre tabindex=0><code>Content-Type: application/json
User-Agent: SaveAny-Bot/1.0
</code></pre><p><strong>Callback request body:</strong></p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;task_id&#34;</span>: <span style=color:#e6db74>&#34;abc123xyz&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;type&#34;</span>: <span style=color:#e6db74>&#34;directlinks&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;status&#34;</span>: <span style=color:#e6db74>&#34;completed&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;storage&#34;</span>: <span style=color:#e6db74>&#34;local&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;path&#34;</span>: <span style=color:#e6db74>&#34;downloads&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;completed_at&#34;</span>: <span style=color:#e6db74>&#34;2026-03-11T10:01:00Z&#34;</span>,
</span></span><span style=display:flex><span> <span style=color:#f92672>&#34;error&#34;</span>: <span style=color:#e6db74>&#34;&#34;</span>
</span></span><span style=display:flex><span>}
</span></span></code></pre></div><p><code>completed_at</code> is only present when status is <code>completed</code> or <code>failed</code>. <code>error</code> is only present when non-empty.</p><p><strong>Retry policy:</strong> Up to 3 attempts, with delays of 1s, 2s, and 3s between retries. Each request has a 30-second timeout.</p></article><footer class=book-footer><div class="flex flex-wrap justify-between"><div><a class="flex align-center" href=https://github.com/krau/saveany-bot/commit/38355dfd142f0f1a819a8837875b33da0d3a81b7 title='Last modified by krau | 2026/03/11' target=_blank rel=noopener><img src=/svg/calendar.svg class=book-icon alt>
<span>2026/03/11</span></a></div><div><a class="flex align-center" href=https://github.com/krau/saveany-bot/edit/main/docs/content/en/usage/api.md target=_blank rel=noopener><img src=/svg/edit.svg class=book-icon alt>
<span>Edit this page</span></a></div></div><script>(function(){function e(e){const t=window.getSelection(),n=document.createRange();n.selectNodeContents(e),t.removeAllRanges(),t.addRange(n)}document.querySelectorAll("pre code").forEach(t=>{t.addEventListener("click",function(){if(window.getSelection().toString())return;e(t.parentElement),navigator.clipboard&&navigator.clipboard.writeText(t.parentElement.textContent)})})})()</script></footer><div class=book-comments></div><label for=menu-control class="hidden book-menu-overlay"></label></div><aside class=book-toc><div class=book-toc-content><nav id=TableOfContents><ul><li><a href=#enabling-the-api>Enabling the API</a></li><li><a href=#authentication>Authentication</a></li><li><a href=#error-response-format>Error Response Format</a></li><li><a href=#endpoints>Endpoints</a><ul><li><a href=#get-health--health-check>GET /health — Health Check</a></li><li><a href=#get-apiv1storages--list-storages>GET /api/v1/storages — List Storages</a></li><li><a href=#get-apiv1task-types--list-supported-task-types>GET /api/v1/task-types — List Supported Task Types</a></li><li><a href=#post-apiv1tasks--create-task>POST /api/v1/tasks — Create Task</a></li><li><a href=#get-apiv1tasks--list-all-tasks>GET /api/v1/tasks — List All Tasks</a></li><li><a href=#get-apiv1taskstask_id--get-task>GET /api/v1/tasks/{task_id} — Get Task</a></li><li><a href=#delete-apiv1taskstask_id--cancel-task>DELETE /api/v1/tasks/{task_id} — Cancel Task</a></li></ul></li><li><a href=#task-statuses>Task Statuses</a></li><li><a href=#webhook-callbacks>Webhook Callbacks</a></li></ul></nav></div></aside></main></body></html>