Skip to content

Readwise Reader API

Source code in readwise/api.py
class ReadwiseReader:
    def __init__(
        self,
        token: str,
    ):
        '''
        Readwise Reader API client.

        Documentation for the Readwise Reader API can be found here:
        https://readwise.io/reader_api

        Args:
            token: Readwise Reader Connector token
        '''
        self._token = token
        self._url = 'https://readwise.io/api/v3'

    @property
    def _session(self) -> requests.Session:
        '''
        Session object for making requests.
        The headers are set to include the token.
        '''
        session = requests.Session()
        session.headers.update(
            {
                'Accept': 'application/json',
                'Authorization': f'Token {self._token}',
            }
        )
        return session

    def _request(
        self, method: str, endpoint: str, params: dict = {}, data: dict = {}
    ) -> requests.Response:
        '''
        Make a request to the Readwise Reader API.
        The request is rate limited to 20 calls per minute.

        Args:
            method: HTTP method
            endpoint: API endpoints
            params: Query parameters
            data: Request body

        Returns:
            requests.Response
        '''
        url = self._url + endpoint
        logging.debug(f'Calling "{method}" on "{url}" with params: {params}')
        response = self._session.request(method, url, params=params, json=data)
        while response.status_code == 429:
            seconds = int(response.headers['Retry-After'])
            logging.warning(f'Rate limited by Readwise, retrying in {seconds} seconds')
            sleep(seconds)
            response = self._session.request(method, url, params=params, data=data)
        response.raise_for_status()
        return response

    def get(self, endpoint: str, params: dict = {}) -> requests.Response:
        '''
        Make a GET request to the Readwise Reader API client.

        Args:
            endpoint: API endpoints
            params: Query parameters

        Returns:
            requests.Response
        '''
        logging.debug(f'Getting "{endpoint}" with params: {params}')
        return self._request('GET', endpoint, params=params)

    def get_with_limit_20(self, endpoint: str, params: dict = {}) -> requests.Response:
        '''
        Get a response from the Readwise Reader API with a rate limit of 20 requests
        per minute.

        Args:
            endpoint: API endpoint
            params: Query parameters
        Returns:
            requests.Response
        '''
        return self.get(endpoint, params)

    def _get_pagination(
        self,
        get_method: Literal['get', 'get_with_limit_20'],
        endpoint: str,
        params: dict = {},
    ) -> Generator[dict, None, None]:
        '''
        Get a response from the Readwise Reader API with pagination.

        Args:
            get_method: Method to use for making requests
            endpoint: API endpoint
            params: Query parameters
            page_size: Number of items per page
        Yields:
            dict: Response data
        '''
        pageCursor = None
        while True:
            if pageCursor:
                params.update({'pageCursor': pageCursor})
            logging.debug(f'Getting page with cursor "{pageCursor}"')
            try:
                response = getattr(self, get_method)(endpoint, params=params)
            except ChunkedEncodingError:
                logging.error(f'Error getting page with cursor "{pageCursor}"')
                sleep(5)
                continue
            data = response.json()
            yield data
            if (
                type(data) == list
                or not data.get('nextPageCursor')
                or data.get('nextPageCursor') == pageCursor
            ):
                break
            pageCursor = data.get('nextPageCursor')

    def get_pagination_limit_20(
        self, endpoint: str, params: dict = {}
    ) -> Generator[dict, None, None]:
        '''
        Get a response from the Readwise Reader API with pagination and a rate limit
        of 20 requests per minute.

        Args:
            endpoint: API endpoint
            params: Query parameters
            page_size: Number of items per page
        Yields:
            Response data
        '''
        yield from self._get_pagination('get_with_limit_20', endpoint, params)

    def post(self, endpoint: str, data: dict = {}) -> requests.Response:
        '''
        Make a POST request to the Readwise Reader API.

        Args:
            endpoint: API endpoints
            data: Request body

        Returns:
            requests.Response
        '''
        url = self._url + endpoint
        logging.debug(f'Posting "{url}" with data: {data}')
        response = self._request('POST', endpoint, data=data)
        response.raise_for_status()
        return response

    def create_document(
        self,
        url: str,
        html: str | None = None,
        should_clean_html: bool | None = None,
        title: str | None = None,
        author: str | None = None,
        summary: str | None = None,
        published_at: datetime | None = None,
        image_url: str | None = None,
        location: Literal['new', 'later', 'archive', 'feed'] = 'new',
        saved_using: str | None = None,
        tags: list[str] = [],
    ) -> requests.Response:
        '''
        Create a document in Readwise Reader.

        Args:
            url: Document URL
            html: Document HTML
            should_clean_html: Whether to clean the HTML
            title: Document title
            author: Document author
            summary: Document summary
            published_at: Date and time the document was published
            image_url: An image URL to use as cover image
            location: Document location
            saved_using: How the document was saved
            tags: List of tags

        Returns:
            requests.Response
        '''
        data: dict[str, Any] = {
            'url': url,
            'tags': tags,
            'location': location,
        }

        if html:
            data['html'] = html

        if should_clean_html is not None:
            data['should_clean_html'] = should_clean_html

        if title:
            data['title'] = title

        if author:
            data['author'] = author

        if summary:
            data['summary'] = summary

        if published_at:
            data['published_at'] = published_at.isoformat()

        if image_url:
            data['image_url'] = image_url

        if saved_using:
            data['saved_using'] = saved_using

        return self.post('/save/', data)

    def get_documents(
        self, params: dict = {}
    ) -> Generator[ReadwiseReaderDocument, None, None]:
        for data in self.get_pagination_limit_20('/list/', params=params):
            for document in data['results']:
                yield ReadwiseReaderDocument(
                    id=document['id'],
                    url=document['url'],
                    source_url=document['source_url'],
                    title=document['title'],
                    author=document['author'],
                    source=document['source'],
                    category=document['category'],
                    location=document['location'],
                    tags=document['tags'],
                    site_name=document['site_name'],
                    word_count=document['word_count'],
                    created_at=datetime.fromisoformat(document['created_at']),
                    updated_at=datetime.fromisoformat(document['updated_at']),
                    notes=document['notes'],
                    published_date=document['published_date'],
                    summary=document['summary'],
                    image_url=document['image_url'],
                    parent_id=document['parent_id'],
                    reading_progress=document['reading_progress'],
                )

