1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>API Reference - The Black Hole Bay</title> 7 <meta name="description" content="Full API reference for The Black Hole Bay - torrent search, movies, TV, games, and more."> 8 <link rel="icon" type="image/jpeg" href="https://theblackholebay.lol/images/icon-haloween.jpg"> 9 <style> 10 :root{--bg:#050505;--bg2:#0a0a0a;--card:#0d0d0d;--border:#1a1a1a;--border2:#2a2a2a;--text:#e0e0e0;--text2:#888;--text3:#555;--orange:#ff6b35;--green:#00d27a;--blue:#3b82f6;--code-bg:#020202;--sidebar-w:260px;} 11 *{box-sizing:border-box;margin:0;padding:0;} 12 body{background:var(--bg);color:var(--text);font-family:'Segoe UI',-apple-system,sans-serif;line-height:1.6;background-image:url('https://www.transparenttextures.com/patterns/stardust.png');background-attachment:fixed;} 13 a{color:var(--orange);text-decoration:none;transition:color .15s;} 14 a:hover{text-decoration:underline;} 15 ::selection{background:var(--orange);color:#fff;} 16 ::-webkit-scrollbar{width:8px;height:8px;} 17 ::-webkit-scrollbar-track{background:var(--bg2);} 18 ::-webkit-scrollbar-thumb{background:var(--border2);border-radius:4px;} 19 ::-webkit-scrollbar-thumb:hover{background:var(--text3);} 20 code,pre{font-family:'SF Mono','Fira Code','Courier New',monospace;font-size:13px;} 21 22 .nav{position:sticky;top:0;z-index:200;background:rgba(5,5,5,.92);backdrop-filter:blur(12px);border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;padding:10px 20px;} 23 .nav-brand{display:flex;align-items:center;gap:10px;font-weight:700;font-size:15px;color:var(--text);} 24 .nav-brand img{width:24px;height:24px;border-radius:6px;} 25 .nav-links{display:flex;gap:20px;align-items:center;} 26 .nav-links a{color:var(--text2);font-size:14px;font-weight:600;} 27 .nav-links a:hover{color:var(--orange);text-decoration:none;} 28 .menu-btn{display:none;background:var(--card);border:1px solid var(--border2);color:var(--text);padding:6px 10px;border-radius:6px;cursor:pointer;font-size:14px;} 29 30 .layout{display:flex;max-width:1100px;margin:0 auto;padding:20px;gap:24px;} 31 32 /* SIDEBAR */ 33 .sidebar{width:var(--sidebar-w);flex-shrink:0;position:sticky;top:60px;align-self:flex-start;max-height:calc(100vh - 80px);overflow-y:auto;padding-right:8px;} 34 .sidebar-group{margin-bottom:24px;} 35 .sidebar-h{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.8px;color:var(--text3);margin-bottom:8px;padding-left:12px;} 36 .sidebar a{display:block;color:var(--text2);font-size:14px;padding:5px 12px;border-radius:6px;transition:all .12s;border-left:2px solid transparent;} 37 .sidebar a:hover{color:var(--text);background:rgba(255,255,255,.03);text-decoration:none;border-left-color:var(--border2);} 38 .sidebar a.active{color:var(--orange);background:rgba(255,107,53,.06);border-left-color:var(--orange);font-weight:600;} 39 40 /* MAIN */ 41 .main{flex:1;min-width:0;} 42 43 /* HERO */ 44 .api-hero{text-align:center;margin-bottom:40px;padding:30px 0;} 45 .api-hero img{width:56px;height:56px;border-radius:12px;margin-bottom:10px;} 46 .api-hero h1{font-size:28px;margin-bottom:6px;} 47 .api-hero p{color:var(--text2);font-size:15px;} 48 49 /* SECTIONS */ 50 .section{margin-bottom:48px;scroll-margin-top:70px;} 51 .section-h{font-size:22px;font-weight:700;margin-bottom:8px;display:flex;align-items:center;gap:8px;padding-bottom:8px;border-bottom:1px solid var(--border);} 52 .section-h .bar{width:4px;height:22px;background:var(--orange);border-radius:2px;} 53 .section-desc{color:var(--text2);font-size:14px;margin-bottom:20px;} 54 55 /* ENDPOINT CARD */ 56 .ep{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:20px;margin-bottom:16px;scroll-margin-top:70px;transition:border-color .2s;} 57 .ep:hover{border-color:var(--border2);} 58 .ep-head{display:flex;align-items:center;gap:10px;margin-bottom:8px;flex-wrap:wrap;} 59 .method{font-size:11px;font-weight:800;padding:3px 10px;border-radius:5px;letter-spacing:.5px;} 60 .get{background:rgba(0,210,122,.12);color:var(--green);border:1px solid rgba(0,210,122,.25);} 61 .post{background:rgba(59,130,246,.12);color:var(--blue);border:1px solid rgba(59,130,246,.25);} 62 .path{font-family:monospace;font-size:14px;color:var(--text);word-break:break-all;} 63 .ep h3{font-size:17px;margin-bottom:6px;font-weight:600;} 64 .ep>p{color:var(--text2);font-size:15px;margin-bottom:12px;} 65 66 /* TABLES */ 67 .tbl{width:100%;border-collapse:collapse;margin:10px 0;font-size:14px;} 68 .tbl th{text-align:left;color:var(--text2);font-size:11px;text-transform:uppercase;letter-spacing:.5px;padding:7px 10px;border-bottom:1px solid var(--border2);font-weight:700;} 69 .tbl td{padding:7px 10px;border-bottom:1px solid var(--border);color:var(--text2);font-size:14px;vertical-align:top;} 70 .tbl td:first-child{color:var(--text);font-family:monospace;font-size:13px;} 71 .tbl td:nth-child(2){font-family:monospace;font-size:13px;} 72 .req{color:var(--orange)!important;font-weight:700;} 73 .opt{color:var(--text3)!important;} 74 75 /* CODE BLOCKS */ 76 .code-block{background:var(--code-bg);border:1px solid var(--border);border-radius:10px;overflow:hidden;margin:12px 0;} 77 .code-head{background:var(--bg2);border-bottom:1px solid var(--border);padding:7px 12px;display:flex;align-items:center;justify-content:space-between;} 78 .code-lang{font-size:11px;color:var(--text3);font-family:monospace;text-transform:uppercase;letter-spacing:.5px;} 79 .copy-btn{background:transparent;border:1px solid var(--border2);color:var(--text2);font-size:12px;padding:3px 10px;border-radius:5px;cursor:pointer;transition:all .15s;} 80 .copy-btn:hover{background:var(--card);color:var(--orange);border-color:var(--orange);} 81 .copy-btn.copied{color:var(--green);border-color:var(--green);} 82 .code-body{padding:14px;overflow-x:auto;} 83 .code-body code{color:#c8c8c8;line-height:1.5;} 84 .code-body .k{color:var(--orange);} 85 .code-body .s{color:var(--green);} 86 .code-body .n{color:var(--blue);} 87 88 /* NOTE */ 89 .note{background:rgba(59,130,246,.06);border:1px solid rgba(59,130,246,.2);border-radius:8px;padding:12px 16px;margin:12px 0;font-size:14px;color:var(--text2);} 90 .note b{color:var(--blue);} 91 .warn{background:rgba(255,107,53,.06);border:1px solid rgba(255,107,53,.2);border-radius:8px;padding:12px 16px;margin:12px 0;font-size:14px;color:var(--text2);} 92 .warn b{color:var(--orange);} 93 94 .footer{text-align:center;padding:30px 20px;color:var(--text3);font-size:13px;border-top:1px solid var(--border);margin-top:40px;} 95 96 /* MOBILE SIDEBAR */ 97 .sidebar-overlay{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.6);z-index:150;} 98 .sidebar-overlay.show{display:block;} 99 body.sidebar-open .sidebar{position:fixed;top:0;left:0;bottom:0;max-height:100vh;z-index:160;width:280px;background:var(--bg2);padding:20px;border-right:1px solid var(--border2);overflow-y:auto;transform:none;} 100 body.sidebar-open .sidebar-overlay{display:block;} 101 102 @media(max-width:768px){ 103 .menu-btn{display:block;} 104 .sidebar{display:none;} 105 .layout{padding:14px;gap:0;} 106 .api-hero h1{font-size:22px;} 107 .section-h{font-size:18px;} 108 .ep{padding:14px;} 109 .tbl{font-size:12px;} 110 .nav-links{gap:12px;} 111 } 112 </style> 113<link rel="stylesheet" href="/css/site-layout.css"></head> 114<body> 115 <nav class="nav"> 116 <a href="/" class="nav-brand"><img src="https://theblackholebay.lol/images/icon-haloween.jpg" alt="Logo"> The Black Hole Bay</a> 117 <div class="nav-links"><a href="/">Docs</a><a href="/api">API</a><a href="https://theblackholebay.lol/">Main Site</a></div> 118 </nav> 119 120 <div class="layout"> 121 <!-- SIDEBAR --> 122 <div class="sidebar-overlay" onclick="document.body.classList.remove('sidebar-open')"></div> 123 <aside class="sidebar" id="sidebar"> 124 <div class="sidebar-group"> 125 <div class="sidebar-h">Getting Started</div> 126 <a href="#base-url">Base URL</a> 127 <a href="#auth">Authentication</a> 128 <a href="#rate-limit">Rate Limiting</a> 129 <a href="#response-format">Response Format</a> 130 </div> 131 <div class="sidebar-group"> 132 <div class="sidebar-h">Torrents</div> 133 <a href="#search">Search Torrents</a> 134 <a href="#torrent-details">Torrent Details</a> 135 <a href="#torrent-files">Torrent Files</a> 136 <a href="#track-download">Track Download</a> 137 </div> 138 <div class="sidebar-group"> 139 <div class="sidebar-h">User Accounts</div> 140 <a href="#user-register">Register</a> 141 <a href="#user-login">Login</a> 142 <a href="#user-me">Get Current User</a> 143 <a href="#user-profile">Get Public Profile</a> 144 <a href="#user-upload">Upload Torrent</a> 145 <a href="#user-rate">Rate User</a> 146 </div> 147 <div class="sidebar-group"> 148 <div class="sidebar-h">Movies & TV</div> 149 <a href="#trending-movies">Trending Movies</a> 150 <a href="#movie-lists">Movie Lists</a> 151 <a href="#tv-lists">TV Lists</a> 152 <a href="#discover">Discover</a> 153 <a href="#tmdb-search">TMDB Search</a> 154 <a href="#tv-season">Season Episodes</a> 155 <a href="#suggestions-endpoint">Suggestions</a> 156 <a href="#applications-endpoint">Applications</a> 157 <a href="#music-artwork">Music Artwork</a> 158 <a href="#music-import">Playlist Import</a> 159 <a href="#trending-tv">Trending TV</a> 160 <a href="#now-playing">Now Playing</a> 161 <a href="#popular-tv">Popular TV</a> 162 <a href="#movie-details">Movie Details</a> 163 <a href="#tv-details">TV Details</a> 164 </div> 165 <div class="sidebar-group"> 166 <div class="sidebar-h">Games</div> 167 <a href="#trending-games">Trending Games</a> 168 <a href="#desktop-updates">Desktop App Updates</a> 169 </div> 170 <div class="sidebar-group"> 171 <div class="sidebar-h">Reference</div> 172 <a href="#images">Images</a> 173 <a href="#errors">Errors</a> 174 <a href="#code-examples">Code Examples</a> 175 <a href="#best-practices">Best Practices</a> 176 </div> 177 </aside> 178 179 <!-- MAIN --> 180 <main class="main"> 181 <div class="api-hero"> 182 <img src="https://theblackholebay.lol/images/icon-haloween.jpg" alt="The Black Hole Bay"> 183 <h1>API Reference</h1> 184 <p>Full endpoint documentation with parameters, responses, and code examples</p> 185 </div> 186 187 <!-- GETTING STARTED --> 188 <div class="section" id="base-url"> 189 <div class="section-h"><span class="bar"></span> Getting Started</div> 190 <p class="section-desc">Everything you need to start using the API.</p> 191 <div class="ep"> 192 <h3>Base URL</h3> 193 <p>All API requests should be made to:</p> 194 <div class="code-block"><div class="code-head"><span class="code-lang">text</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>https://theblackholebay.lol</code></div></div> 195 <div class="note"><b>Note:</b> All endpoints are prefixed with <code>/api</code>. Example: <code>https://theblackholebay.lol/api/search?q=dune</code></div> 196 </div> 197 <div class="ep" id="auth"> 198 <h3>Authentication</h3> 199 <p>No authentication is required for public search and metadata endpoints.</p> 200 <table class="tbl"><tr><th>Header</th><th>Required</th><th>Description</th></tr> 201 <tr><td>Authorization</td><td class="opt">No</td><td>Not required for public endpoints</td></tr> 202 <tr><td>x-session-token</td><td class="req">Yes</td><td>Required for User endpoints (Upload, Rate, Profile Update)</td></tr></table> 203 <div class="note"><b>Note:</b> All third-party API keys (TMDB, RAWG) are kept server-side and are never exposed in client responses.</div> 204 </div> 205 <div class="ep" id="rate-limit"> 206 <h3>Rate Limiting</h3> 207 <p>The API uses IP-based rate limiting.</p> 208 <table class="tbl"><tr><th>Setting</th><th>Value</th></tr> 209 <tr><td>Requests</td><td>120 / minute / IP</td></tr> 210 <tr><td>Auth Limit</td><td>5 / minute / IP (Login/Register)</td></tr> 211 <tr><td>Enforcement</td><td>Automatic</td></tr> 212 <tr><td>Exceeded</td><td><code>429 Too Many Requests</code></td></tr></table> 213 </div> 214 <div class="ep" id="response-format"> 215 <h3>Response Format</h3> 216 <p>All responses are JSON with the following headers:</p> 217 <table class="tbl"><tr><th>Header</th><th>Value</th></tr> 218 <tr><td>Content-Type</td><td>application/json; charset=utf-8</td></tr> 219 <tr><td>Cache-Control</td><td>no-store</td></tr></table> 220 </div> 221 </div> 222 223 <!-- TORRENTS --> 224 <div class="section" id="torrents"> 225 <div class="section-h"><span class="bar"></span> Torrent API</div> 226 <p class="section-desc">Search torrents, get metadata, list files, and track downloads.</p> 227 228 <div class="ep" id="search"> 229 <div class="ep-head"><span class="method get">
229GET</span><span class="path">/api/search</span></div> 230 <p>Queries configured torrent indexers concurrently and merges their results with the local index, including DHT and user uploads. Results are filtered and ranked for relevance before seed count. Season queries such as <code>the expanse S01</code> search the show title and return matching-season releases, including season packs.</p> 231 <table class="tbl"><tr><th>Parameter</th><th>Required</th><th>Default</th><th>Description</th></tr> 232 <tr><td>q</td><td class="opt">No</td><td>""</td><td>Search query. Empty = browse latest uploads</td></tr> 233 <tr><td>category</td><td class="opt">No</td><td>0</td><td>Category code (see below)</td></tr> 234 </table> 235 <p style="margin-top:8px;color:var(--text2);font-size:13px;">Category codes: <code>0</code>=All, <code>100</code>=Audio, <code>200</code>=Video, <code>300</code>=Apps, <code>400</code>=Games, <code>500</code>=Adult, <code>600</code>=Other</p> 236 <div class="code-block"><div class="code-head"><span class="code-lang">bash</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>curl "https://theblackholebay.lol/api/search?q=breaking+bad&category=200"</code></div></div> 237 <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{ 238 "query": "breaking bad", 239 "source": "remote_api+local+multi_indexer", 240 "results": [{ 241 "id": "4034523", 242 "name": "Breaking.Bad.S01E01.1080p.BluRay.x265", 243 "info_hash": "abc123def456...", 244 "magnet": "magnet:?xt=urn:btih:abc123def456...", 245 "seeders": 245, 246 "leechers": 12, 247 "size": 1073741824, 248 "sizeLabel": "1.00 GiB", 249 "category": 200, 250 "added": 1700000000, 251 "username": "uploader123", 252 "status": "trusted", 253 "source": "TPB" 254 }] 255}</code></div></div> 256 <table class="tbl"><tr><th>Field</th><th>Type</th><th>Description</th></tr> 257 <tr><td>id</td><td>string</td><td>Unique torrent ID</td></tr> 258 <tr><td>name</td><td>string</td><td>Torrent title</td></tr> 259 <tr><td>info_hash</td><td>string</td><td>BitTorrent info hash</td></tr> 260 <tr><td>magnet</td><td>string</td><td>Full magnet URI with trackers</td></tr> 261 <tr><td>seeders</td><td>number</td><td>Active seeders</td></tr> 262 <tr><td>leechers</td><td>number</td><td>Active leechers</td></tr> 263 <tr><td>size</td><td>number</td><td>Size in bytes</td></tr> 264 <tr><td>sizeLabel</td><td>string</td><td>Human-readable size</td></tr> 265 <tr><td>added</td><td>number</td><td>Unix timestamp</td></tr> 266 <tr><td>status</td><td>string</td><td>Uploader status (trusted/vip)</td></tr> 267 <tr><td>source</td><td>string</td><td>Origin, such as TPBMirror, 1337x, YTS, DHT, or User_Upload</td></tr></table> 268 <div class="warn"><b>Fallback:</b> If the live search fails, the endpoint returns matching results from the local index with response <code>source: "local_fallback"</code>.</div> 269 </div> 270 271 <div class="ep" id="torrent-details"> 272 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/torrent/{id}</span></div> 273 <p>Returns full metadata for a single torrent.</p> 274 <div class="code-block"><div class="code-head"><span class="code-lang">bash</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>curl "https://theblackholebay.lol/api/torrent/4034523"</code></div></div> 275 <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>
275{ "torrent": { "id":"4034523", "name":"...", "magnet":"magnet:?...", "seeders":245, "sizeLabel":"1.00 GiB" } }</code></div></div> 276 </div> 277 278 <div class="ep" id="torrent-files"> 279 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/files/{id}</span></div> 280 <p>Returns the list of files inside a torrent. For user-uploaded torrents, this is fetched dynamically via WebTorrent.</p> 281 <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{ 282 "files": [ 283 { "name": "video.mkv", "size": 1073741824, "sizeLabel": "1.00 GiB" }, 284 { "name": "subs.srt", "size": 45200, "sizeLabel": "44.14 KiB" } 285 ] 286}</code></div></div> 287 </div> 288 289 <div class="ep" id="track-download"> 290 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/track/download</span></div> 291 <p>Increments the download counter. Call this when a user clicks a magnet link.</p> 292 <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{ "ok": true }</code></div></div> 293 </div> 294 </div> 295 296 <!-- USERS --> 297 <div class="section" id="users"> 298 <div class="section-h"><span class="bar"></span> User API</div> 299 <p class="section-desc">Register, login, upload torrents, and rate users.</p> 300 301 <div class="ep" id="user-register"> 302 <div class="ep-head"><span class="method post">POST</span><span class="path">/api/auth/register</span></div> 303 <p>Register a new user account. Returns a session token. <code>acceptToS: true</code> is required - accounts are only created when the Terms of Service and Privacy Policy are accepted.</p> 304 <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>// Request Body 305{ 306 "username": "newuser", 307 "password": "supersecretpassword", 308 "acceptToS": true 309} 310 311// Response 200 312{ 313 "success": true, 314 "token": "SESSION_TOKEN_HERE", 315 "user": { "id": "...", "username": "newuser", "rank": 1, "xp": 0 } 316}</code></div></div> 317 </div> 318 319 <div class="ep" id="user-login"> 320 <div class="ep-head"><span class="method post">POST</span><span class="path">/api/auth/login</span></div> 321 <p>Login to an existing account. Returns a session token.</p> 322 <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>// Request Body 323{ 324 "username": "existinguser", 325 "password": "supersecretpassword" 326} 327 328// Response 200 329{ 330 "success": true, 331 "token": "SESSION_TOKEN_HERE", 332 "user": { "id": "...", "username": "existinguser", "rank": 5, "xp": 1200 } 333}</code></div></div> 334 </div> 335 336 <div class="ep" id="user-me"> 337 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/user/me</span></div> 338 <p>Returns the currently logged-in user's private profile data. Requires <code>x-session-token</code> header.</p> 339 <div class="code-block"><div class="code-head"><span class="code-lang">bash</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>curl -H "x-session-token: YOUR_TOKEN" "https://theblackholebay.lol/api/user/me"</code></div></div> 340 </div> 341 342 <div class="ep" id="user-profile"> 343 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/user/profile/{username}</span></div> 344 <p>Returns public profile data, uploaded torrents, and rating info for a specific user.</p> 345 <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>// Response 200 346{ 347 "user": { 348 "id": "abc123", 349 "username": "Cybotage",
350 "avatarPath": "/uploads/avatars/abc123.jpg", 351 "bio": "Official uploader", 352 "rank": 10, 353 "badges": ["5 Uploads", "10 Uploads"] 354 }, 355 "torrents": [ { "id": "idx_123", "name": "Movie.mkv" } ], 356 "avgRating": "4.8" 357}</code></div></div> 358 </div> 359 360 <div class="ep" id="user-upload"> 361 <div class="ep-head"><span class="method post">POST</span><span class="path">/api/user/upload</span></div> 362 <p>Submit a new torrent to the index. Auto-approved. Requires <code>x-session-token</code> header.</p> 363 <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>// Request Body 364{ 365 "name": "My.Custom.Torrent.1080p", 366 "info_hash": "9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b", // 40 hex chars 367 "category": 200, 368 "description": "Optional description here" 369} 370 371// Response 200 372{ 373 "success": true, 374 "message": "Torrent uploaded successfully! (+50 XP)" 375}</code></div></div> 376 <div class="warn"><b>Note:</b> Duplicate info hashes are rejected with <code>409 Conflict</code>.</div> 377 </div> 378 379 <div class="ep" id="user-rate"> 380 <div class="ep-head"><span class="method post">POST</span><span class="path">/api/user/rate</span></div> 381 <p>Rate another user (1-5 stars). You can rate a user only once per IP. Requires <code>x-session-token</code> header.</p> 382 <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>// Request Body 383{ 384 "toUserId": "abc123", 385 "score": 5 386} 387 388// Response 200 389{ 390 "success": true, 391 "newRank": 9 392}</code></div></div> 393 </div> 394 </div> 395 396 <!-- MOVIES & TV --> 397 <div class="section" id="movies-tv"> 398 <div class="section-h"><span class="bar"></span> Movies & TV</div> 399 <p class="section-desc">Movie and TV metadata powered by TMDB.</p> 400 401 <div class="ep" id="trending-movies"> 402 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/trending</span></div> 403 <p>Trending movies this week.</p> 404 <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{ 405 "results": [{ 406 "id": 872585, 407 "title": "Oppenheimer", 408 "overview": "...", 409 "poster_path": "/abc.jpg", 410 "backdrop_path": "/def.jpg", 411 "vote_average": 8.4, 412 "release_date": "2024-01-15", 413 "original_language": "en" 414 }] 415}</code></div></div> 416 </div> 417 418 <div class="ep" id="trending-tv"> 419 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/trending_tv</span></div> 420 <p>Trending TV shows this week. Uses <code>name</code> instead of <code>title</code> and <code>first_air_date</code> instead of <code>release_date</code>.</p> 421 </div> 422 423 <div class="ep" id="now-playing"> 424 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/upcoming?page=1</span></div> 425 <p>Movies currently in theaters. Posters cached locally.</p> 426 <table class="tbl"><tr><th>Parameter</th><th>Default</th><th>Description</th></tr> 427 <tr><td>page</td><td>1</td><td>Page number (20 per page)</td></tr></table> 428 <div class="note"><b>Note:</b> <code>poster_path</code> returns a local path: <code>/upcoming/images/{id}.jpg</code> - use directly as <code>https://theblackholebay.lol/upcoming/images/{id}.jpg</code>. Movies older than 90 days are filtered out.</div> 429 </div> 430 431 <div class="ep" id="popular-tv"> 432 <div class="ep-head"><span class="method get">
432GET</span><span class="path">/api/tv_airing?page=1</span></div> 433 <p>Currently popular TV shows, filtered to the last 5 years.</p> 434 <table class="tbl"><tr><th>Parameter</th><th>Default</th><th>Description</th></tr> 435 <tr><td>page</td><td>1</td><td>Page number</td></tr></table> 436 </div> 437 438 <div class="ep" id="movie-lists"> 439 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/movies?list={list}&page={n}</span></div> 440 <p>Cached movie lists. <code>list</code>: <code>now_playing</code>, <code>popular</code>, <code>top_rated</code>, <code>upcoming</code>. Poster art is downloaded once and served locally from <code>/upcoming/images/</code>.</p> 441</div> 442<div class="ep" id="tv-lists"> 443 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/tv?list={list}&page={n}</span></div> 444 <p>Cached TV lists. <code>list</code>: <code>popular</code>, <code>top_rated</code>, <code>on_the_air</code>, <code>airing_today</code>.</p> 445</div> 446<div class="ep" id="discover"> 447 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/discover?type={movie|tv}&genre={id}&page={n}</span></div> 448 <p>Browse movies or TV by TMDB genre id (cached 6 hours). Without a genre id this returns the popular list.</p> 449</div> 450<div class="ep" id="tmdb-search"> 451 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/tmdb/search?type={movie|tv}&q={query}&page={n}</span></div> 452 <p>Proxied movie/TV search - the TMDB key never leaves the server. Results are cached for 30 minutes.</p> 453</div> 454<div class="ep" id="tv-season"> 455 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/tv/{id}/season/{n}</span></div> 456 <p>Episode list for a TV season (cached 24 hours).</p> 457</div> 458<div class="ep" id="suggestions-endpoint"> 459 <div class="ep-head"><span class="method post">POST</span><span class="path">/api/suggestions</span></div> 460 <p>Submit a feature suggestion. Body: <code>{ title, category, description }</code>. Rate limited (5/min per IP), 20KB body cap, honeypot field.</p> 461</div> 462<div class="ep" id="applications-endpoint"> 463 <div class="ep-head"><span class="method post">POST</span><span class="path">/api/applications</span></div> 464 <p>Submit a staff application from <code>/apply</code>. Body: <code>{ role, username, contact, timezone, availability, skills, experience, motivation, links }</code>. Rate limited (3/min per IP).</p> 465</div> 466<div class="ep" id="music-artwork"> 467 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/music/artwork?q={artist title}</span></div> 468 <p>Album artwork + artist lookup for the desktop music player (keyless iTunes-search proxy, 24h server-side cache, never errors - returns <code>{ artwork: null }</code> on miss).</p> 469</div> 470<div class="ep" id="music-import"> 471 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/music/import?url={playlist link}</span></div> 472 <p>Import a YouTube playlist or a public Spotify playlist. The server parses the embedded page data and returns the track list so clients can match each track to the site's audio index.</p> 473</div> 474 <div class="ep" id="movie-details"> 475 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/movie/{id}</span></div> 476 <p>Full movie details including cast, runtime, genres, and IMDb link.</p> 477 <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{ 478 "id": 872585, 479 "title": "Oppenheimer", 480 "runtime": 180, 481 "imdb_id": "tt15398776", 482 "genres": [{ "name": "Drama" }], 483 "credits": { 484 "cast": [{ "name": "Cillian Murphy", "character": "Oppenheimer" }] 485 } 486}</code></div></div> 487 </div> 488 489 <div class="ep" id="tv-details"> 490 <div class="ep-head"><span class="method get">
490GET</span><span class="path">/api/tv/{id}</span></div> 491 <p>Full TV show details including cast, seasons, and episodes.</p> 492 <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{ 493 "name": "Show Name", 494 "number_of_seasons": 3, 495 "number_of_episodes": 24, 496 "episode_run_time": [55], 497 "status": "Returning Series" 498}</code></div></div> 499 </div> 500 </div> 501 502 <!-- GAMES --> 503 <div class="section" id="games"> 504 <div class="section-h"><span class="bar"></span> Games</div> 505 <p class="section-desc">Game metadata powered by RAWG.</p> 506 507 <div class="ep" id="trending-games"> 508 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/trending_games</span></div> 509 <p>Recently released games from the last 6 months, sorted by release date. Filtered to modern platforms (PC, PS4, PS5, Xbox One, Xbox Series, Switch). Up to 24 results.</p> 510 <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{ 511 "results": [{ 512 "name": "Black Myth: Wukong", 513 "background_image": "https://media.rawg.io/media/games/xxx.jpg", 514 "released": "2024-08-20", 515 "rating": "85", 516 "genres": "Action, RPG", 517 "platforms": "PC, PlayStation 5" 518 }] 519}</code></div></div> 520 <table class="tbl"><tr><th>Field</th><th>Type</th><th>Description</th></tr> 521 <tr><td>name</td><td>string</td><td>Game name</td></tr> 522 <tr><td>background_image</td><td>string</td><td>Direct image URL - use as-is</td></tr> 523 <tr><td>released</td><td>string</td><td>Release date (YYYY-MM-DD)</td></tr> 524 <tr><td>rating</td><td>string?</td><td>Metacritic score or RAWG rating</td></tr> 525 <tr><td>genres</td><td>string</td><td>Comma-separated genre names</td></tr> 526 <tr><td>platforms</td><td>string</td><td>Comma-separated platform names</td></tr></table> 527 </div> 528 </div> 529 530 <!-- DESKTOP APP UPDATES --> 531 <div class="section" id="desktop-updates"> 532 <div class="section-h"><span class="bar"></span> Desktop App Updates</div> 533 <p class="section-desc">Release feed and auto-update support for the BlackHole Bay desktop app. Releases are published from the admin panel under <b>App Updates</b>. These endpoints are public and stay reachable even while the site is in maintenance mode.</p> 534 535 <div class="ep" id="update-check"> 536 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/update</span></div> 537 <p>Checks whether a newer build than the caller's is published. This is the endpoint the desktop app calls on launch to decide whether to prompt for an update.</p> 538 <div class="code-block"><div class="code-head"><span class="code-lang">bash</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>curl "https://theblackholebay.lol/api/update?current=1.0.0&channel=stable"</code></div></div> 539 <table class="tbl"><tr><th>Parameter</th><th>Required</th><th>Description</th></tr> 540 <tr><td>current</td><td class="opt">No</td><td>Version the client is currently running. Omit to just fetch the latest release.</td></tr> 541 <tr><td>channel</td><td class="opt">No</td><td><code>stable</code> (default) or <code>beta</code>. Beta clients also receive stable builds; stable clients never see betas.</td></tr></table> 542 <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{ 543 "updateAvailable": true, 544 "current": "1.0.0", 545 "latest": { 546 "id": "6b6e95cf579526b6", 547 "version": "1.1.0", 548 "name": "Stability & Performance Patch", 549 "notes": [ 550 "Fixed stalled downloads on resume", 551 "Improved streaming buffer handling" 552 ], 553 "channel": "stable", 554 "size": 2097152, 555 "sha256": "6ea73b45c3b229e3...", 556 "publishedAt": 1790594645585, 557 "fileName": "BlackHoleBaySetup1.1.0.exe", 558 "available": true, 559 "downloadUrl": "/api/releases/6b6e95cf579526b6/download" 560 } 561}</code></div></div> 562 <table class="tbl"><tr><th>Field</th><th>Type</th><th>Description</th></tr> 563 <tr><td>updateAvailable</td><td>boolean</td><td>True when latest is newer than current. Always <code>false</code> when current is omitted.</td></tr> 564 <tr><td>current</td><td>string</td><td>Echo of the supplied current version.</td></tr> 565 <tr><td>latest</td><td>object?</td><td>The newest available release, or <code>null</code> if nothing is published.</td></tr></table> 566 <div class="note"><b>
566Always HTTP 200.</b> This endpoint never returns an error status for "no update" - check the <code>updateAvailable</code> field instead.</div> 567 </div> 568 <div class="ep" id="releases-list"> 569 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/releases</span></div> 570 <p>Returns every published release, newest first, plus the current stable build. Powers the version history list on the download page.</p> 571 <div class="code-block"><div class="code-head"><span class="code-lang">bash</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>curl "https://theblackholebay.lol/api/releases"</code></div></div> 572 <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{ 573 "releases": [ 574 { 575 "id": "6b6e95cf579526b6", 576 "version": "1.1.0", 577 "name": "Stability & Performance Patch", 578 "notes": ["Fixed stalled downloads on resume"], 579 "channel": "stable", 580 "size": 2097152, 581 "sha256": "6ea73b45c3b229e3...", 582 "publishedAt": 1790594645585, 583 "fileName": "BlackHoleBaySetup1.1.0.exe", 584 "available": true, 585 "downloadUrl": "/api/releases/6b6e95cf579526b6/download" 586 } 587 ], 588 "latest": { "version": "1.1.0", "...": "..." } 589}</code></div></div> 590 <div class="note"><b>Beta releases</b> appear in <code>releases</code> for every client. Only <code>latest</code> respects the channel split, since this endpoint is for display.</div> 591 </div> 592 593 <div class="ep" id="release-download"> 594 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/releases/{id}/download</span></div> 595 <p>Streams the installer binary for a release. Supports <code>Range</code> requests, so interrupted downloads resume instead of restarting.</p> 596 <div class="code-block"><div class="code-head"><span class="code-lang">bash</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code># Full download 597curl -L "https://theblackholebay.lol/api/releases/6b6e95cf579526b6/download" -o app.exe 598 599# Resume an interrupted download 600curl -L -C - "https://theblackholebay.lol/api/releases/6b6e95cf579526b6/download" -o app.exe</code></div></div> 601 <table class="tbl"><tr><th>Header</th><th>Description</th></tr> 602 <tr><td>Content-Disposition</td><td>Forces a save using the original installer filename.</td></tr> 603 <tr><td>Accept-Ranges</td><td>Always <code>bytes</code>.</td></tr> 604 <tr><td>X-Release-Version</td><td>Version of the file being served.</td></tr></table> 605 <div class="note"><b>Status codes:</b> <code>200</code> full body, <code>206</code> partial (Range), <code>404</code> unknown id, <code>410</code> the record exists but the binary is gone from disk.</div> 606 <div class="warn"><b>Verify the checksum.</b> Each release carries a <code>sha256</code> computed server-side while the file was uploaded. Compare it against your downloaded file before executing the installer.</div> 607 </div> 608 <div class="ep" id="desktop-version"> 609 <div class="ep-head"><span class="method get">GET</span><span class="path">/api/desktop-version</span></div> 610 <p>Simple latest-version lookup kept for older clients. Prefer <code>/api/update</code> for new integrations, since it reports whether an update is actually needed.</p> 611 <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{ 612 "version": "1.1.0", 613 "name": "Stability & Performance Patch", 614 "url": "https://theblackholebay.lol/api/releases/6b6e95cf579526b6/download", 615 "notes": ["Fixed stalled downloads on resume"], 616 "sha256": "6ea73b45c3b229e3...", 617 "size": 2097152 618}</code></div></div> 619 <div class="warn"><b>Note:</b> <code>version</code> is <code>null</code> when no release has been published yet.</div> 620 </div> 621 622 <div class="ep" id="client-integration"> 623 <div class="section-h" style="font-size:17px;"><span class="bar"></span> Client Integration</div> 624 <p>Minimal update-check flow for the desktop app. Poll on launch, compare the returned version, then download and verify.</p> 625 <div class="code-block"><div class="code-head"><span class="code-lang">javascript</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>const crypto = require('crypto'); 626 627async function checkForUpdate(currentVersion, channel = 'stable') { 628 const res = await fetch( 629 'https://theblackholebay.lol/api/update?current=' + 630 encodeURIComponent(currentVersion) + '&channel=' + channel 631 ); 632 const data = await res.json(); // always HTTP 200 633 634 if (!data.updateAvailable || !data.latest) return { update: false }; 635 636 return { 637 update: true, 638 version: data.latest.version, 639 notes: data.latest.notes, // show these in the update prompt 640 url: 'https://theblackholebay.lol' + data.latest.downloadUrl, 641 sha256: data.latest.sha256, // verify after download 642 size: data.latest.size 643 }; 644}</code></div></div> 645 <div class="warn"><b>Verify before running.</b> Compute the SHA-256 of the downloaded file and compare it to <code>latest.sha256</code> before launching the installer. If they differ, discard the file and report a failed update.</div> 646 </div> 647 </div> 648 649 <!-- REFERENCE --> 650 <div class="section" id="images"> 651 <div class="section-h"><span class="bar"></span> Image URLs</div> 652 <table class="tbl"><tr><th>Source</th><th>Format</th></tr> 653 <tr><td>TMDB</td><td><code>https://image.tmdb.org/t/p/{size}{path}</code>
653 - sizes: w342, w500, w780, original</td></tr> 654 <tr><td>Local cache</td><td><code>https://theblackholebay.lol/upcoming/images/{id}.jpg</code></td></tr> 655 <tr><td>RAWG</td><td>Use <code>background_image</code> URL directly</td></tr> 656 <tr><td>User Avatars</td><td><code>https://theblackholebay.lol/uploads/avatars/{userId}.png</code></td></tr></table> 657 </div> 658 659 <div class="section" id="errors"> 660 <div class="section-h"><span class="bar"></span> Errors</div> 661 <table class="tbl"><tr><th>Status</th><th>Meaning</th><th>Response</th></tr> 662 <tr><td>400</td><td>Bad Request</td><td><code>{ "error": "Invalid request" }</code></td></tr> 663 <tr><td>401</td><td>Unauthorized</td><td><code>{ "error": "Unauthorized" }</code></td></tr> 664 <tr><td>403</td><td>IP blocked</td><td><code>Forbidden: IP banned.</code></td></tr> 665 <tr><td>404</td><td>Not found</td><td><code>{ "error": "Failed." }</code></td></tr> 666 <tr><td>409</td><td>Conflict</td><td><code>{ "error": "This torrent hash has already been uploaded." }</code></td></tr> 667 <tr><td>429</td><td>Rate limit exceeded</td><td><code>Too Many Requests.</code></td></tr> 668 <tr><td>500</td><td>Server error</td><td><code>{ "error": "Internal Server Error" }</code></td></tr></table> 669 </div> 670 671 <div class="section" id="code-examples"> 672 <div class="section-h"><span class="bar"></span> Code Examples</div> 673 <div class="ep"> 674 <h3>JavaScript</h3> 675 <div class="code-block"><div class="code-head"><span class="code-lang">javascript</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>const API = "https://theblackholebay.lol"; 676 677// Search torrents 678const res = await fetch(`${API}/api/search?q=oppenheimer&category=200`); 679const data = await res.json(); 680 681data.results.forEach(t => { 682 console.log(`${t.name} | ${t.sizeLabel} | S:${t.seeders} L:${t.leechers}`); 683}); 684 685// Login and upload 686const loginRes = await fetch(`${API}/api/auth/login`, { 687 method: 'POST', 688 headers: { 'Content-Type': 'application/json' }, 689 body: JSON.stringify({ username: 'user', password: 'pass' }) 690}); 691const { token } = await loginRes.json(); 692 693await fetch(`${API}/api/user/upload`, { 694 method: 'POST', 695 headers: { 'Content-Type': 'application/json', 'x-session-token': token }, 696 body: JSON.stringify({ name: 'My.Torrent', info_hash: 'abc123...', category: 200 }) 697});</code></div></div> 698 </div> 699 <div class="ep"> 700 <h3>Python</h3> 701 <div class="code-block"><div class="code-head"><span class="code-lang">python</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>import requests 702 703API = "https://theblackholebay.lol" 704 705response = requests.get(f"{API}/api/search", params={ 706 "q": "breaking bad", "category": 200, "page": 0 707}, timeout=10) 708 709for torrent in response.json()["results"]: 710 print(f"{torrent['name']} | {torrent['sizeLabel']}")</code></div></div> 711 </div> 712 <div class="ep"> 713 <h3>cURL</h3> 714 <div class="code-block"><div class="code-head"><span class="code-lang">bash</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code># Search 715curl "https://theblackholebay.lol/api/search?q=dune&category=200" 716 717# Trending movies 718curl "https://theblackholebay.lol/api/trending" 719 720# Trending games 721curl "https://theblackholebay.lol/api/trending_games" 722 723# Movie details 724curl "https://theblackholebay.lol/api/movie/872585"</code></div></div> 725 </div> 726 <div class="ep"> 727 <h3>Minimal API Client</h3> 728 <div class="code-block"><div class="code-head"><span class="code-lang">javascript</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>const API = "https://theblackholebay.lol"; 729 730const client = { 731 search: (q, cat = 0) => 732 fetch(`${API}/api/search?q=${q}&category=${cat}`).then(r => r.json()), 733 734 torrent: (id) => 735 fetch(`${API}/api/torrent/${id}`).then(r => r.json()), 736 737 files: (id) => 738 fetch(`${API}/api/files/${id}`).then(r => r.json()), 739 740 trendingMovies: () => 741 fetch(`${API}/api/trending`).then(r => r.json()), 742 743 trendingTV: () => 744 fetch(`${API}/api/trending_tv`).then(r => r.json()), 745 746 trendingGames: () => 747 fetch(`${API}/api/trending_games`).then(r => r.json()), 748 749 movie: (id) => 750 fetch(`${API}/api/movie/${id}`).then(r => r.json()), 751 752 tv: (id) => 753 fetch(`${API}/api/tv/${id}`).then(r => r.json()) 754};</code></div></div> 755 </div> 756 </div> 757 758 <div class="section" id="best-practices"> 759 <div class="section-h"><span class="bar"></span> Best Practices</div> 760 <div class="ep"> 761 <h3>Caching</h3> 762 <p>API responses use <code>Cache-Control: no-store</code>. For frequently-requested data (like trending movies), implement your own short-lived cache (30-60 seconds) to avoid hitting rate limits.</p> 763 </div> 764 <div class="ep"> 765 <h3>Error Handling</h3> 766 <div class="code-block"><div class="code-head"><span class="code-lang">javascript</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>switch (response.status) { 767 case 200: break; // Success 768 case 400: break; // Bad request
769 case 401: break; // Unauthorized 770 case 403: break; // IP blocked 771 case 404: break; // Not found 772 case 409: break; // Conflict (duplicate) 773 case 429: break; // Back off and retry 774 case 500: break; // Temporary failure 775}</code></div></div> 776 </div> 777 <div class="ep"> 778 <h3>Security</h3> 779 <p>Do not expose provider API keys in frontend applications. All keys are kept server-side. The recommended architecture is:</p> 780 <div class="code-block"><div class="code-head"><span class="code-lang">text</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>Frontend ? HTTPS ? Your App ? Server-side request ? The Black Hole Bay API</code></div></div> 781 </div> 782 </div> 783 </main> 784 </div> 785 786 <div class="footer"> 787 <p>The Black Hole Bay - API Reference</p> 788 <p style="margin-top:4px;">Last updated: October 7, 2026</p> 789 <p style="margin-top:8px;"><a href="/">Docs</a> - <a href="https://theblackholebay.lol/">Main Site</a> - <a href="https://theblackholebay.lol/#donations">Donations</a> - <a href="https://discord.gg/Qb2GQqW3Uq" target="_blank" rel="noopener noreferrer">Discord</a></p> 790 </div> 791 792
792<script> 793 function copyCode(btn){ 794 var code=btn.closest('.code-block').querySelector('code').textContent; 795 navigator.clipboard.writeText(code).then(function(){ 796 btn.textContent='Copied!';btn.classList.add('copied'); 797 setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000); 798 }); 799 } 800 801 // Scrollspy - highlight active sidebar link 802 var sections=document.querySelectorAll('section[id], .ep[id]'); 803 var navLinks=document.querySelectorAll('.sidebar a'); 804 var observer=new IntersectionObserver(function(entries){ 805 entries.forEach(function(entry){ 806 if(entry.isIntersecting){ 807 var id=entry.target.id; 808 navLinks.forEach(function(link){ 809 link.classList.toggle('active',link.getAttribute('href')==='#'+id); 810 }); 811 } 812 }); 813 },{rootMargin:'-80px 0px -70% 0px'}); 814 sections.forEach(function(s){observer.observe(s);}); 815 816 // Smooth scroll 817 navLinks.forEach(function(link){ 818 link.addEventListener('click',function(e){ 819 var href=this.getAttribute('href'); 820 if(href&&href.startsWith('#')){ 821 e.preventDefault(); 822 var target=document.querySelector(href); 823 if(target){target.scrollIntoView({behavior:'smooth'});document.body.classList.remove('sidebar-open');} 824 } 825 }); 826 }); 827 </script>
vendor: 1 bytes, line 827
827
828<script defer src="/js/site-footer.js"></script>
828<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v4bc70e2c01a94c73b74392e4234840661791215815920" integrity="sha512-L0ha0OXavK/8okipN9F8BtP84dg9DUhPERbBXzwI6dgTA55d2+yweo3pn5CSFYs45/r8md2+xvUPtTdvTNRfjA==" data-cf-beacon='{"version":"2024.11.0","token":"fed1a331b87d4dc7b361c56f56c914c2","r":1,"spa":2}' crossorigin="anonymous"></script>
828 829</body> 830</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.