# TV Title Identifier Lookup for LLM Enrichment (`grit-77/tv-title-resolver`) Actor

TV title identifier lookup using TVmaze search. Export series candidates and identifiers as JSON for catalog linking; relevance scores are not match probabilities.

- **URL**: https://apify.com/grit-77/tv-title-resolver.md
- **Developed by:** [Grit](https://apify.com/grit-77) (community)
- **Categories:** AI, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.20 / 1,000 title candidates

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## TV Title Identifier Lookup for LLM Enrichment

Turn a TV series title into bounded TVmaze candidates and identifiers for catalog linking and retrieval-augmented generation (RAG).

Supply one title and receive candidate rows in TVmaze relevance order. Review the candidates before choosing an entity: a fuzzy search result is not a verified exact match, and `searchScore` is not a probability. Planet Earth can return the original series and its sequels in the same result list.

The actor calls the official [`/search/shows?q={title}` endpoint](https://www.tvmaze.com/api#show-search). It projects show metadata rather than exporting the full upstream response. It does not request cast, crew, people, biographies, synopsis, images, episodes or external websites. It makes no streaming availability claims and does not cover books, podcasts or app charts.

### Input

```json
{"query":"planet earth","maxItems":3}
```

| Input | Description |
|---|---|
| `query` | Required series title; whitespace is trimmed; 1-200 characters after trimming. |
| `maxItems` | Maximum dataset rows, including diagnostics. Default 3; integer from 1 to 10. |

The input form is prefilled with the example above. Concurrency is fixed at one official search request at a time; no API key, proxy or login is required for TVmaze.

### Returned fields

| Field | Meaning |
|---|---|
| `tvmazeId` | Numeric TVmaze show identifier. |
| `title` | Candidate series name from TVmaze. |
| `type` | Show type, such as Documentary. |
| `language` | Primary language, when provided. |
| `genres` | TVmaze genre labels; an empty array if absent. |
| `premiered` | Premiere date in `YYYY-MM-DD` form, or null. |
| `ended` | End date in `YYYY-MM-DD` form, or null. |
| `status` | TVmaze show production status. |
| `runtime` | Runtime in minutes, or null; no inferred average. |
| `imdbId` | IMDb title identifier supplied by TVmaze, or null. |
| `thetvdbId` | TheTVDB identifier supplied by TVmaze, or null. |
| `searchScore` | Original TVmaze relevance score, or null if invalid. |
| `sourceUrl` | TVmaze show URL for source attribution and manual review. |

Missing or invalid optional scalar metadata becomes null. Candidate identity requires a positive TVmaze ID and a non-empty title. The actor preserves the returned candidate order and does not promote the first row into an exact-match decision.

### Real output example

First candidate from the saved `sample_output.json` for `planet earth`:

```json
{
  "tvmazeId": 768,
  "title": "Planet Earth",
  "type": "Documentary",
  "language": "English",
  "genres": ["Nature"],
  "premiered": "2006-03-05",
  "ended": "2006-12-10",
  "status": "Ended",
  "runtime": 60,
  "imdbId": "tt0795176",
  "thetvdbId": 79257,
  "searchScore": 1.1842713,
  "sourceUrl": "https://www.tvmaze.com/shows/768/planet-earth"
}
```

Successful candidates have only the fields in the table. An upstream failure or malformed candidate produces an **uncharged diagnostic row** with an `error` object (`code`, `message`, and optional zero-based `candidateIndex`). These diagnostics count toward `maxItems`; consumers must exclude rows containing `error` from entity linking. No matches produces an empty dataset and a `no_candidates` run summary in the default key-value store's `OUTPUT` record. Invalid input fails the run with a field-specific message and writes no candidate.

### Pricing

Proposed `title_candidate` event: **$0.0002 per successful candidate** ($0.20 per 1,000 candidates). No candidate event is charged for errors, empty results, retries or skipped rows. Platform usage and platform-imposed fees may be separate. The owner must configure the event at publication; see [PRICING.md](PRICING.md) for current competitor receipts and billing limitations.

### Limits and source use

- One supplied title per run; at most 10 dataset rows. TVmaze may return fewer matches.
- Three total attempts for network errors, HTTP 429 and 5xx, with 1- and 2-second base backoff. `Retry-After` can increase the pause to a maximum of 30 seconds. Each HTTP request has a 15-second timeout. Redirects are not followed.
- Metadata is whatever the public search endpoint supplies. Missing identifiers, fuzzy matches and sequels require downstream review.
- TVmaze documents caching and rate limits in its [API documentation](https://www.tvmaze.com/api). Separate actor runs share the source's per-IP limits.
- TVmaze data is [licensed under CC BY-SA](https://www.tvmaze.com/api#licensing). Retain TVmaze attribution and comply with ShareAlike when reusing the data; keep `sourceUrl` with catalog or RAG records. This actor is not affiliated with TVmaze.

### Use with AI agents / MCP

An LLM agent can pass a title and a small `maxItems` cap through the Apify API or Apify MCP server. Read candidate rows from the default dataset; use title, dates, language and identifiers to choose the intended series. Preserve `sourceUrl` in downstream records and treat scores as ranking signals, not factual certainty. The actor makes no LLM calls and does not generate facts or summaries.

### Local development

Use the existing earn venv; no extra package installation is needed there. In this actor directory, save the input example as `storage/key_value_stores/default/INPUT.json`, then run in PowerShell:

```powershell
$env:APIFY_LOCAL_STORAGE_DIR = "$PWD/storage"
C:/GritWork/earn/.venv/Scripts/python -m src
C:/GritWork/earn/.venv/Scripts/python -m pytest -q
C:/GritWork/earn/.venv/Scripts/python -m pytest -m live -q
apify validate-schema
```

Dataset rows are persisted under `storage/datasets/default`. On the installed SDK, the `OUTPUT` record is stored as a JSON document without a filename extension. Normal local runs attempt the custom event but do not settle charges; the SDK's local test mode is used separately for the budget probe.

### FAQ

**Does it identify one exact series?** It returns candidates. The consuming application must choose the intended series using the available metadata.

**Does an IMDb ID mean IMDb was fetched?** No. Both external identifiers are copied from the official TVmaze response.

**Can I get cast, synopsis, posters or streaming services?** Those fields and requests are outside this actor's scope.

**What happens when TVmaze is unavailable?** Transient failures are retried within the stated bounds, then an uncharged `upstream_error` diagnostic is stored and the `OUTPUT` summary records that status.

**Are local tests proof of cloud billing?** They prove local behavior and SDK event attempts. Cloud settlement and owner-configured pricing need verification after publication.

# Actor input Schema

## `query` (type: `string`):

Non-empty TV series title, trimmed to 1-200 characters. Only the official TVmaze show-search endpoint is queried.

## `maxItems` (type: `integer`):

Caps all dataset rows, including uncharged candidate diagnostics. Preserves TVmaze relevance order. One search request is active at a time.

## Actor input object example

```json
{
  "query": "planet earth",
  "maxItems": 3
}
```

# Actor output Schema

## `dataset` (type: `string`):

Candidate records in TVmaze relevance order, or uncharged diagnostic rows when upstream data fails.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "query": "planet earth",
    "maxItems": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("grit-77/tv-title-resolver").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "query": "planet earth",
    "maxItems": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("grit-77/tv-title-resolver").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "query": "planet earth",
  "maxItems": 3
}' |
apify call grit-77/tv-title-resolver --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,grit-77/tv-title-resolver"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/EscnFeCaMZsAPiwhh/builds/9ffhD9JE03uyEohgA/openapi.json
