Buscar Subtítulos¶
Busca subtítulos usando varios filtros incluyendo título, ID de IMDb, año y más.
Endpoint¶
GET /api/subtitles/search
Autenticación¶
✅ Autenticación requerida - Incluye tu clave de API en el header Authorization.
Descripción¶
El endpoint de búsqueda es la forma principal de encontrar subtítulos en la base de datos de SubX. Soporta múltiples criterios de búsqueda que pueden combinarse para obtener resultados precisos.
Parámetros de Query¶
Al menos uno de query, title, imdb_id, o public_id es requerido.
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
query |
string | No* | Búsqueda de texto libre en título y descripción |
title |
string | No* | Coincidencia exacta o parcial de título |
imdb_id |
string | No* | ID de IMDb (ej., tt0773262) |
public_id |
string | No* | UUID del subtítulo |
year |
integer | No | Año de estreno de película o de la serie principal (1900-2100) |
video_type |
string | No | Filtrar por movie o episode |
language |
string | No | Código de idioma (actualmente enfocado en español) |
limit |
integer | No | Límite de resultados (1-200, por defecto: 100) |
season |
integer | No | Número de temporada (≥ 1). Solo válido cuando video_type=episode. Retorna HTTP 422 si se usa con cualquier otro video_type o sin él. |
episode |
integer | No | Número de episodio (≥ 1). Solo válido cuando video_type=episode. Retorna HTTP 422 si se usa con cualquier otro video_type o sin él. |
* Al menos uno de estos campos es requerido.
Un year explícito filtra todos los criterios proporcionados. Se incluyen los años
persistidos coincidentes y los desconocidos (null); se excluye un año conocido
diferente. Sin parámetro explícito, un año reconocido en query se aplica solo a la
rama producida por ese query. Los valores vacíos, mal formados o fuera de rango
devuelven HTTP 422. La búsqueda usa datos persistidos y nunca llama a un proveedor
externo. Las respuestas incluyen year anulable y la caché usa search:v3:.
Ejemplos de Solicitud¶
Buscar por Título¶
=== "cURL"
curl -X GET "https://subx-api.duckdns.org/api/subtitles/search?title=Dexter&limit=10" \
-H "Authorization: Bearer {TU_CLAVE_API}"
import requests
headers = {"Authorization": "Bearer {TU_CLAVE_API}"}
params = {
"title": "Dexter",
"limit": 10
}
response = requests.get(
"https://subx-api.duckdns.org/api/subtitles/search",
headers=headers,
params=params
)
data = response.json()
print(f"Found {data['total']} subtitles")
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
)
func main() {
apiKey := "{TU_CLAVE_API}"
baseURL := "https://subx-api.duckdns.org/api/subtitles/search"
params := url.Values{}
params.Add("title", "Dexter")
params.Add("limit", "10")
req, _ := http.NewRequest("GET", baseURL+"?"+params.Encode(), nil)
req.Header.Set("Authorization", "Bearer "+apiKey)
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("Found %v subtitles\n", result["total"])
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "{TU_CLAVE_API}");
var url = "https://subx-api.duckdns.org/api/subtitles/search?title=Dexter&limit=10";
var response = await client.GetAsync(url);
var json = await response.Content.ReadAsStringAsync();
Console.WriteLine(json);
Buscar por ID de IMDb¶
=== "cURL"
curl -X GET "https://subx-api.duckdns.org/api/subtitles/search?imdb_id=tt0773262&limit=20" \
-H "Authorization: Bearer {TU_CLAVE_API}"
import requests
headers = {"Authorization": "Bearer {TU_CLAVE_API}"}
params = {
"imdb_id": "tt0773262", # Dexter
"limit": 20
}
response = requests.get(
"https://subx-api.duckdns.org/api/subtitles/search",
headers=headers,
params=params
)
data = response.json()
for subtitle in data['items']:
season = subtitle.get('season')
episode = subtitle.get('episode')
if season and episode:
print(f"S{season:02d}E{episode:02d} - {subtitle['title']}")
Buscar con Múltiples Filtros¶
=== "cURL"
curl -X GET "https://subx-api.duckdns.org/api/subtitles/search?title=Breaking%20Bad&year=2008&video_type=episode&limit=50" \
-H "Authorization: Bearer {TU_CLAVE_API}"
import requests
headers = {"Authorization": "Bearer {TU_CLAVE_API}"}
params = {
"title": "Breaking Bad",
"year": 2008,
"video_type": "episode",
"limit": 50
}
response = requests.get(
"https://subx-api.duckdns.org/api/subtitles/search",
headers=headers,
params=params
)
Búsqueda de Consulta de Texto Libre¶
El parámetro query realiza una búsqueda más amplia en títulos y descripciones:
=== "cURL"
curl -X GET "https://subx-api.duckdns.org/api/subtitles/search?query=matrix%20reloaded" \
-H "Authorization: Bearer {TU_CLAVE_API}"
import requests
headers = {"Authorization": "Bearer {TU_CLAVE_API}"}
params = {"query": "matrix reloaded"}
response = requests.get(
"https://subx-api.duckdns.org/api/subtitles/search",
headers=headers,
params=params
)
Query vs Title
- Usa
querypara búsquedas amplias en múltiples campos - Usa
titlepara coincidencias exactas o parciales de título titlees más preciso,queryes más flexible :::
Respuesta¶
Respuesta Exitosa (200 OK)¶
{
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"video_type": "episode",
"title": "Dexter",
"season": 1,
"episode": 1,
"year": 2006,
"imdb_id": "tt0773262",
"description": "Dexter S01E01 - Dexter",
"uploader_name": "user123",
"posted_at": "2024-01-15T10:30:00Z",
"downloads": 1250
},
{
"id": "660e9500-f39c-52e5-b827-557766551111",
"video_type": "episode",
"title": "Dexter",
"season": 1,
"episode": 2,
"year": null,
"imdb_id": "tt0773262",
"description": "Dexter S01E02 - Crocodile",
"uploader_name": "user456",
"posted_at": "2024-01-16T14:20:00Z",
"downloads": 980
}
],
"total": 2
}
Campos de Respuesta¶
Objeto Raíz¶
| Campo | Tipo | Descripción |
|---|---|---|
items |
array | Array de objetos de subtítulos |
total |
integer | Número total de resultados |
Objeto Subtítulo¶
| Campo | Tipo | Descripción |
|---|---|---|
id |
string (UUID) | Identificador único del subtítulo |
video_type |
string | Tipo: movie o episode |
title |
string | Título de la película/serie |
season |
integer | null | Número de temporada (para episodios) |
episode |
integer | null | Número de episodio (para episodios) |
year |
integer | null | Año de estreno de película o de la serie principal |
imdb_id |
string | null | Identificador de IMDb (ej., tt0773262) |
description |
string | null | Descripción del subtítulo/info de release |
uploader_name |
string | null | Nombre de usuario del subidor original |
posted_at |
string | Timestamp ISO 8601 |
downloads |
integer | Contador total de descargas |
Códigos de Estado¶
| Código | Descripción |
|---|---|
| 200 | Éxito - Resultados devueltos (puede ser un array vacío) |
| 400 | Solicitud Incorrecta - Parámetros faltantes o inválidos |
| 401 | No Autorizado - Clave de API inválida o faltante |
| 422 | Entidad No Procesable - Año inválido, o season/episode usados sin video_type=episode |
| 429 | Demasiadas Solicitudes - Límite de tasa excedido |
| 500 | Error Interno del Servidor |
Respuestas de Error¶
400 Solicitud Incorrecta - Criterios de Búsqueda Faltantes¶
422 Entidad No Procesable - Año Inválido¶
{
"detail": [
{
"loc": ["query", "year"],
"msg": "ensure this value is greater than or equal to 1900",
"type": "value_error"
}
]
}
422 Entidad No Procesable - season/episode sin video_type=episode¶
401 No Autorizado¶
429 Límite de Tasa Excedido¶
Cuando se excede el límite, la respuesta incluye headers para ayudarte a reintentar en el momento correcto:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1710700000
X-RateLimit-Window: 60
Retry-After: 45
Ejemplos Avanzados¶
Patrón de Paginación¶
Para implementar paginación, usa limit combinado con múltiples solicitudes:
import requests
def search_all_subtitles(title, page_size=100):
"""Fetch all subtitles for a title using pagination."""
headers = {"Authorization": "Bearer {TU_CLAVE_API}"}
all_subtitles = []
offset = 0
while True:
params = {
"title": title,
"limit": page_size
}
response = requests.get(
"https://subx-api.duckdns.org/api/subtitles/search",
headers=headers,
params=params
)
data = response.json()
items = data.get('items', [])
if not items:
break
all_subtitles.extend(items)
# If we got fewer results than the limit, we're done
if len(items) < page_size:
break
return all_subtitles
# Usage
subtitles = search_all_subtitles("Breaking Bad")
print(f"Found {len(subtitles)} total subtitles")
Filtrar Episodios por Temporada¶
Usa los parámetros season (y opcionalmente episode) junto con video_type=episode para filtrar directamente desde la API:
import requests
def get_season_subtitles(imdb_id, season_number):
"""Get all subtitles for a specific season using the season filter."""
headers = {"Authorization": "Bearer {TU_CLAVE_API}"}
params = {
"imdb_id": imdb_id,
"video_type": "episode",
"season": season_number,
"limit": 200,
}
response = requests.get(
"https://subx-api.duckdns.org/api/subtitles/search",
headers=headers,
params=params
)
data = response.json()
return data["items"]
# Get all Dexter Season 1 subtitles
season_1 = get_season_subtitles("tt0773262", 1)
print(f"Found {len(season_1)} subtitles for Season 1")
# Get a specific episode
def get_episode_subtitles(title, season_number, episode_number):
"""Get subtitles for a specific episode."""
headers = {"Authorization": "Bearer {TU_CLAVE_API}"}
params = {
"title": title,
"video_type": "episode",
"season": season_number,
"episode": episode_number,
"limit": 50,
}
response = requests.get(
"https://subx-api.duckdns.org/api/subtitles/search",
headers=headers,
params=params
)
return response.json()["items"]
# Get Breaking Bad S02E03 subtitles
ep_subs = get_episode_subtitles("Breaking Bad", 2, 3)
print(f"Found {len(ep_subs)} subtitles for S02E03")
Restricción
season y episode solo son válidos cuando video_type=episode. Omitir
video_type=episode o establecerlo en movie mientras se pasa season o
episode retorna HTTP 422.
Búsqueda con Lógica de Reintento¶
import requests
import time
def search_with_retry(params, max_retries=3):
"""Search with automatic retry on failure."""
headers = {"Authorization": "Bearer {TU_CLAVE_API}"}
for attempt in range(max_retries):
try:
response = requests.get(
"https://subx-api.duckdns.org/api/subtitles/search",
headers=headers,
params=params,
timeout=10
)
if response.status_code == 429:
# Límite de tasa - usar header Retry-After
retry_after = int(response.headers.get("Retry-After", 60 * (attempt + 1)))
print(f"Límite alcanzado. Esperando {retry_after}s...")
time.sleep(retry_after)
continue
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
raise
print(f"Attempt {attempt + 1} failed: {e}")
time.sleep(60 * (attempt + 1))
return None
# Usage
results = search_with_retry({"title": "Dexter", "limit": 10})
Límite de Tasa¶
El endpoint de búsqueda tiene límite de tasa para asegurar un uso justo. Consulta la Guía de Límites de Tasa para más detalles.
Headers de Respuesta¶
Todas las respuestas incluyen headers de rate limit:
| Header | Descripción |
|---|---|
X-RateLimit-Limit |
Máximo de solicitudes permitidas por ventana |
X-RateLimit-Remaining |
Solicitudes restantes en la ventana actual |
X-RateLimit-Reset |
Timestamp Unix cuando la ventana se reinicia |
X-RateLimit-Window |
Duración de la ventana en segundos |
Retry-After |
Segundos a esperar antes de reintentar (solo en respuestas 429) |
Mejores prácticas:
- Monitorea X-RateLimit-Remaining para reducir la velocidad antes de alcanzar el límite
- Usa el valor del header Retry-After en respuestas 429 en lugar de delays fijos
- Cachea los resultados de búsqueda cuando sea posible
- Usa criterios de búsqueda específicos para reducir conjuntos de resultados
- Considera el parámetro limit para reducir el tamaño de la respuesta
Notas¶
- Los resultados de búsqueda están ordenados por relevancia y recencia
- El parámetro
querydispara trabajos de reindexación automática para contenido nuevo - Los resultados vacíos devuelven
{"items": [], "total": 0}(no es un error) - Los IDs de IMDb deben incluir el prefijo
tt(ej.,tt0773262) - Los números de temporada y episodio comienzan en 1 (no 0)
Próximos Pasos¶
- Obtener Subtítulo - Recupera información detallada del subtítulo
- Descargar Subtítulo - Descarga archivos de subtítulos
- Límites de Tasa - Entender los límites de tasa