Skip to main content

PyAppleBooks

PyAppleBooks is a Python API library to access your Apple Books data.

PyPI License: MIT Buy Me A Coffee

Installation

pip install py_apple_books

Requires Python 3.10+ on macOS.

Available Functions

Collections

Function Description Parameters Return Type
list_collections() List all collections limit?, order_by? ModelIterable
get_collection_by_id(collection_id) Get a collection by its ID collection_id: str Collection
get_collection_by_title(title) Search collections by title substring title: str ModelIterable

Collection writes (v1.9.0+)

Apple exposes no automation API for collections, so these write directly to the library's SQLite store — behind guard rails: every call refuses while Books.app is running, takes a WAL-inclusive timestamped backup (kept under ~/.py_apple_books/backups/, restorable via write_safety.restore_library), validates the schema and aborts on drift, and runs in a single transaction that maintains Core Data's bookkeeping. Only user-created collections can be renamed or deleted; membership edits also work on "Want to Read". Writes also refuse when Books.app's state can't be verified — off macOS (e.g. in Docker), or if pgrep fails — raising WriteError.

⚠️ With iCloud "Collections, bookmarks and highlights" sync enabled, direct writes may not propagate to other devices and can be reverted by a cloud re-sync. Verify a small edit cross-device before relying on it.

Function Description Parameters Return Type
create_collection(title, details?) Create a user collection title: str, details?: str Collection
rename_collection(collection_id, new_title) Rename a user collection collection_id, new_title: str Collection
delete_collection(collection_id) Soft-delete a user collection (books untouched) collection_id None
add_book_to_collection(collection_id, book_id) Add a book (idempotent) collection_id, book_id bool
remove_book_from_collection(collection_id, book_id) Remove a book (idempotent) collection_id, book_id bool

Books

Function Description Parameters Return Type
list_books() List all books limit?, order_by? ModelIterable
get_book_by_id(book_id) Get a book by its ID book_id: str Book
get_book_by_title(title) Search books by title substring title: str ModelIterable
get_books_by_genre(genre) Search books by genre substring genre: str, limit?, order_by? ModelIterable

Reading Progress

Function Description Parameters Return Type
get_books_in_progress() Books currently being read (0% < progress < 100%) limit?, order_by? ModelIterable
get_finished_books() Books marked as finished limit?, order_by? ModelIterable
get_unstarted_books() Books not yet started limit?, order_by? ModelIterable
get_recently_read_books() Books ordered by last-opened date, most recent first limit? (default 10) ModelIterable

Annotations

Function Description Parameters Return Type
list_annotations() List all annotations limit?, order_by? ModelIterable
get_annotation_by_id(annotation_id) Get an annotation by its ID annotation_id: str Annotation
get_annotations_by_color(color) Filter annotations by highlight color color: str, limit?, order_by? ModelIterable
search_annotation_by_highlighted_text(text) Substring search in highlighted text text: str, limit?, order_by? ModelIterable
search_annotation_by_note(note) Substring search in user notes note: str, limit?, order_by? ModelIterable
search_annotation_by_text(text) Substring search across highlighted text, representative text, and notes text: str, limit?, order_by? ModelIterable
get_annotations_by_date_range(after?, before?) Filter annotations by creation date after?: datetime, before?: datetime, limit?, order_by? ModelIterable

Book Content (v1.7.0+)

Read the full text of your non-DRM EPUBs. Powered by ebooklib and beautifulsoup4.

Function Description Parameters Return Type
get_book_content(book_id) Return a BookContent handle after verifying the book is downloaded and not DRM-protected book_id: int BookContent
get_current_reading_location(book_id) Apple Books' auto-tracked "current reading position" bookmark (a zero-width annotation with a CFI) book_id: int Optional[Annotation]
get_current_reading_chapter(book_id) Convenience: resolve the bookmark's CFI to a Chapter book_id: int Optional[Chapter]

BookContent methods:

Method Description Return Type
list_chapters() Flattened table of contents with title, href, fragment, order, depth list[Chapter]
get_chapter_content(chapter_id) Plain text of a chapter, scoped to its fragment anchor str
chapter_at_cfi(cfi) Resolve an EPUB CFI to the containing Chapter Optional[Chapter]

BookContent properties:

