Toru docs

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:

A few endpoints accept the API key in a query parameter for clients that can't carry headers:

Torrents

POST /v1/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": "..."
}
GET /v1/torrents

List the user's recent jobs (newest first). Query: limit (default 25, max 100), offset.

Response

{ "items": [Job, …], "limit": 25, "offset": 0 }
GET /v1/torrents/{id}

Fetch one job with its file list.

Response

{ ...Job, "files": [{ "id", "file_index", "name", "size_bytes",
                       "r2_key?", "external_url?", "mime_type?" }, …] }
DELETE /v1/torrents/{id}

Cancel an in-flight job. Idempotent — already-terminal jobs return 204.

POST /v1/torrents/{id}/sync

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)"
  }
}
GET /v1/torrents/{id}/files/{fileId}/stream

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" }
}
GET /v1/availability?hashes=h1,h2,…

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.

GET /v1/integrations

List the user's connected backends + statuses.

POST /v1/integrations

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
}
POST /v1/integrations/{kind}/test

Probe the upstream provider. Returns ok=true once the provider acknowledges the credential.

DELETE /v1/integrations/{kind}

Disconnect this backend.

API keys

GET /v1/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__"
}
POST /v1/api-keys/rotate

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.

GET /v1/rss-feeds

List the user's RSS feeds.

POST /v1/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
}
PATCH /v1/rss-feeds/{id}

Update fields. Only included fields are changed.

DELETE /v1/rss-feeds/{id}

Delete a feed.

POST /v1/rss-feeds/{id}/poll

Trigger a manual fetch. Re-uses the same parser/dedupe/insert path as the cron.

GET /v1/feed.rss?token=tu_…

OUTBOUND feed: the user's library as RSS for *Arr stacks and generic readers. Token authenticates the user (no Authorization header support).

Account & preferences

GET /v1/account

Plan + entitlement state from RevenueCat.

GET /v1/usage

Current-period usage counters.

GET /v1/preferences

User-tunable streaming preferences.

PATCH /v1/preferences

Partial update.

Errors

All non-2xx responses use the shape:

{ "error": "human-readable detail" }

Common status codes:

Compared to provider APIs

Toru's surface stays close to patterns Stremio addons + *Arr stacks already speak:

OperationReal-DebridPremiumizeTorBoxToru
Submit magnetPOST /torrents/addMagnetPOST /transfer/createPOST /torrents/createtorrentPOST /v1/torrents
Submit hosterPOST /unrestrict/linkPOST /transfer/createPOST /webdl/createwebdownloadPOST /v1/torrents { url }
Job statusGET /torrents/info/{id}GET /transfer/listGET /torrents/mylist?id=GET /v1/torrents/{id}
Stream URLPOST /unrestrict/linkGET item.linkGET /torrents/requestdlGET /v1/torrents/{id}/files/{fid}/stream
Cache checkGET /cache/checkGET /torrents/checkcachedGET /v1/availability?hashes=

Found a gap? File an issue.