__init__(token)

Readwise Reader API client.

Documentation for the Readwise Reader API can be found here: https://readwise.io/reader_api

Parameters:

Name Type Description Default
token str

Readwise Reader Connector token

required
Source code in readwise/api.py
def __init__(
    self,
    token: str,
):
    '''
    Readwise Reader API client.

    Documentation for the Readwise Reader API can be found here:
    https://readwise.io/reader_api

    Args:
        token: Readwise Reader Connector token
    '''
    self._token = token
    self._url = 'https://readwise.io/api/v3'

create_document(url, html=None, should_clean_html=None, title=None, author=None, summary=None, published_at=None, image_url=None, location='new', saved_using=None, tags=[])

Create a document in Readwise Reader.

Parameters:

Name Type Description Default
url str

Document URL

required
html str | None

Document HTML

None
should_clean_html bool | None

Whether to clean the HTML

None
title str | None

Document title

None
author str | None

Document author

None
summary str | None

Document summary

None
published_at datetime | None

Date and time the document was published

None
image_url str | None

An image URL to use as cover image

None
location Literal['new', 'later', 'archive', 'feed']

Document location

'new'
saved_using str | None

How the document was saved

None
tags list[str]

List of tags

[]

Returns:

Type Description
Response

requests.Response

Source code in readwise/api.py
def create_document(
    self,
    url: str,
    html: str | None = None,
    should_clean_html: bool | None = None,
    title: str | None = None,
    author: str | None = None,
    summary: str | None = None,
    published_at: datetime | None = None,
    image_url: str | None = None,
    location: Literal['new', 'later', 'archive', 'feed'] = 'new',
    saved_using: str | None = None,
    tags: list[str] = [],
) -> requests.Response:
    '''
    Create a document in Readwise Reader.

    Args:
        url: Document URL
        html: Document HTML
        should_clean_html: Whether to clean the HTML
        title: Document title
        author: Document author
        summary: Document summary
        published_at: Date and time the document was published
        image_url: An image URL to use as cover image
        location: Document location
        saved_using: How the document was saved
        tags: List of tags

    Returns:
        requests.Response
    '''
    data: dict[str, Any] = {
        'url': url,
        'tags': tags,
        'location': location,
    }

    if html:
        data['html'] = html

    if should_clean_html is not None:
        data['should_clean_html'] = should_clean_html

    if title:
        data['title'] = title

    if author:
        data['author'] = author

    if summary:
        data['summary'] = summary

    if published_at:
        data['published_at'] = published_at.isoformat()

    if image_url:
        data['image_url'] = image_url

    if saved_using:
        data['saved_using'] = saved_using

    return self.post('/save/', data)

