Skip to content
Providers

Providers

Two providers ship today. They are tried in providers.order, left to right, for every item.

ProviderCredentialsNotes
opensubtitles.comAPI key + account username/passwordLargest catalogue. The API key alone allows searching; downloads need the login. Free accounts get a small daily download quota.
subdl.comAPI keyNo login step, more generous quota, smaller catalogue. Good fallback.

Getting credentials

opensubtitles.com — register a consumer at https://www.opensubtitles.com/en/consumers for the API key, then supply the username and password of the account itself. Both are needed: search works with the key, but every download is charged against the logged-in account.

subdl.com — grab a key from https://subdl.com/panel/api. That is the whole setup.

[providers]
order = ["opensubtitles", "subdl"]

[providers.opensubtitles]
enabled   = true
api_key   = ""     # or SUBTRACE_OPENSUBTITLES_API_KEY
username  = ""     # or SUBTRACE_OPENSUBTITLES_USERNAME
password  = ""     # or SUBTRACE_OPENSUBTITLES_PASSWORD
daily_cap = 100

[providers.subdl]
enabled   = true
api_key   = ""     # or SUBTRACE_SUBDL_API_KEY
daily_cap = 100

Every credential can come from the environment instead — see Environment variables.

How a provider is searched

Both providers are queried the same way: by external id when the path carried one (tmdb_id, or imdb_id), and by title plus year only as a fallback. For TV, the season and episode numbers are sent as filters — and results that come back with the wrong season/episode anyway are dropped rather than written to disk.

Fallback and quota

    flowchart TD
  A[item needs a subtitle] --> B{provider 1<br>quota left?}
  B -- no --> D
  B -- yes --> C[search]
  C -- ErrNotFound --> D{provider 2<br>quota left?}
  C -- ErrQuotaExceeded --> P[park provider<br>for the rest of the run] --> D
  C -- results --> R[rank, try top 3,<br>validate, write] --> Z[downloaded]
  D -- yes --> E[search] --> Z
  D -- no --> N[leave item pending]
  

The chain is driven by sentinel errors, not by strings:

ErrorConsequence
ErrNotFoundThis provider has nothing. Move to the next one.
ErrQuotaExceededAccount is out of downloads. Park the provider for the rest of the run and move on.
ErrRateLimitedGoing too fast; back off.
ErrNotConfiguredCredentials missing. Skipped quietly, so scan and --dry-run work on a fresh install with no accounts.

Providers missing credentials are still constructed, which is why a first run with no API keys reports items as still pending rather than failing.

Local quota enforcement

daily_cap is enforced by subtrace itself, from a per-day counter in the downloads table — not by asking the provider. A run therefore stops on its own terms instead of collecting rejections from the API.

Keep the cap at or below the account’s real quota. Today’s usage is visible in subtrace status:

downloads today
  opensubtitles   100 / 100
  subdl           37 / 100

The counter is keyed by UTC date, so it resets at midnight UTC regardless of the container’s TZ.

When both providers come up empty

The item is marked not_found, and becomes eligible again after run.retry_after_hours (default a week), up to run.max_attempts times. New subtitles do get uploaded over time, so the retry is not pointless — but for a file that has failed several times, the cause is usually the folder name rather than the catalogue. See Naming and matching.

Adding a provider

Implement provider.ProviderName, Search, Download, Quota — return the sentinel errors above, then register it. Details on Contributing.