# Multi-Debrid Resolve Patterns

Proven API patterns for Real-Debrid, AllDebrid, Premiumize, and TorBox magnet resolution. Each follows the same flow: add magnet → poll status → unrestrict/decode → return direct download URL.

> **Audited July 2026** against live API docs at api.real-debrid.com, docs.alldebrid.com, and premiumize.me/api. Cross-referenced against two active open-source implementations: torrentio-scraper (TheBeastLT) and Magnetio (peterdsp). Premiumize `/api/cache/check` uses POST (not GET) — confirmed by ALL sources. AllDebrid's `/v4/magnet/instant` is **undocumented** — has worked for years but could break without notice.

## Real-Debrid

**Strategy**: `instantAvailability` deprecated (error 37) since early 2025. All implementations now use: check existing torrents list → if not found, probe via addMagnet → selectFiles → poll → unrestrict.

**Reference behavior**: Both torrentio-scraper and Magnetio check the user's existing torrents list FIRST (`GET /torrents`), then probe only MAX_PROBE_CANDIDATES (5) unmatched hashes. This avoids adding unnecessary magnets for already-known content.

**Smart video selection**: After file selection, pick the largest video file rather than always using fileIdx 0. RD uses 1-based file indices.

```
POST https://api.real-debrid.com/rest/1.0/torrents/addMagnet
  Body: magnet=magnet:?xt=urn:btih:{hash}&dn=video
  Auth: Bearer {rdKey}
  → Returns { id: "tid" }

POST https://api.real-debrid.com/rest/1.0/torrents/selectFiles/{tid}
  Body: files=all
  → Selects all files

Poll GET https://api.real-debrid.com/rest/1.0/torrents/info/{tid}
  Every 1.5s, up to 10 iterations (15s max)
  When status === "downloaded" && links.length > 0:

POST https://api.real-debrid.com/rest/1.0/unrestrict/link
  Body: link={links[0]}
  → Returns { download, filename, filesize }
```

**Error codes**:
- Auth: 8 (bad token), 9 (account locked), 20 (permission denied)
- Content: 35 (infringing file), 36 (fair use limit)
- Limits: 21, 23, 26, 36 (rate limits)
- Too big: 29

**Status values**: `magnet_error`, `magnet_conversion`, `waiting_files_selection`, `queued`, `downloading`, `downloaded`, `error`, `virus`, `compressing`, `uploading`, `dead`.

Ready states: `downloaded`, `dead`. Opening states: `magnet_conversion`, `waiting_files_selection`. Error states: `error`, `magnet_error`, `virus`.

## AllDebrid

**Auth**: `Authorization: Bearer {adKey}` header is now recommended (Jan 2025). Legacy `?apikey=` query param still accepted. The `agent` parameter requirement was **removed** per the Jan 2025 changelog — still accepted for compatibility but no longer needed.

**Cache check** (both references use POST, not GET):

```bash
POST https://api.alldebrid.com/v4/magnet/instant
  Content-Type: application/x-www-form-urlencoded
  Body: agent=magnetio&apikey={adKey}&magnets[]=magnet:?xt=urn:btih:{hash}
  → If data.status === "success" && data.data.magnets[0].instant === true → cached
```

The commonly-used `/v4/magnet/instant` endpoint is **not documented** in the official AllDebrid API docs. It has worked for years across many projects (Comet, Debrid Media Manager, torrentio, Magnetio) but is unofficial.

**Official fallback**: Upload the magnet → check the `ready` field in the upload response:

```bash
POST https://api.alldebrid.com/v4/magnet/upload
  Body: magnets[]=magnet:?xt=urn:btih:{hash}
  Auth: Bearer {adKey}
  → Returns: { data: { magnets: [{ id, hash, ready: true|false, ... }] } }
```

If `ready: false`, poll `/v4.1/magnet/status` (note: v4.1, not v4):

```bash
Poll GET https://api.alldebrid.com/v4.1/magnet/status?agent={agent}&apikey={adKey}&id={magId}
  → AD status codes: 0-3=downloading, 4=Ready, 5-11=error
  When statusCode === 4 → ready
```

Then get files and unlock:

```bash
GET https://api.alldebrid.com/v4/magnet/files?agent={agent}&apikey={adKey}&id={magId}
  → Returns nested files array with { n, s, l } (name, size, link)

GET https://api.alldebrid.com/v4/link/unlock?agent={agent}&apikey={adKey}&link={target.l}
  → Returns { data: { link, filename } }
```

**Status codes**: 0=Queued, 1=Downloading, 2=Compressing, 3=Uploading, 4=Ready, 5=Download error, 6=Upload error, 7=Error, 8=Too big, 9=Internal error, 10=Canceled, 11=Deleted. Retryable errors: 5, 7, 9, 10, 11.

**Error codes**: `AUTH_BAD_APIKEY`, `AUTH_BLOCKED`, `AUTH_USER_BANNED`, `MUST_BE_PREMIUM`, `MAGNET_MUST_BE_PREMIUM`, `MAGNET_TOO_MANY`, `MAGNET_INVALID_URI`, `FREE_TRIAL_LIMIT_REACHED`.

