Saltar a contenido

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 query para búsquedas amplias en múltiples campos
  • Usa title para coincidencias exactas o parciales de título
  • title es más preciso, query es 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

{
  "detail": "Provide at least one search criteria: query, title, imdb_id or public_id"
}

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

{
  "detail": "Parameters 'season' and 'episode' are only allowed when video_type=episode"
}

401 No Autorizado

{
  "detail": "Invalid authentication credentials"
}

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
{
  "detail": "Rate limit exceeded"
}

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 query dispara 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