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.
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.
curl -X GET 'https://api.parse.bot/scraper/195f0514-c4a2-4100-87c0-84b79b177517/search_fictions' \ -H 'X-API-Key: $PARSE_API_KEY'
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()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.
| Param | Type | Description |
|---|---|---|
| page | string | Page number for pagination. |
| genre | string | Genre filter. Accepts: action, adventure, comedy, contemporary, drama, fantasy, historical, horror, mystery, psychological, romance, satire, sci_fi, short_story, thriller, tragedy. |
| title | string | Title or keyword to search for in fiction titles and descriptions. |
| order_by | string | Sort order for results. Accepts: relevance, popularity, rating, name, pages, readers, date. |
| tags_add | string | 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. |
| tags_remove | string | Tag to exclude from results. Uses the same tag identifiers as tags_add. |
{
"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.
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?+
Is this an official API from the source site?+
Can I fix or extend this API myself if I need a new endpoint or field?+
What happens if I call an endpoint that has an issue?+
- Build a fiction recommendation engine filtered by genre and tags like
litrpgorsummoned_hero - Track weekly trending fictions using
list_fictionswith theweekly-popularcategory - 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_scoreorcharacter_score - Compile chapter release histories for a fiction using the
chaptersarray with publication dates - Filter out fictions with specific content warnings when building a reader-facing discovery tool
- Index Royal Road's
best-ratedandcompletecategories to surface finished, high-quality stories
| Tier | Price | Credits/month | Rate limit |
|---|---|---|---|
| Free | $0/mo | 100 | 5 req/min |
| Hobby | $30/mo | 1,000 | 20 req/min |
| Developer | $100/mo | 5,000 | 100 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.
Does Royal Road have an official developer API?+
What does `get_fiction` return for ratings, and how granular is the breakdown?+
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?+
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`?+
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.