> For the complete documentation index, see [llms.txt](https://wiki.crosswatch.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wiki.crosswatch.app/crosswatch/providers/synchronization/adapter-publicmetadb.md).

# Adapter: PublicMetaDB

PublicMetaDB adapter lets CrossWatch sync with PublicMetaDB. It supports watchlist, history, ratings, and progress.

{% hint style="info" %}
Connect PublicMetaDB first. Use: [PublicMetaDB (Authentication provider)](/crosswatch/providers/authentication/auth-trackers/auth-publicmetadb.md).
{% endhint %}

{% hint style="info" %}
PublicMetaDB uses request pacing, retry/backoff, and hourly rating quotas.

Defaults and tuning: [Provider rate limiting](/crosswatch/provider-rate-limiting.md).
{% endhint %}

### What it supports

* Direction: source or target in a pair (one-way or two-way)
* Features:
  * **Watchlist** (movies and shows)
  * **History** (movies and episodes)
  * **Ratings** (movies, shows, and episodes)
  * **Progress** (movies and episodes)
  * **Playlists** (not supported)
* Rating scale: CrossWatch **1–10**, converted to PublicMetaDB **0–100**
* Indexing: present-state snapshot (reads “what exists now”)

### How matching works

PublicMetaDB works best with TMDb IDs.

CrossWatch uses TMDb IDs to tell PublicMetaDB exactly which item changed.

For writes, CrossWatch usually needs:

* Movies: `ids.tmdb`
* Episodes: show `ids.tmdb` plus `season` and `episode`
* History: `watched_at`
* Progress: current position plus total duration

{% hint style="warning" %}
If an item has no TMDb ID, PublicMetaDB sync often can’t write it.

If an episode is missing show TMDb ID or episode numbers, CrossWatch marks it unresolved.
{% endhint %}

### Using PublicMetaDB in a pair

1. Open **Settings → Authentication** and connect PublicMetaDB.
2. Create or edit a pair.
3. Choose **PublicMetaDB** as the source, destination, or both.
4. Enable **Watchlist**, **History**, **Ratings**, or **Progress**.
5. Optionally set a pair-level watchlist name.
6. Save and run the pair.

Internal provider name: `PUBLICMETADB`.

### Watchlist behavior

* Supports movies and shows.
* Supports source, destination, or two-way sync.
* Uses a PublicMetaDB list as the destination container.
* Default list name: `Watchlist`.
* If the list does not exist, CrossWatch can create it automatically.

Created lists are private and marked as managed by CrossWatch.

Pair-level overrides can use a different list name for each pair.

Examples:

* `Movies to Watch`
* `Family Watchlist`
* `Anime Queue`

{% hint style="warning" %}
PublicMetaDB watchlist does not support seasons or individual episodes.
{% endhint %}

### History behavior

* Supports movies and episodes.
* Reads watched items from PublicMetaDB and can sync them elsewhere.
* Writes watched movies and watched episodes into PublicMetaDB.

History is event-based.

The same movie or episode can appear more than once if it was watched multiple times.

CrossWatch keeps those events distinct by including `watched_at` in the internal key.

### Ratings behavior

* Supports movies, shows, and episodes.
* Uses a CrossWatch `1–10` rating scale.
* Converts ratings before sending them to PublicMetaDB.

Examples:

* `8/10` → `80`
* `10/10` → `100`

Default label: `Overall`.

Power users can change the default with `publicmetadb.ratings_label`.

Ratings are label-aware.

A rating for the same item with a different label is treated as a separate rating.

### Progress behavior

* Supports movies and episodes.
* Syncs partial playback state such as resume position.
* Requires current position and total duration.

PublicMetaDB needs both values to store progress.

If duration is missing, CrossWatch can’t send the progress entry.

If PublicMetaDB reports an item as completed or ignored after a progress update, CrossWatch removes the local resume reference from shadow state.

Related: [Progress](/crosswatch/configure-pairs/features/progress.md).

### Settings and state (advanced)

<details>

<summary>Important config keys</summary>

Stored under `publicmetadb`.

Core settings:

* `api_key`
* `base_url`
* `timeout`
* `max_retries`

Watchlist:

* `watchlist_list_id`
* `watchlist_name`
* `watchlist_auto_create`
* `watchlist_page_size`

History and progress:

* `history_per_page`
* `history_max_pages`
* `progress_per_page`
* `progress_max_pages`

Ratings and pacing:

* `ratings_label`
* `ratings_submit_per_hour`
* `ratings_update_per_hour`
* `rate_limit.get_per_sec`
* `rate_limit.post_per_sec`

Defaults:

* `base_url`: `https://publicmetadb.com`
* `timeout`: `15.0`
* `max_retries`: `3`
* `ratings_label`: `Overall`
* `rate_limit.get_per_sec`: `20`
* `rate_limit.post_per_sec`: `3`
* `ratings_submit_per_hour`: `200`
* `ratings_update_per_hour`: `100`

</details>

<details>

<summary>Rate control layers</summary>

PublicMetaDB uses three layers:

* Request pacing for `GET` and non-`GET` calls
* Retry and backoff for `429` and temporary `5xx` errors
* Pair-scoped hourly rating quotas

`DELETE` shares the same pacing bucket as `POST`.

For `429`, CrossWatch honors `Retry-After` when PublicMetaDB sends it.

If a rating quota is reached, CrossWatch marks affected items unresolved and finishes the run.

</details>

<details>

<summary>Watchlist selection order</summary>

CrossWatch chooses the destination list in this order:

1. Use `publicmetadb.watchlist_list_id` if set.
2. Find a list matching `watchlist_name`.
3. Use the first list with type `watchlist`.
4. Fall back to common names like `crosswatch` or `watchlist`.
5. Create a private list if auto-create is enabled.

</details>

<details>

<summary>Local shadow and quota files</summary>

Stored under `/config/.cw_state/`.

Typical files:

* `publicmetadb_watchlist.<pair-scope>.shadow.json`
* `publicmetadb_history.<pair-scope>.shadow.json`
* `publicmetadb_progress.<pair-scope>.shadow.json`
* `publicmetadb_ratings.<pair-scope>.shadow.json`
* `publicmetadb_ratings.<pair-scope>.quota.json`

If no pair scope is available, CrossWatch uses `unscoped`.

Deletes usually need PublicMetaDB remote item IDs, not only TMDb IDs.

That makes the first sync or build-index pass important.

</details>

<details>

<summary>Common unresolved reasons</summary>

* `missing_tmdb_id` — item has no TMDb ID
* `missing_show_tmdb_or_episode_numbers` — episode is missing show TMDb ID, season, or episode
* `missing_watched_at` — history item has no watched timestamp
* `missing_duration` — progress item has no runtime or duration
* `missing_remote_item_id` — missing remote ID for watchlist remove
* `missing_remote_history_id` — missing remote ID for history remove
* `missing_remote_resume_id` — missing remote ID for progress remove
* `missing_remote_rating_id` — missing remote ID for rating remove
* `publicmetadb_hourly_rating_submit_limit` — local rating submit quota reached
* `publicmetadb_hourly_rating_update_limit` — local rating update quota reached

</details>

### Notes and limitations

* Watchlist sync does not support seasons or episodes.
* History sync does not support whole-show watched state.
* Progress sync does not support whole-show progress.
* Ratings sync depends on local shadow state for stable remove behavior.
* Reliable TMDb IDs are the main compatibility requirement.

Pair compatibility still depends on what the other provider supports.

Good pair examples:

* Plex → PublicMetaDB history
* PublicMetaDB → Plex watchlist
* Jellyfin → PublicMetaDB progress


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wiki.crosswatch.app/crosswatch/providers/synchronization/adapter-publicmetadb.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
