Guía
1. Primeros pasos
AniRena es un índice de torrents enfocado en anime, manga, audio y medios relacionados. Puedes navegar y descargar torrents sin una cuenta. Se requiere una cuenta para subir torrents, publicar en grupos o usar la API.
La barra de navegación en la parte superior proporciona acceso a las áreas principales del sitio:
- Inicio — la página de listado y búsqueda de torrents.
- Subir — enviar un nuevo torrent (requiere inicio de sesión).
- Guía — esta página.
- Estadísticas — estadísticas de todo el sitio (torrents, peers, subidas en el tiempo).
- Grupos — directorio de grupos de lanzamiento.
- RSS — feed RSS de las últimas subidas, filtrable por categoría.
Tu menú de cuenta (esquina superior derecha cuando estás conectado) abre un panel de perfil donde puedes ajustar la configuración, gestionar opciones de seguridad y acceder a tu clave API.
2. Navegar y buscar
La página de inicio lista todos los torrents ordenados por fecha de subida. Usa la barra de búsqueda en la parte superior para filtrar resultados.
Búsqueda básica
Escribe cualquier palabra en la barra de búsqueda y presiona Enter (o haz clic en el ícono de búsqueda). Los resultados se clasifican por relevancia cuando hay una consulta activa.
Operadores de búsqueda
Los siguientes operadores pueden combinarse con una consulta regular:
| Operador | Ejemplo | Efecto |
|---|---|---|
user:"name" | user:"SubsPlease" | Mostrar solo torrents subidos por ese usuario. |
Hacer clic en el nombre de un cargador en la lista de torrents ejecuta automáticamente una búsqueda de usuario.
Categorías y subcategorías
Usa el selector de categoría (el ícono de cuadrícula junto a la barra de búsqueda) para restringir los resultados a una categoría.
- Anime
- Manga/Manhwa/Cómic
- Audio
- Literatura
- Acción en vivo
- Imágenes
- Software
- Hentai
- Otro
Cada categoría tiene subcategorías (ej. Anime a RAW, Sub/Audio, Video musical) seleccionables dentro del modal de categoría.
Ordenar y filtrar
Los encabezados de columna en la lista de torrents son clicables para ordenar por esa columna. Nota: los seeders y leechers son valores en tiempo real de Redis y no pueden usarse para ordenar.
Filtro de idioma
Usa el selector de idioma (ícono de bandera) para mostrar solo torrents etiquetados con un idioma específico.
Feed RSS
El feed RSS en /rss proporciona las últimas subidas. Agrega ?category=anime (o cualquier otro slug de categoría) para filtrar el feed.
3. Descargar torrents
Haz clic en cualquier nombre de torrent para abrir el panel de detalles. Desde allí puedes:
- Descargar .torrent — guarda el archivo .torrent directamente. La URL directa es
/torrents/{id}.torrent - Enlace Magnet — se abre directamente en tu cliente de torrents a través del protocolo URI magnet. La URL es
/torrents/{id}/magnet
El panel de detalles también muestra la descripción del torrent, la lista de archivos, la lista de trackers y los conteos de seeders/leechers.
Enlaces de descarga heredados
Los antiguos enlaces de descarga de AniRena aún son compatibles y redirigen automáticamente al archivo .torrent correcto usando el ID heredado: /dl/{old_id}
Clientes BitTorrent recomendados
Cualquier cliente BitTorrent moderno funciona. Los clientes a continuación son recomendados y soportan completamente BitTorrent v2 / torrents híbridos:
4. Crear una cuenta
Registro
Haz clic en Registrarse en la barra de navegación. Elige un nombre de usuario, proporciona una dirección de email y establece una contraseña.
Activación de email
Después de registrarte, se envía un email de verificación a tu dirección. Haz clic en el enlace del email para activar tu cuenta.
Recuperación de contraseña
Si olvidas tu contraseña, haz clic en Olvidaste tu contraseña en la página de inicio de sesión e ingresa tu dirección de email.
5. Subir torrents
Navega a Subir en la barra de navegación. Debes estar conectado con una cuenta activa y no baneada.
Pestaña Subir — enviar un archivo .torrent existente
Arrastra y suelta o selecciona un archivo .torrent. Una vez cargado, completa los campos:
| Campo | Obligatorio | Descripción |
|---|---|---|
| Archivo torrent | Sí | El archivo .torrent a subir. |
| Nombre | No | Reemplaza el nombre de visualización del torrent. |
| Categoría | Sí | La categoría de contenido (Anime, Manga, Audio, etc.). |
| Subcategoría | No | Un tipo más específico dentro de la categoría. |
| Idiomas | No | Una o más etiquetas de idioma que describen el idioma del contenido. |
| Grupo | No | Asocia este lanzamiento con un grupo del que seas miembro. |
| Descripción | No | Descripción en formato Markdown mostrada en la página de detalles (máx. 65535 caracteres). |
| Privado | No | Establece el indicador privado en el torrent, deshabilitando DHT/PEX. |
| URL de Announce | No | Reemplaza o agrega la URL de announce principal del tracker. |
| Trackers extra | No | Se lee desde el archivo torrent. No se puede modificar durante la subida — usa la pestaña Crear si quieres personalizar la lista de trackers. |
| Comentario | No | Reemplaza el campo de comentario del torrent incrustado en el archivo. |
Tu torrent debe incluir al menos una URL de tracker de AniRena en su lista de announce (en cualquier tier). El sitio comprueba esto al subir y rechazará torrents que no incluyan un tracker de AniRena. Si creaste el torrent sin añadir el tracker de AniRena primero, súbelo y luego vuelve a descargarlo del sitio — el archivo descargado tendrá los trackers correctos inyectados automáticamente.
Pestaña Crear — construir un nuevo torrent
La pestaña Crear te permite generar un nuevo .torrent desde cero especificando rutas de archivos, URLs de trackers y otros parámetros directamente en el navegador.
Moderación
Las subidas se verifican automáticamente contra una lista de patrones de contenido prohibido. Los torrents que coincidan con un patrón prohibido serán rechazados. Los torrents duplicados (mismo info hash) también se rechazarán.
6. Tu cuenta
Haz clic en tu nombre de usuario en la esquina superior derecha para abrir el panel de perfil.
Configuración
Cambia el tema de la interfaz, el tamaño de fuente, el esquema de colores, el idioma de la interfaz y las preferencias de visualización relacionadas con torrents.
Contraseña
Ingresa tu contraseña actual y la nueva contraseña dos veces. Se envía un código de verificación a tu dirección de email registrada.
Autenticación de dos factores (2FA)
Habilita la autenticación de dos factores basada en TOTP usando cualquier aplicación de autenticación. Al habilitar 2FA:
- Escanea el código QR (o ingresa el secreto manualmente) en tu aplicación de autenticación.
- Ingresa el código de 6 dígitos mostrado en tu app para confirmar la configuración.
- Guarda los códigos de recuperación mostrados — son códigos de un solo uso para recuperar el acceso.
Para deshabilitar el 2FA, ingresa tu código TOTP actual y confirma.
Sesiones activas
Ver todas las sesiones de inicio de sesión actualmente activas. Haz clic en Revocar en cualquier sesión que no reconozcas.
Clave API
Genera una clave API personal usada para subir torrents programáticamente a través de la API de AniRena.
Eliminar cuenta
Solicitar la eliminación de cuenta inicia un período de gracia de 30 días. Puedes cancelar la eliminación en cualquier momento dentro de ese período.
7. API de AniRena
AniRena proporciona una API JSON que te permite subir torrents programáticamente usando una clave API personal.
Autenticación
La API usa un flujo de autenticación en dos pasos. Primero, intercambia tu clave API permanente por un token portador de corta duración, luego pasa ese token en el encabezado Authorization de cada solicitud API.
Tu clave API está disponible en Tu cuenta > Clave API. Mantenla en secreto — cualquiera que la tenga puede obtener tokens portadores y subir en tu nombre. Si se compromete, revócala inmediatamente y genera una nueva.
Paso 1 — Obtener un token portador
/api/v1/auth/tokenEnvía una solicitud POST al endpoint del token con tu clave API en el encabezado Authorization. No se requiere cuerpo de solicitud.
Authorization: ApiKey <your-api-key>
Respuesta del token
{
"token": "<bearer-token>",
"token_type": "Bearer",
"expires_in": 3600
}Tiempo de vida del token
Los tokens Bearer permanecen válidos hasta 3600 segundos desde su emisión y pueden reutilizarse en cada llamada hasta que caduquen. Cuando un token caduca, genera uno nuevo mediante POST /api/v1/auth/token. Cada respuesta sigue devolviendo el token actual en la cabecera X-New-Token para compatibilidad con clientes existentes.
X-New-Token: <next-bearer-token>
Inicio de sesión en una sola petición (con 2FA)
/api/v1/auth/loginAutentíquese con su nombre de usuario o correo y contraseña en una sola petición y reciba directamente un token bearer. Si su cuenta tiene 2FA activado, incluya el código actual de su autenticador en totp_code (o un código de recuperación en recovery_code). Opcionalmente, ponga new_api_key en true para generar también una clave API permanente nueva en la misma respuesta.
Cuerpo de la solicitud
{
"login": "username or email",
"password": "your-password",
"totp_code": "123456", // obligatorio si 2FA está activado (6 dígitos)
"recovery_code": "", // alternativa a totp_code
"new_api_key": false // ponga true para generar también una nueva clave API
}Respuesta del token
{
"ok": true,
"token": "<bearer-token>",
"token_type": "Bearer",
"expires_in": 3600,
"api_key": "<new-api-key>" // solo presente cuando new_api_key era true
}El token bearer funciona exactamente igual que uno obtenido de /api/v1/auth/token. El campo api_key solo se devuelve cuando new_api_key es true — guárdelo de inmediato, ya que se muestra una sola vez y reemplaza cualquier clave anterior.
Ejemplo — iniciar sesión y (opcionalmente) obtener una nueva clave API
# pip install requests import requests BASE_URL = "https://www.anirena.com" # One request: authenticate (with 2FA if enabled) and get a bearer token. # Set new_api_key=True to also receive a brand-new permanent API key. resp = requests.post( f"{BASE_URL}/api/v1/auth/login", json={ "login": "your-username", # username or email "password": "your-password", "totp_code": "123456", # omit if 2FA is not enabled "new_api_key": True, # optional }, ) resp.raise_for_status() data = resp.json() token = data["token"] # use as: Authorization: Bearer <token> if "api_key" in data: print("New API key — store it now:", data["api_key"])
// Built-in fetch — requires Node.js 18+ const BASE_URL = "https://www.anirena.com"; // One request: authenticate (with 2FA if enabled) and get a bearer token. // Set new_api_key:true to also receive a brand-new permanent API key. const resp = await fetch(`${BASE_URL}/api/v1/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ login: "your-username", // username or email password: "your-password", totp_code: "123456", // omit if 2FA is not enabled new_api_key: true, // optional }), }); const data = await resp.json(); const token = data.token; // use as: Authorization: Bearer <token> if (data.api_key) console.log("New API key — store it now:", data.api_key);
// Requires: curl extension (enabled by default in PHP 8+) <?php define("BASE_URL", "https://www.anirena.com"); // One request: authenticate (with 2FA if enabled) and get a bearer token. // Set new_api_key => true to also receive a brand-new permanent API key. $ch = curl_init(BASE_URL . "/api/v1/auth/login"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode([ "login" => "your-username", // username or email "password" => "your-password", "totp_code" => "123456", // omit if 2FA is not enabled "new_api_key" => true, // optional ]), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Content-Type: application/json"], ]); $data = json_decode(curl_exec($ch), true); curl_close($ch); $token = $data["token"]; // use as: Authorization: Bearer <token> if (isset($data["api_key"])) echo "New API key — store it now: " . $data["api_key"];
// Cargo.toml: // serde_json = "1" // reqwest = { version = "0.12", features = ["blocking", "json"] } const BASE_URL: &str = "https://www.anirena.com"; fn main() { let client = reqwest::blocking::Client::new(); // One request: authenticate (with 2FA if enabled) and get a bearer token. // Set new_api_key:true to also receive a brand-new permanent API key. let data: serde_json::Value = client .post(format!("{BASE_URL}/api/v1/auth/login")) .json(&serde_json::json!({ "login": "your-username", // username or email "password": "your-password", "totp_code": "123456", // omit if 2FA is not enabled "new_api_key": true, // optional })) .send().unwrap() .json().unwrap(); let token = data["token"].as_str().unwrap(); // Authorization: Bearer <token> if let Some(key) = data["api_key"].as_str() { println!("New API key — store it now: {key}"); } }
Paso 2 — Subir un torrent
/api/v1/torrentsEnvía una solicitud POST JSON simple con el token portador en el encabezado Authorization.
Cuerpo de la solicitud
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
torrent | string | Sí | Contenido del archivo .torrent codificado en Base64. |
category | string | Sí | Slug de categoría: anime, manga, audio, literature, live, pictures, software, hentai, other. |
name | string | No | Reemplaza el nombre de visualización del torrent. |
sub_category | string | No | Slug de subcategoría (ej. raw, sub-audio). Debe pertenecer a la categoría elegida. |
languages | string[] | No | Array de códigos de idioma BCP 47 (ej. en, ja). |
group_id | string | No | UUID de un grupo del que seas miembro para asociar este lanzamiento. |
description | string | No | Descripción del lanzamiento en formato Markdown (máx. 65535 caracteres). |
comment | string | No | Reemplaza el campo de comentario del torrent incrustado. |
is_private | boolean | No | Establecer en true para habilitar el indicador privado en el torrent. |
comments_enabled | boolean | No | Permitir comentarios en este torrent. El valor predeterminado es true (habilitado). |
anime_id | string | No | UUID de una entrada de anime para vincular a este torrent. Obtenga el UUID mediante GET /api/v1/anime/search. Devuelve 400 si el UUID no coincide con ninguna entrada conocida. |
announce | string | No | Reemplaza o agrega la URL de announce principal. |
trackers | string | No | Lista separada por saltos de línea de URLs de trackers adicionales. |
test | boolean | No | Establece en true para realizar una ejecución de prueba: la solicitud se valida completamente pero el torrent no se guarda. Úsalo para verificar que tu carga útil es correcta antes de enviarla de verdad. |
Respuesta de éxito en ejecución de prueba — 200 OK
{
"ok": true,
"test": true,
"name": "My Torrent Title",
"info_hash_v1": "aabbccddeeff...",
"info_hash_v2": null
}Códigos de idioma disponibles
abaaafaksqamarar-001anhyasavaeayazbmbaeubebnbhbibsbrbgmyyuecachcenyzhzh-HKzh-Hanszh-SGzh-TWcucvkwcocrhrcsdadvnlnl-BEdzenen-USeoeteefofjfilfifrfr-CAffgllgkadede-ATelgnguhthahehzhihohuisioigidiaieiuikgaitjajvklknkrkskkkmkirwrnkvkgkokjkukylolalvlilnltlulbmkmgmsmlmtgvmimrmhmnnanvngnendsenonbnnocorojomospipsfaplptpt-BRpaqurormrusmsgsascgdsrsr-Latnsniisdsiskslsonrsteses-419es-MXsuswsssvtltytgtatttethbotitotstntrtktwukuruguzvevivowacyfywoxhyiyozazuEjemplo de solicitud
# pip install requests import base64, pathlib, requests API_KEY = "YOUR_API_KEY" BASE_URL = "https://www.anirena.com" # Step 1: exchange API key for a short-lived bearer token auth = requests.post( f"{BASE_URL}/api/v1/auth/token", headers={"Authorization": f"ApiKey {API_KEY}"}, ) auth.raise_for_status() token = auth.json()["token"] # Step 2: upload — plain JSON with the bearer token torrent_b64 = base64.b64encode(pathlib.Path("file.torrent").read_bytes()).decode() resp = requests.post( f"{BASE_URL}/api/v1/torrents", json={ "torrent": torrent_b64, "category": "anime", "sub_category": "raw", "languages": ["ja"], "description": "# My Release\n\nRelease notes here.", "is_private": False, }, headers={"Authorization": f"Bearer {token}"}, ) resp.raise_for_status() data = resp.json() token = resp.headers.get("X-New-Token", token) # save for next request print(data["id"], data["name"]) # torrent UUID and title
// Built-in modules only — requires Node.js 18+ (for global fetch) const fs = require("fs"); const API_KEY = "YOUR_API_KEY"; const BASE_URL = "https://www.anirena.com"; // Step 1: exchange API key for a short-lived bearer token let authResp = await fetch(`${BASE_URL}/api/v1/auth/token`, { method: "POST", headers: { Authorization: `ApiKey ${API_KEY}` }, }); let { token } = await authResp.json(); // Step 2: upload — plain JSON with the bearer token const torrentB64 = fs.readFileSync("file.torrent").toString("base64"); const resp = await fetch(`${BASE_URL}/api/v1/torrents`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, body: JSON.stringify({ torrent: torrentB64, category: "anime", sub_category: "raw", languages: ["ja"], description: "# My Release\n\nRelease notes here.", is_private: false, }), }); const data = await resp.json(); token = resp.headers.get("x-new-token") ?? token; // save for next request console.log(data.id, data.name); // torrent UUID and title
// Requires: curl extension (enabled by default in PHP 8+) <?php define("API_KEY", "YOUR_API_KEY"); define("BASE_URL", "https://www.anirena.com"); // Step 1: exchange API key for a short-lived bearer token $ch = curl_init(BASE_URL . "/api/v1/auth/token"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: ApiKey " . API_KEY], ]); $token = json_decode(curl_exec($ch), true)["token"]; curl_close($ch); // Step 2: upload — plain JSON with the bearer token $torrentB64 = base64_encode(file_get_contents("file.torrent")); $respHeaders = []; $ch = curl_init(BASE_URL . "/api/v1/torrents"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode([ "torrent" => $torrentB64, "category" => "anime", "sub_category" => "raw", "languages" => ["ja"], "description" => "# My Release\n\nRelease notes here.", "is_private" => false, ]), CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADERFUNCTION => function($ch, $h) use (&$respHeaders) { $respHeaders[] = $h; return strlen($h); }, CURLOPT_HTTPHEADER => [ "Content-Type: application/json", "Authorization: Bearer " . $token, ], ]); $data = json_decode(curl_exec($ch), true); curl_close($ch); foreach ($respHeaders as $h) { // save new token for next request if (stripos($h, "X-New-Token:") === 0) $token = trim(substr($h, strlen("X-New-Token:"))); } echo $data["id"] . " " . $data["name"]; // torrent UUID and title
// Cargo.toml: // base64 = "0.22" // serde_json = "1" // reqwest = { version = "0.12", features = ["blocking", "json"] } use base64::{engine::general_purpose::STANDARD as B64, Engine}; const API_KEY: &str = "YOUR_API_KEY"; const BASE_URL: &str = "https://www.anirena.com"; fn main() { let client = reqwest::blocking::Client::new(); // Step 1: exchange API key for a short-lived bearer token let auth: serde_json::Value = client .post(format!("{BASE_URL}/api/v1/auth/token")) .header("Authorization", format!("ApiKey {API_KEY}")) .send().unwrap().json().unwrap(); let mut token = auth["token"].as_str().unwrap().to_string(); // Step 2: upload — plain JSON with the bearer token let torrent_b64 = B64.encode(std::fs::read("file.torrent").unwrap()); let resp = client .post(format!("{BASE_URL}/api/v1/torrents")) .header("Authorization", format!("Bearer {token}")) .json(&serde_json::json!({ "torrent": torrent_b64, "category": "anime", "sub_category": "raw", "languages": ["ja"], "description": "# My Release\n\nRelease notes here.", "is_private": false })) .send().unwrap(); if let Some(t) = resp.headers().get("x-new-token") { token = t.to_str().unwrap().to_string(); // save for next request } let data: serde_json::Value = resp.json().unwrap(); println!("{} {}", data["id"], data["name"]); // torrent UUID and title }
Respuesta de éxito — 200 OK
{
"ok": true,
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "My Torrent Title",
"info_hash_v1": "aabbccddeeff...",
"info_hash_v2": null
}Respuestas de error
| Estado HTTP | Significado |
|---|---|
400 | Cuerpo de solicitud inválido o campo obligatorio faltante. |
401 | Token portador faltante, expirado o ya rotado. Vuelve a autenticarte mediante POST /api/v1/auth/token. |
403 | Cuenta baneada, deshabilitada o IP bloqueada. |
409 | Torrent duplicado — ya existe el mismo info hash. |
422 | El archivo torrent no pudo analizarse o falló la validación. |
429 | Límite de velocidad excedido. Reintenta después del reinicio de la ventana. |
503 | El sitio está en modo de mantenimiento o solo lectura. |
Limitación de velocidad
Las subidas de API están sujetas a un límite de velocidad configurable separado de la interfaz web. El límite es por clave API.
Generar archivos torrent con torrent-builder
torrent-builder es una herramienta CLI de código abierto construida sobre libtorrent-rasterbar que permite crear archivos .torrent de BitTorrent v1, v2 e hybrid desde la línea de comandos. Funciona perfectamente con la AniRena upload API — genera el archivo localmente y luego haz POST directamente al tracker. cantalupo555/torrent-builder.
Compilar desde el código fuente
Requiere CMake >= 3.28.3 y libtorrent-rasterbar >= 2.0.11. Clona el repositorio y compila con CMake:
# Install system dependencies sudo apt-get install build-essential cmake libtorrent-rasterbar-dev # Clone & build git clone https://github.com/cantalupo555/torrent-builder.git cd torrent-builder mkdir build && cd build cmake .. && cmake --build .
# Install dependencies via Homebrew brew install cmake libtorrent-rasterbar # Clone & build git clone https://github.com/cantalupo555/torrent-builder.git cd torrent-builder mkdir build && cd build cmake .. && cmake --build .
Indicadores principales
| Campo | Descripción |
|---|---|
--path | Ruta al archivo o directorio a empaquetar (requerido). |
--output | Nombre del archivo .torrent de salida (requerido). |
--version | Formato BitTorrent — 1 = v1, 2 = v2, 3 = hybrid (predeterminado: 3). |
--tracker | Agregar una URL de anuncio de tracker. Repite el indicador para agregar múltiples trackers. |
--comment | Incrustar una cadena de comentario de metadatos en el torrent. |
--private | Establecer el indicador privado para restringir la distribución solo a los trackers listados. |
--piece-size | Tamaño de pieza en KB (16-32768). Dejar sin establecer para selección automática. |
-i | Iniciar el modo de configuración interactivo paso a paso. |
Flujo de trabajo completo: compilar -> subir
Los ejemplos a continuación crean un torrent hybrid con torrent-builder, luego se autentican con la AniRena API y suben el resultado en un solo script.
# pip install requests import base64, subprocess, requests API_KEY = "YOUR_API_KEY" BASE_URL = "https://www.anirena.com" # Step 1: build the torrent with torrent-builder # --version 1=v1 2=v2 3=hybrid (default) subprocess.run([ "./torrent-builder/build/torrent_builder", "--path", "/data/my_release", "--output", "my_release.torrent", "--version", "3", # hybrid "--tracker", "udp://open.tracker.gg:6969/announce", "--comment", "My Release", "--creator", "--creation-date", ], check=True) # Step 2: authenticate token = requests.post( f"{BASE_URL}/api/v1/auth/token", headers={"Authorization": f"ApiKey {API_KEY}"}, ).json()["token"] # Step 3: upload torrent_b64 = base64.b64encode(open("my_release.torrent", "rb").read()).decode() resp = requests.post( f"{BASE_URL}/api/v1/torrents", json={ "torrent": torrent_b64, "category": "anime", "sub_category": "raw", "languages": ["ja"], "comments_enabled": True, }, headers={"Authorization": f"Bearer {token}"}, ) resp.raise_for_status() data = resp.json() print(data["id"], data["name"])
// Built-in modules — Node.js 18+ const fs = require("fs"); const { execFileSync } = require("child_process"); const API_KEY = "YOUR_API_KEY"; const BASE_URL = "https://www.anirena.com"; // Step 1: build the torrent (--version 1=v1, 2=v2, 3=hybrid) execFileSync("./torrent-builder/build/torrent_builder", [ "--path", "/data/my_release", "--output", "my_release.torrent", "--version", "3", "--tracker", "udp://open.tracker.gg:6969/announce", "--comment", "My Release", "--creator", "--creation-date", ]); // Step 2: authenticate let { token } = await (await fetch(`${BASE_URL}/api/v1/auth/token`, { method: "POST", headers: { Authorization: `ApiKey ${API_KEY}` }, })).json(); // Step 3: upload const torrentB64 = fs.readFileSync("my_release.torrent").toString("base64"); const resp = await fetch(`${BASE_URL}/api/v1/torrents`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, body: JSON.stringify({ torrent: torrentB64, category: "anime", sub_category: "raw", languages: ["ja"], comments_enabled: true, }), }); const data = await resp.json(); console.log(data.id, data.name);
// PHP 8+ with curl and proc_open <?php define("API_KEY", "YOUR_API_KEY"); define("BASE_URL", "https://www.anirena.com"); // Step 1: build the torrent (version: 1=v1, 2=v2, 3=hybrid) exec(implode(" ", array_map("escapeshellarg", [ "./torrent-builder/build/torrent_builder", "--path", "/data/my_release", "--output", "my_release.torrent", "--version", "3", "--tracker", "udp://open.tracker.gg:6969/announce", "--comment", "My Release", "--creator", "--creation-date", ]))); // Step 2: authenticate $ch = curl_init(BASE_URL . "/api/v1/auth/token"); curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: ApiKey " . API_KEY]]); $token = json_decode(curl_exec($ch), true)["token"]; curl_close($ch); // Step 3: upload $torrentB64 = base64_encode(file_get_contents("my_release.torrent")); $respHeaders = []; $ch = curl_init(BASE_URL . "/api/v1/torrents"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode([ "torrent" => $torrentB64, "category" => "anime", "sub_category" => "raw", "languages" => ["ja"], "comments_enabled" => true, ]), CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADERFUNCTION => function($ch, $h) use (&$respHeaders) { $respHeaders[] = $h; return strlen($h); }, CURLOPT_HTTPHEADER => ["Content-Type: application/json", "Authorization: Bearer " . $token], ]); $data = json_decode(curl_exec($ch), true); curl_close($ch); echo $data["id"] . " " . $data["name"];
// Cargo.toml: // base64 = "0.22" // serde_json = "1" // reqwest = { version = "0.12", features = ["blocking", "json"] } use base64::{engine::general_purpose::STANDARD as B64, Engine}; use std::process::Command; const API_KEY: &str = "YOUR_API_KEY"; const BASE_URL: &str = "https://www.anirena.com"; fn main() { // Step 1: build the torrent (--version 1=v1, 2=v2, 3=hybrid) Command::new("./torrent-builder/build/torrent_builder") .args([ "--path", "/data/my_release", "--output", "my_release.torrent", "--version", "3", "--tracker", "udp://open.tracker.gg:6969/announce", "--comment", "My Release", "--creator", "--creation-date", ]) .status().expect("torrent_builder failed"); let client = reqwest::blocking::Client::new(); // Step 2: authenticate let auth: serde_json::Value = client .post(format!("{BASE_URL}/api/v1/auth/token")) .header("Authorization", format!("ApiKey {API_KEY}")) .send().unwrap().json().unwrap(); let token = auth["token"].as_str().unwrap().to_string(); // Step 3: upload let torrent_b64 = B64.encode(std::fs::read("my_release.torrent").unwrap()); let resp: serde_json::Value = client .post(format!("{BASE_URL}/api/v1/torrents")) .header("Authorization", format!("Bearer {token}")) .json(&serde_json::json!({ "torrent": torrent_b64, "category": "anime", "sub_category": "raw", "languages": ["ja"], "comments_enabled": true, })) .send().unwrap().json().unwrap(); println!("{} {}", resp["id"], resp["name"]); }
Buscar metadatos de torrents
/api/v1/torrents/searchEnvía una solicitud POST JSON simple para recuperar listados de torrents con las mismas opciones de búsqueda y filtrado disponibles en el sitio web. El archivo .torrent en sí no se devuelve — usa la ruta de descarga normal para eso.
# pip install requests (token already obtained — see upload example) resp = requests.post( f"{BASE_URL}/api/v1/torrents/search", json={"q": "Sword Art Online", "category": "anime", "per_page": 25}, headers={"Authorization": f"Bearer {token}"}, ) resp.raise_for_status() data = resp.json() token = resp.headers.get("X-New-Token", token) # save for next request for t in data["torrents"]: print(t["title"], "-", t["magnet"])
// token already obtained — see upload example const resp = await fetch(`${BASE_URL}/api/v1/torrents/search`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, body: JSON.stringify({ q: "Sword Art Online", category: "anime", per_page: 25 }), }); const data = await resp.json(); token = resp.headers.get("x-new-token") ?? token; // save for next request data.torrents.forEach(t => console.log(t.title, "-", t.magnet));
// token already obtained — see upload example $respHeaders = []; $ch = curl_init(BASE_URL . "/api/v1/torrents/search"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode([ "q" => "Sword Art Online", "category" => "anime", "per_page" => 25 ]), CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADERFUNCTION => function($ch, $h) use (&$respHeaders) { $respHeaders[] = $h; return strlen($h); }, CURLOPT_HTTPHEADER => [ "Content-Type: application/json", "Authorization: Bearer " . $token, ], ]); $data = json_decode(curl_exec($ch), true); curl_close($ch); foreach ($respHeaders as $h) { // save new token for next request if (stripos($h, "X-New-Token:") === 0) $token = trim(substr($h, strlen("X-New-Token:"))); } foreach ($data["torrents"] as $t) { echo $t["title"] . " - " . $t["magnet"] . "\n"; }
// client and token already obtained — see upload example let resp = client .post(format!("{BASE_URL}/api/v1/torrents/search")) .header("Authorization", format!("Bearer {token}")) .json(&serde_json::json!({"q": "Sword Art Online", "category": "anime", "per_page": 25})) .send().unwrap(); if let Some(t) = resp.headers().get("x-new-token") { token = t.to_str().unwrap().to_string(); // save for next request } let data: serde_json::Value = resp.json().unwrap(); for t in data["torrents"].as_array().unwrap() { println!("{} - {}", t["title"], t["magnet"]); }
Parámetros de búsqueda
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
q | string | No | Búsqueda de texto libre. Soporta prefijos group:slug, group:"Name", user:name. |
category | string | No | Slug de categoría (ej. "anime"). |
sub_category | string | No | Slug de subcategoría (ej. "raw"). |
languages | string[] | No | Array de códigos de idioma BCP 47 (ej. en, ja). |
sort | string | No | Campo de ordenamiento: date (predeterminado), size, seeders, leechers, completed, title. |
order | string | No | Dirección de ordenamiento: desc (predeterminado) o asc. |
page | integer | No | Número de página, comenzando desde 1 (predeterminado 1). |
per_page | integer | No | Resultados por página, 1–250 (predeterminado 50). |
hide_adult | boolean | No | Excluir torrents de categoría adultos. Predeterminado true para usuarios regulares. |
show_dead | boolean | No | Cuando es false (predeterminado), se excluyen los torrents más antiguos que el periodo de gracia para torrents muertos que no tienen seeders activos. Establece en true para incluirlos. |
Respuesta
{
"total": 1234,
"page": 1,
"per_page": 50,
"total_pages": 25,
"from": 1,
"to": 50,
"torrents": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "My Release Title",
"info_hash_v1": "aabbccddeeff...",
"info_hash_v2": null,
"size_fmt": "1.4 GB",
"completed": 42,
"seeders": 10,
"leechers": 3,
"languages": ["ja", "en"],
"comment_count": 7,
"created_at": "2024-01-15 12:34",
"cat_slug": "anime",
"sub_slug": "raw",
"group_name": null,
"uploader": "username",
"magnet": "magnet:?xt=urn:btih:..."
}
]
}comment_count — Número de comentarios no eliminados en este torrent.
Limitación de velocidad de búsqueda
Las solicitudes de búsqueda están sujetas a un límite de velocidad configurable separado (predeterminado 60 solicitudes por 60 segundos por clave API). Exceder el límite devuelve 429 Too Many Requests. Las cuentas de staff están exentas.
Obtener detalles del torrent
/api/v1/torrent/{id}Recupera los metadatos completos de un torrent individual — incluidos los campos que el endpoint de búsqueda omite, como la descripción en Markdown, el comentario incrustado del archivo .torrent, la lista de archivos con el tamaño de cada uno y el diseño completo de niveles de trackers. Cuando están disponibles, los recuentos en vivo de seeders y leechers se leen del tracker.
Respuesta
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "My Release Title",
"info_hash_v1": "aabbccddeeff...",
"info_hash_v2": null,
"size_fmt": "1.4 GB",
"completed": 42,
"seeders": 10,
"leechers": 3,
"ext_seeders": 128,
"ext_leechers": 14,
"created_at": "2024-01-15 12:34",
"torrent_created": "2024-01-15 12:30",
"created_by_client": "mktorrent 1.1",
"cat_name": "Anime",
"cat_slug": "anime",
"sub_name": "Raw",
"sub_slug": "raw",
"group_name": null,
"group_slug": null,
"uploader": "username",
"uploader_id": "...",
"description": "# My Release\n\nRelease notes here.",
"comment": "",
"is_private": false,
"magnet": "magnet:?xt=urn:btih:...",
"languages": [
{ "code": "ja", "name": "Japanese", "country_code": "jp" }
],
"tracker_tiers": [
{ "tier": 0, "urls": ["udp://tracker.example.org:6969/announce"] }
],
"files": [
{ "path": "My Release/episode-01.mkv", "size": 1503238553 }
],
"comments_enabled": true,
"comments_locked": false,
"comment_count": 7
}seeders, leechers — Recuentos en vivo del tracker propio; ambos informan 0 cuando el almacén del tracker no tiene entrada para este info hash o no está accesible.
ext_seeders, ext_leechers — Máximo número de seeders y leechers reportado por cualquier tracker externo individual rastreado para este torrent. Los trackers que siguen el mismo swarm se superponen, por lo que se usa el máximo en lugar de la suma; ambos reportan 0 cuando ningún tracker tiene datos de scrape para este info hash.
Respuestas de error
| Estado HTTP | Significado |
|---|---|
400 | El id del torrent debe ser un UUID de 36 caracteres con guiones o una cadena hexadecimal pura de 32 caracteres. |
401 | Token portador faltante, expirado o ya rotado. Vuelve a autenticarte mediante POST /api/v1/auth/token. |
404 | Torrent no encontrado. |
429 | Límite de velocidad excedido. Reintenta después del reinicio de la ventana. |
503 | El sitio está en modo de mantenimiento o solo lectura. |
Obtener comentarios del torrent
/api/v1/torrents/{id}/commentsRecuperar comentarios paginados de un torrent. La cantidad de comentarios por página está controlada por la configuración COMMENT_PER_PAGE en el archivo .env del servidor (predeterminado 20). Solo los torrents con comentarios habilitados devolverán resultados — todos los demás devuelven 403.
Parámetros de consulta
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
page | integer | No | Número de página, comenzando en 1 (predeterminado 1). |
# pip install requests (token already obtained — see upload example) TORRENT_ID = "550e8400-e29b-41d4-a716-446655440000" resp = requests.get( f"{BASE_URL}/api/v1/torrents/{TORRENT_ID}/comments", params={"page": 1}, headers={"Authorization": f"Bearer {token}"}, ) resp.raise_for_status() data = resp.json() token = resp.headers.get("X-New-Token", token) # save for next request for c in data["comments"]: print(c["username"], "-", c["body"])
// token already obtained — see upload example const TORRENT_ID = "550e8400-e29b-41d4-a716-446655440000"; const resp = await fetch(`${BASE_URL}/api/v1/torrents/${TORRENT_ID}/comments?page=1`, { headers: { Authorization: `Bearer ${token}` }, }); const data = await resp.json(); token = resp.headers.get("x-new-token") ?? token; // save for next request data.comments.forEach(c => console.log(c.username, "-", c.body));
// token already obtained — see upload example $torrentId = "550e8400-e29b-41d4-a716-446655440000"; $respHeaders = []; $ch = curl_init(BASE_URL . "/api/v1/torrents/{$torrentId}/comments?page=1"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADERFUNCTION => function($ch, $h) use (&$respHeaders) { $respHeaders[] = $h; return strlen($h); }, CURLOPT_HTTPHEADER => ["Authorization: Bearer " . $token], ]); $data = json_decode(curl_exec($ch), true); curl_close($ch); foreach ($respHeaders as $h) { // save new token for next request if (stripos($h, "X-New-Token:") === 0) $token = trim(substr($h, strlen("X-New-Token:"))); } foreach ($data["comments"] as $c) { echo $c["username"] . " - " . $c["body"] . "\n"; }
// client and token already obtained — see upload example let torrent_id = "550e8400-e29b-41d4-a716-446655440000"; let resp = client .get(format!("{BASE_URL}/api/v1/torrents/{torrent_id}/comments?page=1")) .header("Authorization", format!("Bearer {token}")) .send().unwrap(); if let Some(t) = resp.headers().get("x-new-token") { token = t.to_str().unwrap().to_string(); // save for next request } let data: serde_json::Value = resp.json().unwrap(); for c in data["comments"].as_array().unwrap() { println!("{} - {}", c["username"], c["body"]); }
Respuesta
{
"torrent_id": "550e8400-e29b-41d4-a716-446655440000",
"page": 1,
"per_page": 20,
"total": 45,
"total_pages": 3,
"comments": [
{
"id": "...",
"user_id": "...",
"username": "uploader",
"role": "user",
"author_banned": false,
"body": "Great release!",
"created_at": "2024-01-15 12:34:00",
"edited_at": null,
"edited_by_username": null,
"deleted_at": null
}
]
}El campo body es una cadena vacía cuando el autor del comentario está baneado o el comentario ha sido eliminado. El indicador author_banned indica cuál es el caso.
Respuestas de error
| Estado HTTP | Significado |
|---|---|
401 | Token portador faltante, expirado o ya rotado. Vuelve a autenticarte mediante POST /api/v1/auth/token. |
403 | Los comentarios están deshabilitados para este torrent. |
404 | Torrent no encontrado. |
503 | El sitio está en modo de mantenimiento o solo lectura. |
Buscar entradas de anime
/api/v1/anime/search?q=<query>Busque entradas de anime por título para obtener su UUID. El UUID puede pasarse como anime_id en el cuerpo de carga para vincular un torrent a una entrada de anime en el momento de la carga, o usarse con PUT /api/torrents/{id}/anime después de la carga. No se requiere autenticación. Sujeto al mismo límite de frecuencia que la búsqueda de torrents (predeterminado 60 solicitudes por 60 segundos por IP).
# pip install requests (no authentication required)
resp = requests.get(
f"{BASE_URL}/api/v1/anime/search",
params={"q": "Sword Art Online", "page": 1, "per_page": 10},
)
resp.raise_for_status()
for item in resp.json()["results"]:
print(item["id"], "-", item["title"])// No authentication required
const resp = await fetch(
`${BASE_URL}/api/v1/anime/search?q=${encodeURIComponent("Sword Art Online")}&page=1&per_page=10`
);
const data = await resp.json();
data.results.forEach(item => console.log(item.id, "-", item.title));// No authentication required
$ch = curl_init(BASE_URL . "/api/v1/anime/search?" . http_build_query([
"q" => "Sword Art Online", "page" => 1, "per_page" => 10,
]));
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($data["results"] as $item) {
echo $item["id"] . " - " . $item["title"] . "\n";
}// No authentication required
let data: serde_json::Value = reqwest::blocking::get(
format!("{BASE_URL}/api/v1/anime/search?q=Sword+Art+Online&page=1&per_page=10")
).unwrap().json().unwrap();
for item in data["results"].as_array().unwrap() {
println!("{} - {}", item["id"], item["title"]);
}Parámetros de consulta
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
q | string | Sí | Cadena de búsqueda de título (requerida). Se compara con título y sinónimos. |
page | integer | No | Número de página, comenzando desde 1 (predeterminado 1). |
per_page | integer | No | Resultados por página, 1–50 (predeterminado 10). |
Respuesta
{
"total": 42,
"page": 1,
"per_page": 10,
"total_pages": 5,
"results": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"title": "Sword Art Online",
"anime_type": "TV",
"episodes": 25,
"status": "FINISHED",
"season": "FALL",
"season_year": 2012,
"picture": "https://cdn.myanimelist.net/images/anime/...",
"thumbnail": "https://cdn.myanimelist.net/images/anime/...",
"duration_secs": 1440
}
]
}Listar y recuperar grupos
/api/v1/groupsDevuelve una lista paginada de grupos públicos (habilitados y no bloqueados). Requiere autenticación con Bearer token.
/api/v1/groups/{id_or_slug}Devuelve un único grupo público por ID numérico o slug. Devuelve 404 si el grupo está deshabilitado o bloqueado.
Parámetros de consulta (solo lista)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
q | string | No | Filtrar por nombre de grupo (opcional, coincidencia de subcadena). |
page | integer | No | Número de página (predeterminado 1). |
per_page | integer | No | Resultados por página, 1–100 (predeterminado 20). |
sort | string | No | Columna de ordenamiento: name | slug | members | torrents | created (predeterminado name). |
order | string | No | Dirección de ordenamiento: asc o desc (predeterminado asc). |
Respuesta (lista)
{
"total": 12,
"page": 1,
"per_page": 20,
"total_pages": 1,
"groups": [
{
"id": 1,
"name": "SubsPlease",
"slug": "subsplease",
"subdomain_slug": "subsplease",
"description": "Weekly simulcast batches.",
"owner": "admin",
"member_count": 42,
"torrent_count": 1337,
"created_at": "2024-01-15 12:34"
}
]
}Respuesta (única)
{
"id": 1,
"name": "SubsPlease",
"slug": "subsplease",
"subdomain_slug": "subsplease",
"description": "Weekly simulcast batches.",
"owner": "admin",
"member_count": 42,
"torrent_count": 1337,
"created_at": "2024-01-15 12:34"
}Respuestas de error
| Estado HTTP | Significado |
|---|---|
401 | Token portador faltante, expirado o ya rotado. Vuelve a autenticarte mediante POST /api/v1/auth/token. |
404 | Grupo no encontrado o no accesible públicamente. |
429 | Límite de velocidad excedido. Reintenta después del reinicio de la ventana. |
503 | El sitio está en modo de mantenimiento o solo lectura. |
8. Donaciones
Si deseas apoyar a AniRena y ayudar a cubrir los costos de alojamiento de nuestros servidores y servicios, puedes enviar una donación a una de las siguientes carteras de criptomonedas:
bc1qy2h3ddq6ak5damvnf4r5vu3ydehhxrcq8gllwn0xCbaFe03832F95F86AF2536d52710e78C63b62Cd33ucetj2XDGHQg9PVRPMxerNi7c6kX7GJkjQNg9yjwGegLbpt61yX3RjGtB1Ef8vgVz6Hr6baQsTjVkCualquier donación, grande o pequeña, es muy apreciada y va directamente a mantener AniRena en funcionamiento. ¡Gracias por tu apoyo!
9. Software
AniRena Player es una aplicación de escritorio gratuita que te permite transmitir vídeo directamente desde los torrents indexados en este sitio, sin necesidad de esperar a que termine la descarga completa. Solo pega un enlace magnet o abre un archivo .torrent, y la reproducción comienza tan pronto como haya suficientes datos disponibles.
Ambas versiones son totalmente independientes: todas las dependencias están incluidas dentro del ejecutable. Sin instalador, sin entorno de ejecución que configurar — solo descarga y ejecuta.
Instalador (.exe). Se actualiza solo dentro de la app.
Imagen de disco (.dmg) para Mac con Apple Silicon (M1 y posteriores). Se actualiza solo dentro de la app.
Imagen de disco (.dmg) para Mac con Intel. Se actualiza solo dentro de la app.
Archivo único portátil, no requiere instalación. El único formato de Linux con actualización automática dentro de la app.
Instalación: sudo apt install ./<file>.deb — se actualiza vía apt o con una nueva descarga, no dentro de la app.
Instalación: sudo dnf install ./<file>.rpm — se actualiza vía dnf o con una nueva descarga, no dentro de la app.
Instala manualmente en dispositivos Android ARM de 64 bits (la mayoría de teléfonos / tabletas modernos). Se actualiza descargando un APK nuevo.
Versiones anteriores
Instala manualmente en dispositivos Android ARM de 32 bits (teléfonos / tabletas más antiguos). Se actualiza descargando un nuevo APK.