API Reference
Full endpoint documentation with parameters, responses, and code examples
Everything you need to start using the API.
Base URL
All API requests should be made to:
https://theblackholebay.lol/api. Example: https://theblackholebay.lol/api/search?q=duneAuthentication
No authentication is required for public search and metadata endpoints.
| Header | Required | Description |
|---|---|---|
| Authorization | No | Not required for public endpoints |
| x-session-token | Yes | Required for User endpoints (Upload, Rate, Profile Update) |
Rate Limiting
The API uses IP-based rate limiting.
| Setting | Value |
|---|---|
| Requests | 120 / minute / IP |
| Auth Limit | 5 / minute / IP (Login/Register) |
| Enforcement | Automatic |
| Exceeded | 429 Too Many Requests |
Response Format
All responses are JSON with the following headers:
| Header | Value |
|---|---|
| Content-Type | application/json; charset=utf-8 |
| Cache-Control | no-store |
Search torrents, get metadata, list files, and track downloads.
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 the expanse S01 search the show title and return matching-season releases, including season packs.
| Parameter | Required | Default | Description |
|---|---|---|---|
| q | No | "" | Search query. Empty = browse latest uploads |
| category | No | 0 | Category code (see below) |
Category codes: 0=All, 100=Audio, 200=Video, 300=Apps, 400=Games, 500=Adult, 600=Other
curl "https://theblackholebay.lol/api/search?q=breaking+bad&category=200"{
"query": "breaking bad",
"source": "remote_api+local+multi_indexer",
"results": [{
"id": "4034523",
"name": "Breaking.Bad.S01E01.1080p.BluRay.x265",
"info_hash": "abc123def456...",
"magnet": "magnet:?xt=urn:btih:abc123def456...",
"seeders": 245,
"leechers": 12,
"size": 1073741824,
"sizeLabel": "1.00 GiB",
"category": 200,
"added": 1700000000,
"username": "uploader123",
"status": "trusted",
"source": "TPB"
}]
}| Field | Type | Description |
|---|---|---|
| id | string | Unique torrent ID |
| name | string | Torrent title |
| info_hash | string | BitTorrent info hash |
| magnet | string | Full magnet URI with trackers |
| seeders | number | Active seeders |
| leechers | number | Active leechers |
| size | number | Size in bytes |
| sizeLabel | string | Human-readable size |
| added | number | Unix timestamp |
| status | string | Uploader status (trusted/vip) |
| source | string | Origin, such as TPBMirror, 1337x, YTS, DHT, or User_Upload |
source: "local_fallback".Returns full metadata for a single torrent.
curl "https://theblackholebay.lol/api/torrent/4034523"{ "torrent": { "id":"4034523", "name":"...", "magnet":"magnet:?...", "seeders":245, "sizeLabel":"1.00 GiB" } }Returns the list of files inside a torrent. For user-uploaded torrents, this is fetched dynamically via WebTorrent.
{
"files": [
{ "name": "video.mkv", "size": 1073741824, "sizeLabel": "1.00 GiB" },
{ "name": "subs.srt", "size": 45200, "sizeLabel": "44.14 KiB" }
]
}Increments the download counter. Call this when a user clicks a magnet link.
{ "ok": true }Register, login, upload torrents, and rate users.
Register a new user account. Returns a session token. acceptToS: true is required - accounts are only created when the Terms of Service and Privacy Policy are accepted.
// Request Body
{
"username": "newuser",
"password": "supersecretpassword",
"acceptToS": true
}
// Response 200
{
"success": true,
"token": "SESSION_TOKEN_HERE",
"user": { "id": "...", "username": "newuser", "rank": 1, "xp": 0 }
}Login to an existing account. Returns a session token.
// Request Body
{
"username": "existinguser",
"password": "supersecretpassword"
}
// Response 200
{
"success": true,
"token": "SESSION_TOKEN_HERE",
"user": { "id": "...", "username": "existinguser", "rank": 5, "xp": 1200 }
}Returns the currently logged-in user's private profile data. Requires x-session-token header.
curl -H "x-session-token: YOUR_TOKEN" "https://theblackholebay.lol/api/user/me"Returns public profile data, uploaded torrents, and rating info for a specific user.
// Response 200
{
"user": {
"id": "abc123",
"username": "Cybotage",
"avatarPath": "/uploads/avatars/abc123.jpg",
"bio": "Official uploader",
"rank": 10,
"badges": ["5 Uploads", "10 Uploads"]
},
"torrents": [ { "id": "idx_123", "name": "Movie.mkv" } ],
"avgRating": "4.8"
}Submit a new torrent to the index. Auto-approved. Requires x-session-token header.
// Request Body
{
"name": "My.Custom.Torrent.1080p",
"info_hash": "9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b", // 40 hex chars
"category": 200,
"description": "Optional description here"
}
// Response 200
{
"success": true,
"message": "Torrent uploaded successfully! (+50 XP)"
}409 Conflict.Rate another user (1-5 stars). You can rate a user only once per IP. Requires x-session-token header.
// Request Body
{
"toUserId": "abc123",
"score": 5
}
// Response 200
{
"success": true,
"newRank": 9
}Movie and TV metadata powered by TMDB.
Trending movies this week.
{
"results": [{
"id": 872585,
"title": "Oppenheimer",
"overview": "...",
"poster_path": "/abc.jpg",
"backdrop_path": "/def.jpg",
"vote_average": 8.4,
"release_date": "2024-01-15",
"original_language": "en"
}]
}Trending TV shows this week. Uses name instead of title and first_air_date instead of release_date.
Movies currently in theaters. Posters cached locally.
| Parameter | Default | Description |
|---|---|---|
| page | 1 | Page number (20 per page) |
poster_path returns a local path: /upcoming/images/{id}.jpg - use directly as https://theblackholebay.lol/upcoming/images/{id}.jpg. Movies older than 90 days are filtered out.Currently popular TV shows, filtered to the last 5 years.
| Parameter | Default | Description |
|---|---|---|
| page | 1 | Page number |
Cached movie lists. list: now_playing, popular, top_rated, upcoming. Poster art is downloaded once and served locally from /upcoming/images/.
Cached TV lists. list: popular, top_rated, on_the_air, airing_today.
Browse movies or TV by TMDB genre id (cached 6 hours). Without a genre id this returns the popular list.
Proxied movie/TV search - the TMDB key never leaves the server. Results are cached for 30 minutes.
Episode list for a TV season (cached 24 hours).
Submit a feature suggestion. Body: { title, category, description }. Rate limited (5/min per IP), 20KB body cap, honeypot field.
Submit a staff application from /apply. Body: { role, username, contact, timezone, availability, skills, experience, motivation, links }. Rate limited (3/min per IP).
Album artwork + artist lookup for the desktop music player (keyless iTunes-search proxy, 24h server-side cache, never errors - returns { artwork: null } on miss).
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.
Full movie details including cast, runtime, genres, and IMDb link.
{
"id": 872585,
"title": "Oppenheimer",
"runtime": 180,
"imdb_id": "tt15398776",
"genres": [{ "name": "Drama" }],
"credits": {
"cast": [{ "name": "Cillian Murphy", "character": "Oppenheimer" }]
}
}Full TV show details including cast, seasons, and episodes.
{
"name": "Show Name",
"number_of_seasons": 3,
"number_of_episodes": 24,
"episode_run_time": [55],
"status": "Returning Series"
}Game metadata powered by RAWG.
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.
{
"results": [{
"name": "Black Myth: Wukong",
"background_image": "https://media.rawg.io/media/games/xxx.jpg",
"released": "2024-08-20",
"rating": "85",
"genres": "Action, RPG",
"platforms": "PC, PlayStation 5"
}]
}| Field | Type | Description |
|---|---|---|
| name | string | Game name |
| background_image | string | Direct image URL - use as-is |
| released | string | Release date (YYYY-MM-DD) |
| rating | string? | Metacritic score or RAWG rating |
| genres | string | Comma-separated genre names |
| platforms | string | Comma-separated platform names |
Release feed and auto-update support for the BlackHole Bay desktop app. Releases are published from the admin panel under App Updates. These endpoints are public and stay reachable even while the site is in maintenance mode.
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.
curl "https://theblackholebay.lol/api/update?current=1.0.0&channel=stable"| Parameter | Required | Description |
|---|---|---|
| current | No | Version the client is currently running. Omit to just fetch the latest release. |
| channel | No | stable (default) or beta. Beta clients also receive stable builds; stable clients never see betas. |
{
"updateAvailable": true,
"current": "1.0.0",
"latest": {
"id": "6b6e95cf579526b6",
"version": "1.1.0",
"name": "Stability & Performance Patch",
"notes": [
"Fixed stalled downloads on resume",
"Improved streaming buffer handling"
],
"channel": "stable",
"size": 2097152,
"sha256": "6ea73b45c3b229e3...",
"publishedAt": 1790594645585,
"fileName": "BlackHoleBaySetup1.1.0.exe",
"available": true,
"downloadUrl": "/api/releases/6b6e95cf579526b6/download"
}
}| Field | Type | Description |
|---|---|---|
| updateAvailable | boolean | True when latest is newer than current. Always false when current is omitted. |
| current | string | Echo of the supplied current version. |
| latest | object? | The newest available release, or null if nothing is published. |
updateAvailable field instead.Returns every published release, newest first, plus the current stable build. Powers the version history list on the download page.
curl "https://theblackholebay.lol/api/releases"{
"releases": [
{
"id": "6b6e95cf579526b6",
"version": "1.1.0",
"name": "Stability & Performance Patch",
"notes": ["Fixed stalled downloads on resume"],
"channel": "stable",
"size": 2097152,
"sha256": "6ea73b45c3b229e3...",
"publishedAt": 1790594645585,
"fileName": "BlackHoleBaySetup1.1.0.exe",
"available": true,
"downloadUrl": "/api/releases/6b6e95cf579526b6/download"
}
],
"latest": { "version": "1.1.0", "...": "..." }
}releases for every client. Only latest respects the channel split, since this endpoint is for display.Streams the installer binary for a release. Supports Range requests, so interrupted downloads resume instead of restarting.
# Full download
curl -L "https://theblackholebay.lol/api/releases/6b6e95cf579526b6/download" -o app.exe
# Resume an interrupted download
curl -L -C - "https://theblackholebay.lol/api/releases/6b6e95cf579526b6/download" -o app.exe| Header | Description |
|---|---|
| Content-Disposition | Forces a save using the original installer filename. |
| Accept-Ranges | Always bytes. |
| X-Release-Version | Version of the file being served. |
200 full body, 206 partial (Range), 404 unknown id, 410 the record exists but the binary is gone from disk.sha256 computed server-side while the file was uploaded. Compare it against your downloaded file before executing the installer.Simple latest-version lookup kept for older clients. Prefer /api/update for new integrations, since it reports whether an update is actually needed.
{
"version": "1.1.0",
"name": "Stability & Performance Patch",
"url": "https://theblackholebay.lol/api/releases/6b6e95cf579526b6/download",
"notes": ["Fixed stalled downloads on resume"],
"sha256": "6ea73b45c3b229e3...",
"size": 2097152
}version is null when no release has been published yet.Minimal update-check flow for the desktop app. Poll on launch, compare the returned version, then download and verify.
const crypto = require('crypto');
async function checkForUpdate(currentVersion, channel = 'stable') {
const res = await fetch(
'https://theblackholebay.lol/api/update?current=' +
encodeURIComponent(currentVersion) + '&channel=' + channel
);
const data = await res.json(); // always HTTP 200
if (!data.updateAvailable || !data.latest) return { update: false };
return {
update: true,
version: data.latest.version,
notes: data.latest.notes, // show these in the update prompt
url: 'https://theblackholebay.lol' + data.latest.downloadUrl,
sha256: data.latest.sha256, // verify after download
size: data.latest.size
};
}latest.sha256 before launching the installer. If they differ, discard the file and report a failed update.| Source | Format |
|---|---|
| TMDB | https://image.tmdb.org/t/p/{size}{path} - sizes: w342, w500, w780, original |
| Local cache | https://theblackholebay.lol/upcoming/images/{id}.jpg |
| RAWG | Use background_image URL directly |
| User Avatars | https://theblackholebay.lol/uploads/avatars/{userId}.png |
| Status | Meaning | Response |
|---|---|---|
| 400 | Bad Request | { "error": "Invalid request" } |
| 401 | Unauthorized | { "error": "Unauthorized" } |
| 403 | IP blocked | Forbidden: IP banned. |
| 404 | Not found | { "error": "Failed." } |
| 409 | Conflict | { "error": "This torrent hash has already been uploaded." } |
| 429 | Rate limit exceeded | Too Many Requests. |
| 500 | Server error | { "error": "Internal Server Error" } |
JavaScript
const API = "https://theblackholebay.lol";
// Search torrents
const res = await fetch(`${API}/api/search?q=oppenheimer&category=200`);
const data = await res.json();
data.results.forEach(t => {
console.log(`${t.name} | ${t.sizeLabel} | S:${t.seeders} L:${t.leechers}`);
});
// Login and upload
const loginRes = await fetch(`${API}/api/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'user', password: 'pass' })
});
const { token } = await loginRes.json();
await fetch(`${API}/api/user/upload`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'x-session-token': token },
body: JSON.stringify({ name: 'My.Torrent', info_hash: 'abc123...', category: 200 })
});Python
import requests
API = "https://theblackholebay.lol"
response = requests.get(f"{API}/api/search", params={
"q": "breaking bad", "category": 200, "page": 0
}, timeout=10)
for torrent in response.json()["results"]:
print(f"{torrent['name']} | {torrent['sizeLabel']}")cURL
# Search
curl "https://theblackholebay.lol/api/search?q=dune&category=200"
# Trending movies
curl "https://theblackholebay.lol/api/trending"
# Trending games
curl "https://theblackholebay.lol/api/trending_games"
# Movie details
curl "https://theblackholebay.lol/api/movie/872585"Minimal API Client
const API = "https://theblackholebay.lol";
const client = {
search: (q, cat = 0) =>
fetch(`${API}/api/search?q=${q}&category=${cat}`).then(r => r.json()),
torrent: (id) =>
fetch(`${API}/api/torrent/${id}`).then(r => r.json()),
files: (id) =>
fetch(`${API}/api/files/${id}`).then(r => r.json()),
trendingMovies: () =>
fetch(`${API}/api/trending`).then(r => r.json()),
trendingTV: () =>
fetch(`${API}/api/trending_tv`).then(r => r.json()),
trendingGames: () =>
fetch(`${API}/api/trending_games`).then(r => r.json()),
movie: (id) =>
fetch(`${API}/api/movie/${id}`).then(r => r.json()),
tv: (id) =>
fetch(`${API}/api/tv/${id}`).then(r => r.json())
};Caching
API responses use Cache-Control: no-store. For frequently-requested data (like trending movies), implement your own short-lived cache (30-60 seconds) to avoid hitting rate limits.
Error Handling
switch (response.status) {
case 200: break; // Success
case 400: break; // Bad request
case 401: break; // Unauthorized
case 403: break; // IP blocked
case 404: break; // Not found
case 409: break; // Conflict (duplicate)
case 429: break; // Back off and retry
case 500: break; // Temporary failure
}Security
Do not expose provider API keys in frontend applications. All keys are kept server-side. The recommended architecture is:
Frontend ? HTTPS ? Your App ? Server-side request ? The Black Hole Bay API