**File structure**: AllDebrid returns nested folder entries. Flatten with:
```js
function flattenFiles(entries) {
  const result = [];
  for (const entry of entries) {
    if (Array.isArray(entry.e)) result.push(...flattenFiles(entry.e));
    result.push(entry);
  }
  return result;
}
```
Each file: `{ n: filename, s: size, l: link }`.

## Premiumize

**⛔ CORRECTION**: Both the official API docs AND both reference implementations (torrentio-scraper, Magnetio) use **POST** for `/api/cache/check`. Using GET is incorrect.

**Auth**: `Authorization: Bearer {pmKey}` header is now recommended (legacy: `?apikey=` query param).

**Parameter format**: Use `items[][src]` not `items[]`:

```bash
POST https://www.premiumize.me/api/cache/check
  Content-Type: application/x-www-form-urlencoded
  Body: apikey={pmKey}&items[][src]=magnet:?xt=urn:btih:{hash}
  → Returns: { status: "success", response: [true|false], filename: [...], filesize: [...] }
```

**Resolve** — try directdl first (instant if cached), fall back to create:

```bash
POST https://www.premiumize.me/api/transfer/directdl
  Body: apikey={pmKey}&src=magnet:?xt=urn:btih:{hash}
  → If cached: { status: "success", content: [{ link, stream_link, path, size }] }
  → If not cached: { status: "error" }
```

If directdl fails, create a transfer:
```bash
POST https://www.premiumize.me/api/transfer/create
  Body: apikey={pmKey}&src=magnet:?xt=urn:btih:{hash}
  → Returns { id, status }

Poll GET https://www.premiumize.me/api/transfer/list?apikey={pmKey}
  When transfer.status === "finished" || transfer.status === "seeding" → ready
```

**Transfer statuses**: `waiting`, `queued`, `running` (downloading), `finished`, `seeding` (ready), `deleted`, `error`, `timeout` (error).

**Output**: Content entries have `{ link, stream_link, path, size }`. Prefer `stream_link` over `link` for transcoded streaming, `link` for direct download. Smart video selection by largest size.

## TorBox

**⛔ CORRECTION**: checkcached uses **POST** with batch hashes array, not GET with single hash.

**Cache check** (POST, batch):

```bash
POST https://api.torbox.app/v1/api/torrents/checkcached
  Auth: Bearer {tbKey}
  Content-Type: application/json
  Body: { "hashes": ["hash1", "hash2", ...] }
  Query: ?format=list&list_files=true
  → Returns: { success: true, data: [{ hash, files: [{ id, short_name, name, size, opensubtitles_hash }] }] }
```

The response includes file details with `opensubtitles_hash` — useful for subtitle matching.

**Create torrent**:

```bash
POST https://api.torbox.app/v1/api/torrents/createtorrent
  Auth: Bearer {tbKey}
  Content-Type: application/json
  Body: { "magnet": "magnet:?xt=urn:btih:{hash}", "allow_zip": false, "seed": 1 }
  → Returns: { success: true, data: { torrent_id, queued_id } }
```

Handle `ACTIVE_LIMIT` error by freeing space: stop a seeding torrent or delete a downloading one.

**Poll**:

```bash
GET https://api.torbox.app/v1/api/torrents/mylist?id={torrentId}
  Auth: Bearer {tbKey}
  → Returns: { success: true, data: { id, hash, download_present, download_state, files, ... } }
```

**Ready check**: `torrent.download_present === true`. States: `completed` (ready), `error`, `dead` (error).

**Request download link**:

```bash
GET https://api.torbox.app/v1/api/torrents/requestdl?torrent_id={id}&file_id={fileId}&token={tbKey}&redirect=true
```

Note: uses `torrent_id` and `file_id` query params, NOT `id` and `token`.

**Control torrent** (for cleanup):

```bash
POST https://api.torbox.app/v1/api/torrents/controltorrent
  Auth: Bearer {tbKey}
  Body: { "torrent_id": id, "operation": "delete"|"stop_seeding" }
```

**Error codes**: `BAD_TOKEN`, `AUTH_ERROR`, `PLAN_RESTRICTED_FEATURE`, `MONTHLY_LIMIT`, `COOLDOWN_LIMIT`, `ACTIVE_LIMIT`, `DOWNLOAD_TOO_LARGE`.

## Auto-Provider Pattern

```js
const providers = [];
if (RD_API_KEY) providers.push(['RD', () => rdResolve(hash, RD_API_KEY)]);
if (AD_API_KEY) providers.push(['AD', () => adResolve(hash, AD_API_KEY)]);
if (PM_API_KEY) providers.push(['PM', () => pmResolve(hash, PM_API_KEY)]);
if (TB_API_KEY) providers.push(['TB', () => tbResolve(hash, TB_API_KEY)]);

for (const [name, fn] of providers) {
  try {
    const result = await fn();
    if (result) return result;
  } catch (err) { /* try next */ }
}
```

## Config Auto-Detection

Set env vars in `.env`, providers auto-activate:
- `RD_API_KEY` → Real-Debrid enabled
- `AD_API_KEY` → AllDebrid enabled (+ `AD_API_AGENT` for agent string)
- `PM_API_KEY` → Premiumize enabled
- `TB_API_KEY` → TorBox enabled