get(endpoint, params={})

Make a GET request to the Readwise Reader API client.

Parameters:

Name Type Description Default
endpoint str

API endpoints

required
params dict

Query parameters

{}

Returns:

Type Description
Response

requests.Response

Source code in readwise/api.py
def get(self, endpoint: str, params: dict = {}) -> requests.Response:
    '''
    Make a GET request to the Readwise Reader API client.

    Args:
        endpoint: API endpoints
        params: Query parameters

    Returns:
        requests.Response
    '''
    logging.debug(f'Getting "{endpoint}" with params: {params}')
    return self._request('GET', endpoint, params=params)

get_documents(params={})

Source code in readwise/api.py
def get_documents(
    self, params: dict = {}
) -> Generator[ReadwiseReaderDocument, None, None]:
    for data in self.get_pagination_limit_20('/list/', params=params):
        for document in data['results']:
            yield ReadwiseReaderDocument(
                id=document['id'],
                url=document['url'],
                source_url=document['source_url'],
                title=document['title'],
                author=document['author'],
                source=document['source'],
                category=document['category'],
                location=document['location'],
                tags=document['tags'],
                site_name=document['site_name'],
                word_count=document['word_count'],
                created_at=datetime.fromisoformat(document['created_at']),
                updated_at=datetime.fromisoformat(document['updated_at']),
                notes=document['notes'],
                published_date=document['published_date'],
                summary=document['summary'],
                image_url=document['image_url'],
                parent_id=document['parent_id'],
                reading_progress=document['reading_progress'],
            )

get_pagination_limit_20(endpoint, params={})

Get a response from the Readwise Reader API with pagination and a rate limit of 20 requests per minute.

Parameters:

Name Type Description Default
endpoint str

API endpoint

required
params dict

Query parameters

{}
page_size

Number of items per page

required

Yields: Response data

Source code in readwise/api.py
def get_pagination_limit_20(
    self, endpoint: str, params: dict = {}
) -> Generator[dict, None, None]:
    '''
    Get a response from the Readwise Reader API with pagination and a rate limit
    of 20 requests per minute.

    Args:
        endpoint: API endpoint
        params: Query parameters
        page_size: Number of items per page
    Yields:
        Response data
    '''
    yield from self._get_pagination('get_with_limit_20', endpoint, params)

get_with_limit_20(endpoint, params={})

Get a response from the Readwise Reader API with a rate limit of 20 requests per minute.

Parameters:

Name Type Description Default
endpoint str

API endpoint

required
params dict

Query parameters

{}

Returns: requests.Response

Source code in readwise/api.py
def get_with_limit_20(self, endpoint: str, params: dict = {}) -> requests.Response:
    '''
    Get a response from the Readwise Reader API with a rate limit of 20 requests
    per minute.

    Args:
        endpoint: API endpoint
        params: Query parameters
    Returns:
        requests.Response
    '''
    return self.get(endpoint, params)

post(endpoint, data={})

Make a POST request to the Readwise Reader API.

Parameters:

Name Type Description Default
endpoint str

API endpoints

required
data dict

Request body

{}

Returns:

Type Description
Response

requests.Response

Source code in readwise/api.py
def post(self, endpoint: str, data: dict = {}) -> requests.Response:
    '''
    Make a POST request to the Readwise Reader API.

    Args:
        endpoint: API endpoints
        data: Request body

    Returns:
        requests.Response
    '''
    url = self._url + endpoint
    logging.debug(f'Posting "{url}" with data: {data}')
    response = self._request('POST', endpoint, data=data)
    response.raise_for_status()
    return response