Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/rivenmedia/riven/llms.txt

Use this file to discover all available pages before exploring further.

Every piece of media tracked by Riven — whether a Movie, Show, Season, or Episode — carries a last_state field that records exactly where it sits in the processing pipeline. This state machine is the backbone of Riven’s automation: the event loop reads last_state to decide which service should handle an item next, and services write a new last_state when their work succeeds or fails.

The States Enum

States are defined in program/media/state.py as a Python Enum:
class States(Enum):
    Unknown            = "Unknown"
    Unreleased         = "Unreleased"
    Ongoing            = "Ongoing"
    Requested          = "Requested"
    Indexed            = "Indexed"
    Scraped            = "Scraped"
    Downloaded         = "Downloaded"
    Symlinked          = "Symlinked"
    Completed          = "Completed"
    PartiallyCompleted = "PartiallyCompleted"
    Failed             = "Failed"
    Paused             = "Paused"

State Definitions

Unknown

The item’s state cannot be determined — usually because it was just inserted into the database without enough context, or because a service returned an unexpected result. The event loop will attempt to re-process the item from the beginning on the next cycle.

Unreleased

The media has not been released yet. The indexer detected a future release date and will not attempt scraping until the release window arrives. Riven periodically re-checks unreleased items.

Ongoing

Applies to TV shows where new episodes are still airing. A show in Ongoing is decomposed into its constituent seasons and episodes, each of which progresses independently. The show itself returns to Ongoing (or advances to Completed) as episodes finish.

Requested

An item has been received from a content source (Overseerr, Plex Watchlist, Listrr, Mdblist, or Trakt) and is queued for the indexer. This is the entry point for all newly discovered media.

Indexed

Metadata has been fetched from TMDB or TVDB (title, year, genres, episode list, etc.). The item is now ready to be passed to the scraper.

Scraped

At least one suitable torrent was found by the scraper and scored by RTN. The winning infohash is recorded in active_stream. The item is queued for the downloader to send to the debrid provider.

Downloaded

The debrid provider has cached the torrent and reported a download link. The item is queued for the filesystem service to register it in RivenVFS.

Symlinked

The item has been registered in RivenVFS and is accessible on the host filesystem via the FUSE mount. Despite the name, no actual symlinks are created — the term is a legacy of an earlier architecture. The item is now queued for the updater to trigger a library scan.
The Symlinked state name is preserved for backwards compatibility. It means “the file is available in the virtual filesystem” rather than “a symlink exists on disk.”

Completed

The media server (Plex, Jellyfin, or Emby) has confirmed the item is in its library. Post-processing tasks run once at this point, and the item enters a steady resting state. Riven will not re-process it unless explicitly retried.

PartiallyCompleted

One or more child items (seasons or episodes) are Completed but the parent is not fully done yet. This state applies to Show and Season items and drives the scheduler to keep attempting the remaining incomplete children.

Failed

All scraping attempts have been exhausted (controlled by scraping.max_failed_attempts). The event loop will not automatically retry a Failed item. Use the retry API to re-queue it manually.
A Failed item is effectively frozen. The event loop’s process_event function returns immediately for any item in Failed or Paused state, so it will never advance on its own.

Paused

The item has been manually paused through the API. Like Failed, the event loop skips paused items completely. Use the unpause endpoint to resume normal processing.

State Transition Flow

The following diagram shows the normal happy-path progression and the branches that lead to Ongoing, PartiallyCompleted, Failed, and Paused:
                  ┌──────────────┐
  (new request)   │   Requested  │
─────────────────►│              │
                  └──────┬───────┘
                         │ IndexerService

                  ┌──────────────┐
                  │   Indexed    │
                  └──────┬───────┘
                         │ Scraping

              ┌──────────────────────┐
              │   Scraped            │◄──────────── (retry)
              └──────────┬───────────┘
                         │ Downloader

              ┌──────────────────────┐
              │   Downloaded         │
              └──────────┬───────────┘
                         │ FilesystemService

              ┌──────────────────────┐
              │   Symlinked          │
              └──────────┬───────────┘
                         │ Updater

              ┌──────────────────────┐
              │   Completed          │
              └──────────────────────┘

Special states (can occur at various stages):
  Unreleased ──► (wait for release date) ──► Indexed
  Ongoing    ──► per-season/episode loop ──► PartiallyCompleted ──► Completed
  Failed     ──► (manual retry required)
  Paused     ──► (manual unpause required)
The process_event function in program/state_transition.py encodes every transition:
elif existing_item.last_state in [States.Indexed, States.Unknown]:
    next_service = services.scraping
elif existing_item.last_state == States.Scraped:
    next_service = services.downloader
elif existing_item.last_state == States.Downloaded:
    next_service = services.filesystem
elif existing_item.last_state == States.Symlinked:
    next_service = services.updater
elif existing_item.last_state == States.Completed:
    next_service = services.post_processing

Checking Item States via the API

Filter items by state using the state query parameter on the items endpoint:
# All Failed items
GET /api/v1/items?state=Failed

# All items currently being scraped
GET /api/v1/items?state=Scraped

# Items that are PartiallyCompleted (useful for ongoing shows)
GET /api/v1/items?state=PartiallyCompleted
The response includes the item’s last_state, scraped_times, and other metadata that helps diagnose why an item is stuck.

Retrying Failed Items

To re-queue one or more failed (or otherwise stuck) items, send their IDs to the retry endpoint:
POST /api/v1/items/retry
Content-Type: application/json

{
  "ids": [42, 107, 318]
}
This resets scraped_at, scraped_times, and active_stream, then re-emits the items into the event queue starting from the Indexed state. You can also trigger a bulk retry of the entire library with:
POST /api/v1/items/retry_library
Before retrying, check scraped_times in the item detail. If it is already at max_failed_attempts, consider adjusting your scraper configuration or ranking thresholds so the next attempt has a better chance of succeeding.

Pausing and Unpausing Items

Items can be paused to prevent them from being processed without deleting them:
# Pause items
POST /api/v1/items/pause
Content-Type: application/json

{ "ids": [42, 107] }

# Unpause items (resumes from current state)
POST /api/v1/items/unpause
Content-Type: application/json

{ "ids": [42, 107] }
Pausing is useful during maintenance windows or when a specific item is known to have no available torrents and you want to prevent repeated retry cycles.

Build docs developers (and LLMs) love