async def reader_create_document(
url: str,
html: Optional[str] = None,
should_clean_html: Optional[bool] = None,
title: Optional[str] = None,
author: Optional[str] = None,
summary: Optional[str] = None,
publishedDate: Optional[str] = None,
imageUrl: Optional[str] = None,
location: Optional[Literal["new", "later", "archive", "feed"]] = "new",
category: Optional[Literal["article", "email", "rss", "highlight", "note", "pdf", "epub", "tweet", "video"]] = None,
saved_using: Optional[str] = None,
tags: Optional[List[str]] = None,
notes: Optional[str] = None,
) -> CreateDocumentResponse:
"""
Save a new document (URL) to your Readwise Reader library.
Args:
url: (Required) URL to save. Can be a placeholder if no URL is available (e.g., https://yourapp.com#document1)
html: Provide the document's content as valid HTML (Readwise will not scrape the URL)
should_clean_html: Auto-clean HTML and parse metadata when html is provided (default: false)
title: Override the detected title
author: Override the detected author
summary: Override the detected summary
publishedDate: Publication date (ISO 8601 format)
imageUrl: Cover image URL
location: Where to save the document (new, later, archive, feed; default: new)
category: Override the detected category (article, email, rss, highlight, note, pdf, epub, tweet, video)
saved_using: Source identifier for the document (e.g., "MyApp")
tags: List of tags to apply
notes: Personal notes about the document
Returns:
CreateDocumentResponse with id, url, title, and status
"""
ctx = get_reader_context()
logger.info(f"reader_create_document: url={url}, title={title}, location={location}")
try:
# Validate URL
if not url:
raise ValueError("URL is required. Provide a valid URL to save.")
# Validate location
if location:
_validate_location(location, VALID_SAVE_LOCATIONS)
# Validate category
if category:
_validate_category(category)
# Build payload
payload: Dict[str, Any] = {"url": url}
if html:
payload["html"] = html
if should_clean_html is not None:
payload["should_clean_html"] = should_clean_html
if title:
payload["title"] = title
if author:
payload["author"] = author
if summary:
payload["summary"] = summary
if publishedDate:
payload["published_date"] = publishedDate
if imageUrl:
payload["image_url"] = imageUrl
if location:
payload["location"] = location
if category:
payload["category"] = category
if saved_using:
payload["saved_using"] = saved_using
if tags:
payload["tags"] = tags
if notes:
payload["notes"] = notes
# Make API request
response = await ctx.client.post("/save/", json=payload)
response.raise_for_status()
data = response.json()
# Parse response
try:
return CreateDocumentResponse(
id=data.get("id", ""),
url=data.get("url", ""),
title=data.get("title", "Untitled"),
status="created" if response.status_code == 201 else "updated",
)
except (TypeError, ValueError) as e:
logger.error(f"Invalid API response format: {e}")
raise ValueError(
"Unexpected response format from Reader API when creating document."
)
except httpx.HTTPStatusError as e:
if e.response.status_code == 401:
raise ValueError(
"Authentication failed. Please check your READWISE_ACCESS_TOKEN."
)
elif e.response.status_code == 400:
error_detail = e.response.text
raise ValueError(f"Invalid request: {error_detail}")
elif e.response.status_code == 429:
raise _rate_limit_error(e.response)
raise
except ValueError as e:
raise
except Exception as e:
logger.error(f"Error in reader_create_document: {str(e)}")
raise ValueError(f"Failed to create document: {str(e)}")