Docs / API reference
API reference
REST + JSON. Bearer-auth all /v1/* endpoints with your tu_ key.
Authentication
Every request to /v1/* takes one of two credentials:
- API key:
Authorization: Bearer tu_…— long-lived, ideal for programmatic clients (Stremio, *Arr, scripts). - JWT:
Authorization: Bearer eyJ…— Supabase access token, used by the dashboard.
A few endpoints accept the API key in a query parameter for clients that can't carry headers:
GET /v1/feed.rss?token=tu_…(RSS readers)GET addon.toru.ravengrid.cc/<tu_…>/manifest.json(Stremio)
Torrents
Submit a magnet URI or hoster URL. Toru routes through your connected debrid backends first; if none have it cached, falls through to the shared cold-start cascade (Premiumize → Torrin → TorBox → Real-Debrid → AllDebrid).
Body
{ "magnet": "magnet:?xt=urn:btih:..." }
// or
{ "url": "https://hoster.example/file" }
Response
{
"id": "0e7e2aab-…",
"info_hash": "f65787e7…",
"status": "queued | cached_hit | downloading | uploading | ready | failed | cancelled",
"cached_in_store": "premiumize | torrin | torbox | realdebrid | alldebrid | rss | null",
"external_id": "",
"external_source": "",
"progress_pct": 42.5,
"created_at": "..."
}
List the user's recent jobs (newest first). Query: limit (default 25, max 100), offset.
Response
{ "items": [Job, …], "limit": 25, "offset": 0 }
Fetch one job with its file list.
Response
{ ...Job, "files": [{ "id", "file_index", "name", "size_bytes",
"r2_key?", "external_url?", "mime_type?" }, …] }
Cancel an in-flight job. Idempotent — already-terminal jobs return 204.
Force an inline status check against the upstream backend (bypasses the 10s poller cron). Useful for confirming the cascade is wired and for debugging.
Response
{
"job": Job,
"sync": {
"action": "marked_ready | marked_failed | progress_updated | no_change | error",
"backend": "premiumize | torrin | torbox | realdebrid | alldebrid",
"external_id": "...",
"note": "human-readable detail",
"reason": "(error path only)"
}
}
Mint a stream URL for the file. Resolver order: R2 cache → external (Torrin / cascade) → user debrid via StremThru. URLs expire in 15 min.
Response
{
"url": "https://...",
"expires_in_seconds": 900,
"source": "r2 | external | mediaflow | stremthru-proxy | upstream",
"backend": "realdebrid | alldebrid | premiumize | torbox | debridlink",
"file": { "name", "size", "mime_type" }
}
Bulk cache check across the user's connected debrid backends + the global Toru cache.
Integrations
Connect debrid backends + an optional stream proxy (MediaFlow or StremThru-as-proxy).
Tokens are AES-256-GCM encrypted at rest with the server's TOKEN_ENCRYPTION_KEY;
never returned in any response.
List the user's connected backends + statuses.
Create or update an integration.
Body
{
"kind": "realdebrid | alldebrid | premiumize | torbox | debridlink | torrin | mediaflow | stremthru-proxy",
"token": "...",
"label": "personal account", // optional
"config": { "base_url": "..." } // optional, kind-specific
}
Probe the upstream provider. Returns ok=true once the provider acknowledges the credential.
Disconnect this backend.
API keys
Fetch the user's API key. Auto-mints one on first call. Plaintext is decrypted from the at-rest ciphertext.
Response
{
"id", "key_prefix", "name",
"created_at", "last_used_at?", "revoked_at?",
"plaintext": "tu__"
}
Revoke the current key and mint a fresh one. Use when you suspect compromise.
RSS auto-grab
Paste an RSS feed URL — Toru polls it every poll_interval_seconds (≥300)
and submits new items via the same cascade.
List the user's RSS feeds.
Add a feed.
Body
{
"name": "1337x — TV",
"url": "https://...",
"filter_regex": "S0[1-9]E[0-9]{2}", // optional, RE2
"filter_quality": "1080p | 4k | any", // optional
"poll_interval_seconds": 900 // optional, min 300
}
Update fields. Only included fields are changed.
Delete a feed.
Trigger a manual fetch. Re-uses the same parser/dedupe/insert path as the cron.
OUTBOUND feed: the user's library as RSS for *Arr stacks and generic readers. Token authenticates the user (no Authorization header support).
Account & preferences
Plan + entitlement state from RevenueCat.
Current-period usage counters.
User-tunable streaming preferences.
Partial update.
Errors
All non-2xx responses use the shape:
{ "error": "human-readable detail" }
Common status codes:
400malformed body / invalid magnet / bad query parameter401missing or invalid Authorization402plan does not entitle this action404not found / not owned by this user409conflict (e.g. stream-URL on a not-yet-ready job)422request reached the system but couldn't be applied502upstream backend (debrid / Torrin) misbehaved
Compared to provider APIs
Toru's surface stays close to patterns Stremio addons + *Arr stacks already speak:
| Operation | Real-Debrid | Premiumize | TorBox | Toru |
|---|---|---|---|---|
| Submit magnet | POST /torrents/addMagnet | POST /transfer/create | POST /torrents/createtorrent | POST /v1/torrents |
| Submit hoster | POST /unrestrict/link | POST /transfer/create | POST /webdl/createwebdownload | POST /v1/torrents { url } |
| Job status | GET /torrents/info/{id} | GET /transfer/list | GET /torrents/mylist?id= | GET /v1/torrents/{id} |
| Stream URL | POST /unrestrict/link | GET item.link | GET /torrents/requestdl | GET /v1/torrents/{id}/files/{fid}/stream |
| Cache check | — | GET /cache/check | GET /torrents/checkcached | GET /v1/availability?hashes= |
Found a gap? File an issue.