Discover/Royal Road API
live

Royal Road APIroyalroad.com

Access Royal Road fiction data via API. Search by title, tags, and genre. Retrieve ratings, chapter lists, stats, and content warnings for any fiction.

This API takes change requests — .
Endpoints
3
Updated
1mo ago

What is the Royal Road API?

The Royal Road API exposes 3 endpoints for discovering and retrieving web fiction data from royalroad.com. Use search_fictions to query by title keyword, genre, or tag filters, list_fictions to pull ranked category feeds like best-rated or trending, and get_fiction to fetch a full detail record including five-dimension ratings, follower counts, chapter lists, and content warnings.

Try it
Page number for pagination.
Genre filter. Accepts: action, adventure, comedy, contemporary, drama, fantasy, historical, horror, mystery, psychological, romance, satire, sci_fi, short_story, thriller, tragedy.
Title or keyword to search for in fiction titles and descriptions.
Sort order for results. Accepts: relevance, popularity, rating, name, pages, readers, date.
Tag to include in results. Use snake_case tag identifiers such as: litrpg, summoned_hero, male_lead, female_lead, sci_fi, strong_lead, adventure, fantasy, reader_interactive, supernatural, magic, progression, action, gamelit, high_fantasy, deck_building, post_apocalyptic, low_fantasy, dungeon_crawler, urban_fantasy, contemporary, grimdark, mystery, survival, crafting, comedy, slice_of_life, local_protagonist, apocalypse, villainous_lead, multiple_lead, drama, romance, space_opera, psychological, first_contact, soft_sci-fi, strategy, harem, non-human_lead, secret_identity, attractive_lead, war_and_military, mythos, kingdom_building, technologically_engineered, artificial_intelligence, modern_knowledge, magitech, anti-hero_lead, super_heroes, horror, satire, system_invasion, school_life, martial_arts, historical, dystopia.
Tag to exclude from results. Uses the same tag identifiers as tags_add.
api.parse.bot/scraper/195f0514-c4a2-4100-87c0-84b79b177517/<endpoint>
Ready to send
Fill in the parameters and hit sign in to send to see live response data here.
Call it over HTTPgrab a free API key at signup
curl -X GET 'https://api.parse.bot/scraper/195f0514-c4a2-4100-87c0-84b79b177517/search_fictions' \
  -H 'X-API-Key: $PARSE_API_KEY'
Python SDK · recommended

Typed, relational, agent-ready

A generated client with real types, enums, and the links between objects — the structure a flat JSON response can't carry. Autocompletes in your editor and reads cleanly to coding agents.

  • Fully typed · autocompletes
  • Objects link to objects
  • Typed errors & pagination

Typed Python client. Set up the SDK in your uv project, then pull this API’s typed client:

uv add parse-sdk
uv run parse init
uv run parse add --marketplace royalroad-com-api

uv run parse add --marketplace pulls a pinned snapshot of this canonical API — it won’t change underneath you. To customize it, subscribe and swap to your own copy.

"""
Royal Road Fiction API Client
Get your API key from: https://parse.bot/settings
"""

import os
import requests
from typing import Optional, Dict, List, Any


