# readwise-plus

> Comprehensive Python SDK for Readwise. Wraps the Readwise API (v2, highlights/books) and the Reader API (v3, documents) behind one concept-oriented interface, plus a CLI and an MCP server. Built for Python 3.12+.

The preferred entry points are `Readwise` (sync) and `AsyncReadwise` (async): concept-oriented facades with `.documents`, `.highlights`, `.books`, `.tags`, `.digests`, `.sync`, and a `.raw.v2`/`.raw.v3` escape hatch for the low-level clients. The CLI and MCP server call the same underlying operations layer, so behavior is consistent everywhere. The pre-0.3 `ReadwiseClient`/`.v2`/`.v3`/managers/workflows/contrib API still works unchanged as a compatibility surface — see MIGRATION.md for the mapping and deprecation timeline.

## Core Concepts

- **Readwise / AsyncReadwise**: preferred SDK facades; concept-oriented attributes over one shared operations layer
- **Operations layer**: `documents`, `highlights`, `books`, `tags`, `digests`, `sync` — async-first, shared by the facade, CLI, and MCP server
- **Raw escape hatch**: `.raw.v2` / `.raw.v3` for direct low-level API access
- **Compatibility API**: `ReadwiseClient`/`AsyncReadwiseClient`, managers, workflows, contrib — unchanged pre-0.3 surfaces, not scheduled for removal before 1.0

## Quick Start

```python
from readwise_sdk import Readwise
from readwise_sdk.models import DocumentSearch

with Readwise() as readwise:  # reads READWISE_API_KEY from the environment
    result = readwise.documents.search(DocumentSearch(query="python", limit=20))
    for doc in result.items:
        print(doc.title)

# Async
from readwise_sdk import AsyncReadwise

async with AsyncReadwise() as readwise:
    result = await readwise.documents.search(DocumentSearch(query="python"))
```

## API Reference

### Facade (`Readwise` / `AsyncReadwise`)

- `.documents.search(DocumentSearch)`, `.get(id, with_content=)`, `.save(DocumentCreate)`, `.update(id, DocumentUpdate)`, `.delete(id)`, `.move(id, location)`, `.set_tags`/`.add_tag`/`.remove_tag`, `.inbox()`/`.later()`/`.archive()`, `.statistics()`, `.bulk_move`, `.bulk_set_tags`
- `.highlights.search(HighlightSearch)`, `.list(...)`, `.get(id)`, `.create(HighlightCreate)`, `.create_from_fields(...)`, `.update(id, HighlightUpdate)`, `.delete(id)`, `.search_items(query)`, `.since(days=)`, `.with_notes()`, `.bulk_tag`/`.bulk_untag`, `.export(...)`, `.push_batch(...)`, `.update_batch(...)`, `.delete_batch(...)`
- `.books.search(BookSearch)`, `.list(...)`, `.get(id)`, `.with_highlights(id)`, `.recent(...)`, `.statistics()`, `.search_items(query)`
- `.tags.auto_tag_highlights(patterns)`, `.get_tag_report()`, `.merge_tags`, `.rename_tag`, `.delete_tag`, `.get_highlights_by_tag`, `.get_untagged_highlights`
- `.digests.daily()`, `.weekly()`, `.book(id)`, `.custom(...)`
- `.sync.full(...)`, `.incremental(...)`, `.poll_once(...)`, `.batch_highlights/.batch_books/.batch_documents(...)`, `.status()`, `.reset()`
- `.raw.v2`, `.raw.v3`: the same low-level clients described under Compatibility API below

Models: import search inputs from `readwise_sdk.models` (`DocumentSearch`, `HighlightSearch`, `BookSearch`); import result/domain types from `readwise_sdk.models.reader` (`Document`, `DocumentCreate`, `DocumentUpdate`, `DocumentCategory`, `DocumentLocation`) and `readwise_sdk.models.readwise` (`Highlight`, `HighlightCreate`, `HighlightUpdate`, `Book`, `BookCategory`, `HighlightColor`, `Tag`).

### Compatibility API

`ReadwiseClient`/`AsyncReadwiseClient` (`from readwise_sdk import ReadwiseClient`):

- `client.v2.list_highlights(updated_after, book_id, page_size)`, `.get_highlight(id)`, `.create_highlights(highlights)`, `.update_highlight(id, update)`, `.delete_highlight(id)`, `.list_books(...)`, `.get_book(id)`, `.export_highlights(...)`, `.get_daily_review()`
- `client.v3.list_documents(location, category, updated_after, with_content)`, `.get_document(id, with_content)`, `.create_document(...)`, `.save_url(url, ...)`, `.update_document(id, update)`, `.delete_document(id)`, `.move_to_later/.archive/.move_to_inbox(id)`, `.get_inbox/.get_reading_list/.get_archive()`

Managers (`from readwise_sdk.managers import ...`): `HighlightManager` (`get_highlights_since`, `get_highlights_by_book`, `search_highlights`, `bulk_tag`), `BookManager` (`get_books_by_category`, `get_book_with_highlights`, `get_reading_stats`), `DocumentManager` (`get_inbox`, `archive`, `get_inbox_stats`), `SyncManager` (`full_sync`, `incremental_sync`).

Workflows (`from readwise_sdk.workflows import ...`): `DigestBuilder` (`create_daily_digest`, `create_weekly_digest`, `create_book_digest`, `create_custom_digest`), `TagWorkflow` (`auto_tag_highlights` with `TagPattern(pattern, tag, case_sensitive=, match_in_notes=, match_in_text=)`, `merge_tags`, `rename_tag`), `ReadingInbox` (`get_queue_stats`, `smart_archive`, `get_inbox_by_priority`), `BackgroundPoller` (`poll_once`, `start`, `stop`).

Contrib (`from readwise_sdk.contrib import ...`): `HighlightPusher` (`push`, `push_batch`), `DocumentImporter` (`import_document`, `save_url`), `BatchSync` (`sync_highlights`, `sync_books`, `sync_all`).

## Exceptions

- `ReadwiseError`: base exception for all SDK errors
- `AuthenticationError`: invalid API token (HTTP 401)
- `RateLimitError`: rate limit exceeded (HTTP 429), includes `retry_after`
- `NotFoundError`: resource not found (HTTP 404)
- `ValidationError`: invalid request data (HTTP 400)
- `ServerError`: server error (HTTP 5xx)

## CLI

`readwise [--output table|json|jsonl] [--no-color] [--quiet] <group> <command>` with groups `highlights`, `books`, `documents` (alias `reader`), `tags`, `digest`, `sync`, plus `version`.

## MCP Server

Install the `mcp` extra and run `readwise-mcp` (or `python -m readwise_sdk.mcp`). Nine tools: `save_to_reader`, `search_documents`, `get_document`, `update_document`, `delete_document`, `get_highlights`, `export_highlights`, `create_highlight`, `get_books`. Auth via `READWISE_API_KEY` (env var or `~/.env`).

## Installation

```bash
pip install readwise-plus          # core
pip install readwise-plus[cli]     # + CLI
pip install readwise-plus[mcp]     # + MCP server
```

## Environment Variables

- `READWISE_API_KEY`: default API token if not provided to `Readwise`/`AsyncReadwise`/`ReadwiseClient`

## Links

- [GitHub Repository](https://github.com/EvanOman/readwise-plus)
- [MIGRATION.md](https://github.com/EvanOman/readwise-plus/blob/main/MIGRATION.md) — old→new API mapping and deprecation timeline
- [Readwise API Documentation](https://readwise.io/api_deets)
- [Reader API Documentation](https://readwise.io/reader_api)
