Skip to content

Search Subtitles

Search for subtitles using various filters including title, IMDb ID, year, and more.

Endpoint

GET /api/subtitles/search

Authentication

Authentication required - Include your API key in the Authorization header.

Description

The search endpoint is the primary way to find subtitles in the SubX database. It supports multiple search criteria that can be combined for precise results.

Query Parameters

At least one of query, title, imdb_id, or public_id is required.

Parameter Type Required Description
query string No* Free-text search across title and description
title string No* Exact or partial title match
imdb_id string No* IMDb ID (e.g., tt0773262)
public_id string No* Subtitle UUID
year integer No Movie release or show premiere year (1900-2100)
video_type string No Filter by movie or episode
language string No Language code (currently focused on Spanish)
limit integer No Results limit (1-200, default: 100)
season integer No Season number (≥ 1). Only valid when video_type=episode. Returns HTTP 422 if used with any other video_type or without it.
episode integer No Episode number (≥ 1). Only valid when video_type=episode. Returns HTTP 422 if used with any other video_type or without it.

* At least one of these fields is required.

An explicit year filters every supplied criterion. Matching persisted years and unknown (null) years are included; a different known year is excluded. Without an explicit parameter, a year recognized in query applies only to the branch produced by that query. Empty, malformed, or out-of-range values return HTTP 422. Search uses persisted data only and never calls an external metadata provider. Responses include nullable year, and cache keys use the fixed search:v3: prefix.

Request Examples

Search by Title

=== "cURL"

curl -X GET "https://subx-api.duckdns.org/api/subtitles/search?title=Dexter&limit=10" \
  -H "Authorization: Bearer {YOUR_API_KEY_HERE}"
import requests

headers = {"Authorization": "Bearer {YOUR_API_KEY_HERE}"}
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 := "{YOUR_API_KEY_HERE}"
    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", "{YOUR_API_KEY_HERE}");

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);

Search by IMDb ID

=== "cURL"

curl -X GET "https://subx-api.duckdns.org/api/subtitles/search?imdb_id=tt0773262&limit=20" \
  -H "Authorization: Bearer {YOUR_API_KEY_HERE}"
import requests

headers = {"Authorization": "Bearer {YOUR_API_KEY_HERE}"}
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']}")

Search with Multiple Filters

=== "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 {YOUR_API_KEY_HERE}"
import requests

headers = {"Authorization": "Bearer {YOUR_API_KEY_HERE}"}
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
)

The query parameter performs a broader search across titles and descriptions:

=== "cURL"

curl -X GET "https://subx-api.duckdns.org/api/subtitles/search?query=matrix%20reloaded" \
  -H "Authorization: Bearer {YOUR_API_KEY_HERE}"
import requests

headers = {"Authorization": "Bearer {YOUR_API_KEY_HERE}"}
params = {"query": "matrix reloaded"}

response = requests.get(
    "https://subx-api.duckdns.org/api/subtitles/search",
    headers=headers,
    params=params
)

Query vs Title

  • Use query for broad searches across multiple fields
  • Use title for exact or partial title matches
  • title is more precise, query is more flexible :::

Response

Success Response (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
}

Response Fields

Root Object

Field Type Description
items array Array of subtitle objects
total integer Total number of results

Subtitle Object

Field Type Description
id string (UUID) Unique subtitle identifier
video_type string Type: movie or episode
title string Title of the movie/show
season integer | null Season number (for episodes)
episode integer | null Episode number (for episodes)
year integer | null Movie release year or parent show's premiere year
imdb_id string | null IMDb identifier (e.g., tt0773262)
description string | null Subtitle description/release info
uploader_name string | null Original uploader username
posted_at string ISO 8601 timestamp
downloads integer Total download count

Status Codes

Code Description
200 Success - Results returned (may be empty array)
400 Bad Request - Missing or invalid parameters
401 Unauthorized - Invalid or missing API key
422 Unprocessable Entity - Invalid year, or season/episode used without video_type=episode
429 Too Many Requests - Rate limit exceeded
500 Internal Server Error

Error Responses

400 Bad Request - Missing Search Criteria

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

422 Unprocessable Entity - Invalid Year

{
  "detail": [
    {
      "loc": ["query", "year"],
      "msg": "ensure this value is greater than or equal to 1900",
      "type": "value_error"
    }
  ]
}

422 Unprocessable Entity - season/episode without video_type=episode

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

401 Unauthorized

{
  "detail": "Invalid authentication credentials"
}

429 Rate Limit Exceeded

When rate limited, the response includes headers to help you retry at the right time:

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"
}

Advanced Examples

Pagination Pattern

To implement pagination, use limit combined with multiple requests:

import requests

def search_all_subtitles(title, page_size=100):
    """Fetch all subtitles for a title using pagination."""
    headers = {"Authorization": "Bearer {YOUR_API_KEY_HERE}"}
    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")

Filter Episodes by Season

Use the season (and optionally episode) parameters together with video_type=episode to filter directly at the API level:

import requests

def get_season_subtitles(imdb_id, season_number):
    """Get all subtitles for a specific season using the season filter."""
    headers = {"Authorization": "Bearer {YOUR_API_KEY_HERE}"}
    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 {YOUR_API_KEY_HERE}"}
    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")

Constraint

season and episode are only valid when video_type=episode. Omitting video_type=episode or setting it to movie while passing season or episode returns HTTP 422.

Search with Retry Logic

import requests
import time

def search_with_retry(params, max_retries=3):
    """Search with automatic retry on failure."""
    headers = {"Authorization": "Bearer {YOUR_API_KEY_HERE}"}

    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:
                # Rate limited - use Retry-After header
                retry_after = int(response.headers.get("Retry-After", 60 * (attempt + 1)))
                print(f"Rate limited. Waiting {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})

Rate Limiting

The search endpoint has rate limiting to ensure fair usage. See the Rate Limits Guide for details.

Response Headers

All responses include rate limit headers:

Header Description
X-RateLimit-Limit Maximum requests allowed per window
X-RateLimit-Remaining Requests remaining in the current window
X-RateLimit-Reset Unix timestamp when the window resets
X-RateLimit-Window Window duration in seconds
Retry-After Seconds to wait before retrying (only on 429 responses)

Best practices: - Monitor X-RateLimit-Remaining to slow down before hitting the limit - Use the Retry-After header value on 429 responses instead of hardcoded delays - Cache search results when possible - Use specific search criteria to reduce result sets - Consider the limit parameter to reduce response size

Notes

  • Search results are sorted by relevance and recency
  • The query parameter triggers automatic reindexing jobs for new content
  • Empty results return {"items": [], "total": 0} (not an error)
  • IMDb IDs should include the tt prefix (e.g., tt0773262)
  • Season and episode numbers start at 1 (not 0)

Next Steps