class ParseClient:
    """Client for interacting with the Royal Road Fiction API via Parse."""

    def __init__(self, api_key: Optional[str] = None):
        """
        Initialize the Parse API client.

        Args:
            api_key: API key for authentication. If not provided, reads from PARSE_API_KEY env var.
        """
        self.base_url = "https://api.parse.bot"
        self.scraper_id = "195f0514-c4a2-4100-87c0-84b79b177517"
        self.api_key = api_key or os.getenv("PARSE_API_KEY")

        if not self.api_key:
            raise ValueError(
                "API key not provided. Set PARSE_API_KEY environment variable or pass api_key parameter."
            )

    def _call(
        self, endpoint: str, method: str = "POST", **params
    ) -> Dict[str, Any]:
        """
        Make a request to the Parse API.

        Args:
            endpoint: The endpoint name (e.g., 'search_fictions')
            method: HTTP method ('GET' or 'POST')
            **params: Query/body parameters for the request

        Returns:
            Parsed JSON response as dictionary

        Raises:
            requests.RequestException: If the API request fails
        """
        url = f"{self.base_url}/scraper/{self.scraper_id}/{endpoint}"
        headers = {"X-API-Key": self.api_key, "Content-Type": "application/json"}

        if method.upper() == "GET":
            response = requests.get(url, headers=headers, params=params)
        else:  # POST
            response = requests.post(url, headers=headers, json=params)

        response.raise_for_status()
        return response.json()

    def search_fictions(
        self,
        title: Optional[str] = None,
        tags_add: Optional[str] = None,
        tags_remove: Optional[str] = None,
        genre: Optional[str] = None,
        order_by: Optional[str] = None,
        page: int = 1,
    ) -> Dict[str, Any]:
        """
        Search fictions by title, tags, and genre.

        Args:
            title: Title or keyword to search for
            tags_add: Tag to include in results (snake_case)
            tags_remove: Tag to exclude from results (snake_case)
            genre: Genre filter (e.g., 'fantasy', 'sci_fi', 'action')
            order_by: Sort order ('relevance', 'popularity', 'rating', 'name', 'pages', 'readers', 'date')
            page: Page number for pagination (default: 1)

        Returns:
            Dictionary with 'results', 'page', 'total_pages', 'count'
        """
        params = {"page": str(page)}

        if title:
            params["title"] = title
        if tags_add:
            params["tags_add"] = tags_add
        if tags_remove:
            params["tags_remove"] = tags_remove
        if genre:
            params["genre"] = genre
        if order_by:
            params["order_by"] = order_by

        return self._call("search_fictions", method="GET", **params)

    def get_fiction(self, fiction_id: str) -> Dict[str, Any]:
        """
        Get detailed information about a specific fiction.

        Args:
            fiction_id: Numeric fiction ID from Royal Road (e.g., '8300')

        Returns:
            Dictionary with fiction details including description, ratings, chapters, etc.
        """
        return self._call("get_fiction", method="GET", fiction_id=fiction_id)

    def list_fictions(
        self,
        category: str = "trending",
        page: int = 1,
    ) -> Dict[str, Any]:
        """
        List fictions from a specific category/ranking page.

        Args:
            category: Category to list. One of: 'best-rated', 'trending', 'active-popular',
                     'complete', 'weekly-popular', 'latest-updates', 'new', 'rising-stars', 'writathon'
            page: Page number for pagination (default: 1)

        Returns:
            Dictionary with 'results', 'page', 'total_pages', 'count', 'category'
        """
        return self._call(
            "list_fictions", method="GET", category=category, page=str(page)
        )


def main():
    """Practical workflow example: Find LitRPG fictions and show details."""

    # Initialize the client
    client = ParseClient()

    print("=" * 70)
    print("Royal Road Fiction API - Practical Example")
    print("=" * 70)

    # Step 1: Search for LitRPG fictions with progression tag
    print("\n📚 Searching for LitRPG fictions with progression...")
    search_results = client.search_fictions(
        tags_add="litrpg",
        tags_add="progression" if False else None,  # Tags search example
        order_by="rating",
        page=1,
    )

    print(f"Found {search_results['count']} results (Page {search_results['page']} of {search_results['total_pages']})")

    if not search_results["results"]:
        print("No results found.")
        return

    # Step 2: Show top 3 results summary
    print("\n📖 Top Results:")
    top_fictions = search_results["results"][:3]

    for idx, fiction in enumerate(top_fictions, 1):
        print(f"\n{idx}. {fiction['title']}")
        print(f"   Rating: ⭐ {fiction['rating']}")
        print(f"   Followers: {fiction['followers']:,}")
        print(f"   Chapters: {fiction['chapters']}")
        print(f"   Views: {fiction['views']:,}")
        print(f"   ID: {fiction['fiction_id']}")

    # Step 3: Get detailed information for the top result
    if top_fictions:
        top_fiction_id = top_fictions[0]["fiction_id"]
        print(f"\n\n🔍 Fetching detailed info for '{top_fictions[0]['title']}'...")

        detailed_info = client.get_fiction(top_fiction_id)

        print(f"\n{'='*70}")
        print(f"Title: {detailed_info['title']}")
        print(f"Author: {detailed_info['author']}")
        print(f"Description: {detailed_info['description'][:200]}...")

        # Show ratings breakdown
        ratings = detailed_info["ratings"]
        print(f"\nRatings Breakdown:")
        print(f"  Overall: {ratings['overall_score']}")
        print(f"  Story: {ratings['story_score']}")
        print(f"  Style: {ratings['style_score']}")
        print(f"  Grammar: {ratings['grammar_score']}")
        print(f"  Character: {ratings['character_score']}")

        # Show content warnings
        if detailed_info["warnings"]:
            print(f"\n⚠️  Content Warnings:")
            for warning in detailed_info["warnings"]:
                print(f"  - {warning}")

        # Show tags
        print(f"\nTags: {', '.join(detailed_info['tags'])}")

        # Show recent chapters
        print(f"\nRecent Chapters ({len(detailed_info['chapters'])} total):")
        for chapter in detailed_info["chapters"][:3]:
            print(f"  - {chapter['title']} ({chapter['date'][:10]})")

    # Step 4: List best-rated fictions
    print(f"\n\n{'='*70}")
    print("📊 Browsing best-rated fictions...")
    best_rated = client.list_fictions(category="best-rated", page=1)

    print(f"\nTop 5 Best-Rated Fictions:")
    for idx, fiction in enumerate(best_rated["results"][:5], 1):
        status = ", ".join(fiction["labels"]) if fiction["labels"] else "Active"
        print(f"{idx}. {fiction['title']} - Rating: ⭐{fiction['rating']} [{status}]")

    print(f"\n{'='*70}")
    print("✅ Example complete!")