Property Description
is_epub True if the path is an EPUB bundle directory
is_pdf True if the path is a single PDF file
is_downloaded True if locally materialized (not an iCloud placeholder) — does not trigger hydration
is_drm_protected True if the EPUB carries FairPlay META-INF/sinf.xml, Adobe rights.xml, or an encryption.xml that encrypts more than fonts (font obfuscation alone doesn't count; an unparseable file does)

Exceptions

Content-access methods may raise:

Exception When
BookNotDownloadedError The book has no local file (never downloaded) or the file is an iCloud placeholder
DRMProtectedError The book is DRM-protected — usually an Apple Books Store purchase (FairPlay), occasionally an encrypted imported EPUB
UnsafeEpubEntryError A file in the EPUB bundle resolves outside it (absolute/../ href, symlink), isn't a regular file, or is implausibly large; the book is refused
AppleBooksError Base class for all exceptions above, plus EPUB-parsing errors

Examples

Creating a client

from py_apple_books import PyAppleBooks

api = PyAppleBooks()

List books

for book in api.list_books():
    print(f"{book.title} — {book.author or 'Unknown Author'}")

Get annotations

for a in api.list_annotations(limit=5):
    print(f"[{a.color}] {a.selected_text}")

Search highlights

for a in api.search_annotation_by_highlighted_text('genome'):
    print(a.selected_text)

Filter by color

for a in api.get_annotations_by_color('yellow'):
    print(a.selected_text)

Reading progress

for book in api.get_books_in_progress(limit=5):
    print(f"{book.title}: {book.reading_progress:.1f}%")

Read chapter content

from py_apple_books.exceptions import BookNotDownloadedError, DRMProtectedError

book = api.get_book_by_id(42)
try:
    content = api.get_book_content(book.id)
except BookNotDownloadedError:
    print("Open the book in Apple Books first to download it.")
except DRMProtectedError:
    print("DRM-protected Store purchase — text content isn't readable.")
else:
    for ch in content.list_chapters():
        print(f"{'  ' * ch.depth}[{ch.order}] {ch.title}")

    # Read the first substantive chapter
    text = content.get_chapter_content(content.list_chapters()[0].id)
    print(text[:500])

See what the user is currently reading

# The book they most recently opened
for book in api.get_recently_read_books(limit=1):
    ch = api.get_current_reading_chapter(book.id)
    if ch is None:
        continue
    content = api.get_book_content(book.id)
    text = content.get_chapter_content(ch.id)
    print(f"Currently reading: {book.title}")
    print(f"Progress: {book.reading_progress:.1f}%")
    print(f"Chapter {ch.order}: {ch.title}")
    print(text[:500])

Get all collections and books

for collection in api.list_collections():
    print(f"{collection.title}: {len(collection.books)} books")
    for book in collection.books:
        print(f"  - {book.title}")

How content access handles Apple Books' quirks

  • iCloud placeholders — books you've imported but haven't opened lately can live only in iCloud. is_downloaded detects this via os.stat (for files) or du -sk (for bundle directories) without triggering a download. get_book_content raises BookNotDownloadedError so you can prompt the user to open the book in Apple Books.
  • DRM'd Store purchases — FairPlay-encrypted EPUBs carry META-INF/sinf.xml / an encryption.xml covering their content, and their chapter bodies are opaque ciphertext. Detected up-front from that evidence; callers get a clear DRMProtectedError. EPUBs whose encryption.xml only obfuscates fonts are readable.
  • Untrusted book files — imported EPUBs are stored as unzipped bundles, so every entry is read through a contained reader: anything resolving outside the bundle, non-regular files and oversized entries raise UnsafeEpubEntryError instead of exposing unrelated local files.
  • Missing metadata — Apple's unknown-author placeholder is returned as author=None, and the ZPAGECOUNT placeholder (0 or 1) as page_count=None.
  • Non-standard EPUB layouts — OPFs at unusual paths, NCX files not declared in <spine toc=…> (e.g. The 4-Hour Workweek), EPUB3 books with nav docs only (e.g. On Numbers and Games), URL-encoded href characters (%21 → !), duplicate navPoint ids — all handled, with a stdlib NCX override for the ebooklib blind spots.
  • Fragment-scoped chapters — Project Gutenberg EPUBs often put multiple sections in one XHTML file, separated by anchors. get_chapter_content returns only the requested section, not the whole file.

Running the tests

pip install -e '.[dev]'
pytest

The test suite covers placeholder/DRM detection, NCX parsing, XHTML text extraction, fragment scoping, and end-to-end BookContent behavior against generated EPUB fixtures. No real Apple Books library required.

Upcoming Features

  • Adding a book to collection
  • Removing a book from collection
  • Updating annotations

Contribution

Thank you for considering contributing to this project! Your help is greatly appreciated.

Opening Issues

If you encounter a bug, have a feature request, or want to discuss something related to the project, please open an issue on the GitHub repository. When opening an issue, please provide:

Bug Reports: Describe the issue in detail. Include steps to reproduce the bug if possible, along with any error messages or screenshots.

Feature Requests: Clearly explain the new feature you'd like to see added to the project. Provide context on why this feature would be beneficial.

General Discussions: Feel free to start discussions on broader topics related to the project.

Contributing

1️⃣ Fork the GitHub repository https://github.com/vgnshiyer/py-apple-books
2️⃣ Create a new branch for your changes (git checkout -b feature/my-new-feature).
3️⃣ Make your changes and test them thoroughly.
4️⃣ Push your changes and open a Pull Request to main.

Please provide a clear title and description of your changes.

License

PyAppleBooks is licensed under the MIT license. See the LICENSE file for details.

Metadata

Release files for py-apple-books 1.9.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for py-apple-books 1.9.1
File Size Uploaded
py_apple_books-1.9.1.tar.gz 73.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for py-apple-books 1.9.1
File Interpreter ABI Platform
py_apple_books-1.9.1-py3-none-any.whl Python 3 none any Details

Total release size: 126.0 kB

Release files / py_apple_books-1.9.1.tar.gz

Download URL py_apple_books-1.9.1.tar.gz
Size 73.9 kB
Tags Source
SHA-256 checksum
How to use checksums
a689d5c65d444e21c7654383e364e57850fa652c11d446ab797eab6a885d81f2
BLAKE2b-256 checksum
How to use checksums
f17c8f01e8f106c7b8253cc098c6bbedb89dd0e1c4259d566d3a08bf56d4d5a4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / py_apple_books-1.9.1-py3-none-any.whl

Download URL py_apple_books-1.9.1-py3-none-any.whl
Size 52.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3c733bac24e8039398791ec39551ce258a25b739c8e6d5a4f7d63528433f6c36
BLAKE2b-256 checksum
How to use checksums
2aa821fa4bf2e9d7b177f82f7e26a64729b0e3b3d91c123387940ebcc4732960
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page