Deployed 91db1fa with MkDocs version: 1.6.1

This commit is contained in:
2026-09-24 16:18:52 +00:00
parent b2747f3329
commit 1ab97c836f
7 changed files with 139 additions and 50 deletions
+42 -4
View File
@@ -1914,6 +1914,17 @@
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#slow-responses" class="md-nav__link">
<span class="md-ellipsis">
Slow responses
</span>
</a>
</li>
<li class="md-nav__item">
@@ -2785,6 +2796,17 @@
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#slow-responses" class="md-nav__link">
<span class="md-ellipsis">
Slow responses
</span>
</a>
</li>
<li class="md-nav__item">
@@ -2999,7 +3021,7 @@
<li><strong>Authentication.</strong> Every request except <code>GET /api/health</code> requires the <code>X-Secret-Key</code> header when you configure an API key. <code>--no-key</code> disables the check entirely. See <a href="../authentication/">Authentication</a>.</li>
<li><strong>Content type.</strong> Request bodies are JSON (<code>Content-Type: application/json</code>). All success responses are JSON unless this page documents a <code>204 No Content</code>.</li>
<li><strong>CORS.</strong> Every response carries permissive CORS headers, and the server answers <code>OPTIONS</code> preflight requests automatically.</li>
<li><strong>Compression.</strong> JSON responses of 256 bytes or more are gzip-compressed when the client sends <code>Accept-Encoding: gzip</code>.</li>
<li><strong>Compression.</strong> JSON responses of 256 bytes or more are gzip-compressed when the client sends <code>Accept-Encoding: gzip</code>. A <a href="#slow-responses">slow response</a> is not compressed.</li>
<li><strong>Service allowlist.</strong> The effective allowlist for your API key filters the service-facing endpoints (the intersection of the global <code>serve.services</code> list and your per-key list). The server treats services you cannot use as unknown.</li>
</ul>
<div class="admonition warning">
@@ -4647,6 +4669,20 @@ an HTTP caller would be remote code execution. You configure post-scripts only i
</span><span id="__span-28-8"><a id="__codelineno-28-8" name="__codelineno-28-8" href="#__codelineno-28-8"></a>POST /api/session/{id}/keys/bad → report a server-vault content key that did not decrypt
</span><span id="__span-28-9"><a id="__codelineno-28-9" name="__codelineno-28-9" href="#__codelineno-28-9"></a>DELETE /api/session/{id} → tear down, harvest updated cache
</span></code></pre></div>
<h3 id="slow-responses">Slow responses<a class="headerlink" href="#slow-responses" title="Permanent link">&para;</a></h3>
<p>The <code>titles</code>, <code>tracks</code> and <code>license</code> routes can take minutes on a large catalogue. When the
answer is not ready after 30 seconds, the route sends status <code>200</code> and the headers. It then
sends a newline every 30 seconds until the JSON body is ready. JSON parsers ignore the leading
newlines. This keeps the connection open through a reverse proxy that closes an idle request.</p>
<div class="admonition warning">
<p class="admonition-title">A late failure has status <code>200</code></p>
<p>The server sent the status before the work ended. A failure after the first 30 seconds
arrives as the <a href="../errors/#the-standard-error-shape">standard error body</a> with status <code>200</code>.
Check <code>status</code> in the body, not only the HTTP status. The body has no HTTP status field.
Use <code>error_code</code> and <code>retryable</code> to decide what to do.</p>
</div>
<p>A slow response is not gzip-compressed. Gzip output cannot start before the body is ready, and
the newlines must go out at once.</p>
<h3 id="post-apisessioncreate"><code>POST /api/session/create</code><a class="headerlink" href="#post-apisessioncreate" title="Permanent link">&para;</a></h3>
<p>Make a remote session for a service and title. Authentication runs asynchronously in the background. This call returns immediately with <code>status: "authenticating"</code>, and you then poll the prompt endpoint. The body accepts <code>service</code> and <code>title_id</code> (both required). It also accepts a broad set of optional keys, because the body allows <code>additionalProperties</code>. These are <code>credentials</code> (<code>{username, password, extra?}</code>), <code>cookies</code> (base64 of zlib-compressed Netscape cookie file), <code>proxy</code>, <code>no_proxy</code>, <code>profile</code>, <code>cache</code> (map of forwarded cache files, keyed by the file path relative to the service cache directory with <code>/</code> separators and no <code>.json</code> suffix, so a nested key such as <code>session_web/&lt;sha1&gt;</code> lands in a subdirectory), <code>client_region</code>, <code>proxy_region</code>, <code>cdm_type</code>, <code>range_</code>, <code>vcodec</code>, <code>quality</code>, <code>best_available</code>, <code>dl_params</code>, <code>client</code>, and any service CLI options. <code>proxy_region</code> is the country code the client resolved <code>proxy</code> from; the server matches it against its own accounts. <code>client</code> is a freeform object the dashboard shows as sent (the CLI puts <code>version</code>, <code>code_hash</code>, <code>platform</code> and a redacted <code>argv</code> in it); the server ignores it above 4096 bytes of JSON.</p>
<p><code>dl_params</code> is an object with the <code>dl</code> track selection: <code>lang</code>, <code>v_lang</code> and <code>a_lang</code> (arrays of language strings), <code>acodec</code> (an array of audio codec names or <code>dl -a</code> aliases such as <code>ddp</code>) and <code>forced_subs</code> (boolean). The service reads these values from <code>ctx.parent.params</code>. The server uses the <code>dl</code> default for each absent or malformed value, and drops an unknown codec name.</p>
@@ -4816,7 +4852,8 @@ an HTTP caller would be remote code execution. You configure post-scripts only i
</table>
<p>The buffer keeps the newest 500 records. Only the service instance's own logger is mirrored; core pipeline loggers stay on the server. A server-account session gets no log relay at all - auth-time service output can carry the operator account's identity, so it stays server-side.</p>
<h3 id="get-apisessionsession_idtitles"><code>GET /api/session/{session_id}/titles</code><a class="headerlink" href="#get-apisessionsession_idtitles" title="Permanent link">&para;</a></h3>
<p>Fetch the titles for an authenticated remote session.</p>
<p>Fetch the titles for an authenticated remote session.
A slow answer arrives as a <a href="#slow-responses">slow response</a>.</p>
<div class="tabbed-set tabbed-alternate" data-tabs="23:1"><input checked="checked" id="__tabbed_23_1" name="__tabbed_23" type="radio" /><div class="tabbed-labels"><label for="__tabbed_23_1">Response <code>200</code></label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@@ -4830,7 +4867,7 @@ an HTTP caller would be remote code execution. You configure post-scripts only i
</div>
<p>Requires an authenticated remote session. Otherwise it returns <code>404 SESSION_NOT_FOUND</code>.</p>
<h3 id="post-apisessionsession_idtracks"><code>POST /api/session/{session_id}/tracks</code><a class="headerlink" href="#post-apisessionsession_idtracks" title="Permanent link">&para;</a></h3>
<p>Get tracks and chapters for a specific title within the remote session. Body: <code>{ "title_id": "..." }</code> (required). Unlike <code>list-tracks</code>, track objects here <strong>include</strong> download URLs, along with session headers/cookies and manifest data the client needs to download directly.</p>
<p>Get tracks and chapters for a specific title within the remote session. Body: <code>{ "title_id": "..." }</code> (required). Unlike <code>list-tracks</code>, track objects here <strong>include</strong> download URLs, along with session headers/cookies and manifest data the client needs to download directly. A slow answer arrives as a <a href="#slow-responses">slow response</a>.</p>
<p><code>manifests</code> holds each served DASH/ISM manifest once. A service can build an AdaptationSet of its own that the served manifest does not contain. <code>track_manifests</code> holds a one-AdaptationSet MPD for each such track, and the client re-parses that in place of the whole manifest.</p>
<div class="tabbed-set tabbed-alternate" data-tabs="24:1"><input checked="checked" id="__tabbed_24_1" name="__tabbed_24" type="radio" /><div class="tabbed-labels"><label for="__tabbed_24_1">Response <code>200</code></label></div>
<div class="tabbed-content">
@@ -4960,7 +4997,8 @@ an HTTP caller would be remote code execution. You configure post-scripts only i
</tbody>
</table>
<h3 id="post-apisessionsession_idlicense"><code>POST /api/session/{session_id}/license</code><a class="headerlink" href="#post-apisessionsession_idlicense" title="Permanent link">&para;</a></h3>
<p>Get the content keys for the DRM. The <code>mode</code> field selects one of two modes.</p>
<p>Get the content keys for the DRM. The <code>mode</code> field selects one of two modes.
A slow answer arrives as a <a href="#slow-responses">slow response</a>.</p>
<p><strong>Proxy mode</strong> (<code>mode: "proxy"</code>, the default) forwards the client's CDM <code>challenge</code> to the service's license endpoint and returns the raw license bytes:</p>
<div class="tabbed-set tabbed-alternate" data-tabs="27:2"><input checked="checked" id="__tabbed_27_1" name="__tabbed_27" type="radio" /><input id="__tabbed_27_2" name="__tabbed_27" type="radio" /><div class="tabbed-labels"><label for="__tabbed_27_1">Request</label><label for="__tabbed_27_2">Response <code>200</code></label></div>
<div class="tabbed-content">
+35 -1
View File
@@ -1804,6 +1804,17 @@
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#late-failures-with-status-200" class="md-nav__link">
<span class="md-ellipsis">
Late failures with status 200
</span>
</a>
</li>
<li class="md-nav__item">
@@ -2198,6 +2209,17 @@
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#late-failures-with-status-200" class="md-nav__link">
<span class="md-ellipsis">
Late failures with status 200
</span>
</a>
</li>
<li class="md-nav__item">
@@ -2627,6 +2649,17 @@ body that uses an integer <code>status</code> and no <code>error_code</code> or
(integer <code>status</code>, no <code>error_code</code>). The <code>GET /api/health</code> endpoint is exempt
from authentication, so it never returns the key-error shape.</p>
</div>
<h2 id="late-failures-with-status-200">Late failures with status <code>200</code><a class="headerlink" href="#late-failures-with-status-200" title="Permanent link">&para;</a></h2>
<p>The remote session <code>titles</code>, <code>tracks</code> and <code>license</code> routes send status <code>200</code> and the headers
when the work takes longer than 30 seconds. They then send a newline every 30 seconds to keep
the connection open. See <a href="../endpoints/#slow-responses">Slow responses</a>. A failure after that
time arrives as the standard error body, but with status <code>200</code>. The body has no field for the
HTTP status that the error would otherwise have. <code>error_code</code> and <code>retryable</code> stay in the body.</p>
<div class="admonition warning">
<p class="admonition-title">Check the body on these routes</p>
<p>On these routes a <code>200</code> does not always mean success. Parse the body and treat
<code>"status": "error"</code> with an <code>error_code</code> as a failure.</p>
</div>
<h2 id="responses-with-no-body">Responses with no body<a class="headerlink" href="#responses-with-no-body" title="Permanent link">&para;</a></h2>
<p>Not every failure or completion has a JSON body. Some endpoints answer with
<strong><code>204 No Content</code></strong> and an empty body, notably deleting a terminal download job and
@@ -2676,7 +2709,8 @@ debugging, never on a server reachable by untrusted clients.</p>
<div class="admonition note">
<p class="admonition-title">Reference consumer</p>
<p>unshackle's own remote-download client treats any status of <code>400</code> or above as
fatal: it logs <code>Server error [&lt;error_code&gt;]: &lt;message&gt;</code> and exits. It also wraps
fatal, and a <code>200</code> body with <code>"status": "error"</code> and an <code>error_code</code> too: it logs
<code>Server error [&lt;error_code&gt;]: &lt;message&gt;</code> and exits. It also wraps
requests in a retry adapter (five attempts, backing off on <code>429</code>, <code>500</code>, <code>502</code>,
<code>503</code>, and <code>504</code>). This is a reasonable template for your own client's error
handling.</p>
+17 -5
View File
@@ -2646,12 +2646,12 @@ generally never touch this directly, but it explains behaviour you might observe
<tr>
<td>Idle session TTL</td>
<td><strong>300s</strong> (5 min) default</td>
<td>Refreshed on every request to the session</td>
<td>Refreshed on every request to the session and when a <code>titles</code> or <code>tracks</code> request ends. A remote session does not expire while a request works on it</td>
</tr>
<tr>
<td>Max concurrent sessions</td>
<td><strong>100</strong> default</td>
<td>Oldest (least recently used) is evicted when full</td>
<td>Oldest (least recently used) is evicted when full. A remote session that a request works on goes last</td>
</tr>
<tr>
<td>Auth/input timeout</td>
@@ -2827,6 +2827,9 @@ layer.</p>
<li><code>InputBridge.request_input(prompt, timeout=600)</code> blocks the sync auth thread on a
<code>threading.Event</code> until <code>submit_response()</code> or <code>cancel()</code> fires.</li>
<li>A timeout raises <code>TimeoutError</code> and marks the remote session <code>FAILED</code>.</li>
<li>A prompt after authentication (in <code>get_titles()</code>, <code>get_tracks()</code> or licensing) raises
<code>RuntimeError</code> at once. The client polls for prompts only during authentication, so
no answer can come.</li>
<li><code>AUTH_INPUT_TIMEOUT = 600.0</code> seconds. This is also the TTL granted to
<code>AUTHENTICATING</code> / <code>PENDING_INPUT</code> sessions in the store.</li>
</ul>
@@ -2956,9 +2959,18 @@ seconds left before this specific remote session expires.</p>
remote session API and a good template for any client.</p>
<ul>
<li><strong><code>RemoteClient.request</code></strong> sets <code>X-Secret-Key</code> and <code>User-Agent: unshackle/&lt;version&gt;</code>,
uses a 120s timeout for <code>POST</code> and 30s for <code>GET</code>/<code>DELETE</code>, and treats any
<code>status_code &gt;= 400</code> as fatal: it logs <code>Server error [&lt;error_code&gt;]: &lt;message&gt;</code>
and raises <code>SystemExit(1)</code>.</li>
uses the server's <code>timeout</code> from <code>remote_services</code> (default 120s) as the read timeout,
and treats any <code>status_code &gt;= 400</code> as fatal: it logs <code>Server error [&lt;error_code&gt;]: &lt;message&gt;</code>
and raises <code>SystemExit(1)</code>. A <code>200</code> body with <code>"status": "error"</code> is also fatal (see
the heartbeat below).</li>
<li><strong>Heartbeat.</strong> The <code>titles</code>, <code>tracks</code> and <code>license</code> routes send a newline every 30s while
the service works. The read timeout starts again at each byte, so a slow request stays
open for as long as the server works on it, and a reverse proxy does not close it as
idle. A route that answers within 30s sends the usual response. After 30s the status
is already <code>200</code>, so a failure after that time arrives as the usual error body with
status <code>200</code>. Your client must check <code>status</code> in the body. JSON parsers ignore the
leading newlines. The server does not gzip-compress a heartbeat response, because gzip
output cannot start before the body is ready.</li>
<li><strong>Retries.</strong> The download-side HTTP session mounts an adapter with
<code>Retry(total=5, backoff_factor=0.2, status_forcelist=[429, 500, 502, 503, 504])</code>.</li>
<li><strong>Flow.</strong> <code>authenticate()</code> → <code>create</code> (+ poll <code>prompt</code> every 2s up to a 600s