if __name__ == "__main__":
    main()
All endpoints · 3 totalmissing one? ·

Search fictions by title keyword, tags, genre, and ordering. Returns paginated results with 20 items per page. Supports filtering by included/excluded tags and genre.

Input
ParamTypeDescription
pagestringPage number for pagination.
genrestringGenre filter. Accepts: action, adventure, comedy, contemporary, drama, fantasy, historical, horror, mystery, psychological, romance, satire, sci_fi, short_story, thriller, tragedy.
titlestringTitle or keyword to search for in fiction titles and descriptions.
order_bystringSort order for results. Accepts: relevance, popularity, rating, name, pages, readers, date.
tags_addstringTag to include in results. Use snake_case tag identifiers such as: litrpg, summoned_hero, male_lead, female_lead, sci_fi, strong_lead, adventure, fantasy, reader_interactive, supernatural, magic, progression, action, gamelit, high_fantasy, deck_building, post_apocalyptic, low_fantasy, dungeon_crawler, urban_fantasy, contemporary, grimdark, mystery, survival, crafting, comedy, slice_of_life, local_protagonist, apocalypse, villainous_lead, multiple_lead, drama, romance, space_opera, psychological, first_contact, soft_sci-fi, strategy, harem, non-human_lead, secret_identity, attractive_lead, war_and_military, mythos, kingdom_building, technologically_engineered, artificial_intelligence, modern_knowledge, magitech, anti-hero_lead, super_heroes, horror, satire, system_invasion, school_life, martial_arts, historical, dystopia.
tags_removestringTag to exclude from results. Uses the same tag identifiers as tags_add.
Response
{
  "type": "object",
  "fields": {
    "page": "integer",
    "count": "integer",
    "results": "array of fiction summaries with fiction_id, title, url, cover_url, tags, labels, rating, followers, pages, views, chapters",
    "total_pages": "integer"
  },
  "sample": {
    "page": 1,
    "count": 20,
    "results": [
      {
        "url": "https://www.royalroad.com/fiction/65629/the-game-at-carousel-a-horror-movie-litrpg",
        "tags": [
          "LitRPG",
          "Urban Fantasy",
          "Progression"
        ],
        "pages": 4306,
        "title": "The Game at Carousel: A Horror Movie LitRPG",
        "views": 7954762,
        "labels": [
          "Original",
          "STUB"
        ],
        "rating": 4.81,
        "chapters": 443,
        "cover_url": "https://www.royalroadcdn.com/public/covers-large/65629-the-game-at-carousel-a-horror-movie-litrpg.jpg?time=1681708250",
        "followers": 10603,
        "fiction_id": "65629"
      }
    ],
    "total_pages": 1068
  }
}

About the Royal Road API

Search and Browse Fictions

The search_fictions endpoint accepts a title keyword along with optional genre, tags_add, and tags_remove parameters. Tags use snake_case identifiers (e.g. litrpg, summoned_hero, female_lead). Results are paginated at 20 items per page and can be sorted via order_by using values like relevance, popularity, rating, pages, or date. Each result in the results array includes fiction_id, title, url, cover_url, tags, labels, rating, followers, pages, views, and chapters.

Category Listings

The list_fictions endpoint retrieves fictions from Royal Road's ranking and browsing pages. The category parameter accepts values including best-rated, trending, active-popular, complete, and weekly-popular. Response pagination varies by category (20–50 items per page). The response shape mirrors search_fictions, returning the same fiction summary fields plus a category string confirming which feed was queried.

Fiction Detail Records

The get_fiction endpoint takes a numeric fiction_id (available from either listing endpoint) and returns a full detail record. The ratings object breaks down into five scores: overall_score, style_score, story_score, grammar_score, and character_score. Additional fields include author, warnings (content warnings as an array of strings), favorites, followers, cover_url, labels, tags, and a chapters array where each entry contains title, url, and date.

Reliability & maintenance

The Royal Road API is a managed, monitored endpoint for royalroad.com — not a raw scraper you maintain. Every endpoint is automatically health-checked on a schedule, and when royalroad.com changes and a check fails, the API is automatically queued for repair and re-verified. It is built to keep working as the site underneath it changes.

This isn't an official royalroad.com API — it's an independent, maintained REST wrapper over public data. Where the source has no official API (or only a limited one), Parse gives you a stable contract over a source that never promised one, and keeps it current. Need a new endpoint or field? You can revise it yourself in plain English and the agent rebuilds it against the live site in minutes — contributing the change back to the shared API is free.

Will this API break when the source site changes?+
It's built not to. Every endpoint is health-checked on a schedule with automated test probes. When the source site changes and a check fails, the API is automatically queued for repair and re-verified — that's the self-healing layer. Each API page shows when its endpoints were last verified. And because marketplace APIs are shared, any fix reaches everyone using it.
Is this an official API from the source site?+
No — Parse APIs are independent, managed REST wrappers over publicly available data. That is the point: where a site has no official API (or only a limited one), Parse gives you a maintained, monitored endpoint for that data and keeps it working as the site changes — so you get a stable contract over a source that never promised one.
Can I fix or extend this API myself if I need a new endpoint or field?+
Yes — and you don't have to wait on us. This API was generated by the Parse agent, which stays attached. Describe the change in plain English ("add an endpoint that returns reviews", "fix the price field") in the revise box on the API page or via the revise_api MCP tool, and the agent rebuilds it against the live site in minutes. Contributing the change back to the public API is free.
What happens if I call an endpoint that has an issue?+
Errors are machine-readable: a bad call returns a clean status with the list of available endpoints and a repair hint, so an agent (or you) can recover or trigger a fix instead of failing silently. Confirmed failures feed the automatic repair queue.
Common use cases
  • Build a fiction recommendation engine filtered by genre and tags like litrpg or summoned_hero
  • Track weekly trending fictions using list_fictions with the weekly-popular category
  • Monitor follower and favorites growth for specific fiction IDs over time via get_fiction
  • Aggregate Royal Road's five-dimension rating scores to rank authors by grammar_score or character_score
  • Compile chapter release histories for a fiction using the chapters array with publication dates
  • Filter out fictions with specific content warnings when building a reader-facing discovery tool
  • Index Royal Road's best-rated and complete categories to surface finished, high-quality stories
Pricing & limitsSee full pricing →
TierPriceCredits/monthRate limit
Free$0/mo1005 req/min
Hobby$30/mo1,00020 req/min
Developer$100/mo5,000100 req/min

One credit = one API call regardless of which marketplace API you call. Exceeding the rate limit returns a 429 response. Authenticate with the X-API-Key header.

Frequently asked questions
Does Royal Road have an official developer API?+
Royal Road does not publish an official public developer API or documented REST endpoints for third-party use.
What does `get_fiction` return for ratings, and how granular is the breakdown?+
The ratings object contains five fields: overall_score, style_score, story_score, grammar_score, and character_score. These correspond directly to the rating dimensions Royal Road displays on each fiction page. Raw vote counts are not included in the current response.
Can I retrieve the full text content of individual chapters?+
Not currently. The chapters array in get_fiction returns each chapter's title, url, and date, but not the body text. The API covers search, listing, and fiction-level metadata. You can fork it on Parse and revise to add a chapter-content endpoint.
How does tag filtering work in `search_fictions`?+
The tags_add parameter includes only fictions that carry the specified tag, while tags_remove excludes fictions with that tag. Both use the same snake_case identifiers. Currently only a single tag value is accepted per parameter per request; multi-tag filtering in one call is not supported. You can fork the API on Parse and revise it to support passing multiple tag values simultaneously.
Are author profile pages or reader reviews accessible through the API?+
Not currently. The API covers fiction metadata, ratings, chapter lists, and category rankings. Author profile data and individual reader reviews are not exposed. You can fork it on Parse and revise to add endpoints for author profiles or review listings.
Page content last updated . Spec covers 3 endpoints from royalroad.com.
Related APIs in EntertainmentSee all →