Readwise API

Our API supports creating, fetching, and updating highlights on behalf of the user. Rather than following any particular standard to the letter, we tried to make it as fun to use and easy to understand as possible. If you have any questions, please reach out :)

Authentication

Set a header with key "Authorization" and value: "Token XXX" where XXX is your Readwise access token. You (or your users) can get that from here: readwise.io/access_token

If you want to check that a token is valid, just make a GET request to https://readwise.io/api/v2/auth/ with the above header. You should receive a 204 response.


Highlight CREATE

If you want to sync highlights to a user's Readwise account from your own application, this is the only endpoint you should need.


Request: POST to https://readwise.io/api/v2/highlights/


Parameters: A JSON object with the key highlights, pointing to an array of objects, each with these keys:

Key Type Description Required
text string The highlight text, (technically the only field required in a highlight object) yes
title string Title of the book/article/podcast (the "source") no
author string Author of the book/article/podcast (the "source") no
image_url string The url of a cover image for this book/article/podcast (the "source") no
source_url string The url of the article/podcast (the "source") no
source_type string One of: book, article or podcast
If source_type is not provided and source_url is, we assume it's an article. If source_url is not provided we assume book type.
no
note string Annotation note attached to the specific highlight.
You can also use this field to create tags thanks to our inline tagging functionality.
no
location integer Highlight's location in the source text. Used to order the highlights.
If not provided we will fill this based on the order of highlights in the list.
If location_type is "podcast", we interpret the integer as number of seconds that elapsed from the start of the recording.
no
location_type string One of: page, order or time_offset
Default is order. If provided type is different than "order", make sure to provide location as well (see below).
no
highlighted_at string A datetime representing when the highlight was taken in the ISO 8601 format; default timezone is UTC.
Example: "2020-07-14T20:11:24+00:00"
no
highlight_url string Unique url of the specific highlight (eg. a concrete tweet or a podcast snippet) no

The highlights array can be length 1+ and each highlight can be from the same or multiple books/articles. If you don't include a title, we'll put the highlight in a generic "Quotes" book, and if you don't include an author we'll keep it blank or just use the URL domain (if a source_url was provided).

Finally, we de-dupe highlights by title/author/text/source_url. So if you send a highlight with those 4 things the same (including nulls) then it will do nothing rather than create a "duplicate".

You can also use this endpoint to easily update a previously created highlight if a highlight_url was set. Pass the same url with a new text and the highlight's text will be updated.


Response:

  • Status code: 200
  • List of created/updated books/articles/podcasts:


[
  {
    "id": 111,
    "title": "Moby Dick",
    "author": "Herman Melville",
    "category": "books",
    "num_highlights": 2,
    "last_highlight_at": null,
    "updated": "2020-10-08T12:00:22.447912Z",
    "cover_image_url": "https://readwise.io/static/images/default-book-icon-0.png",
    "highlights_url": "https://readwise.io/bookreview/111",
    "source_url": null,
    "modified_highlights": [
      1337
    ]
  }
]
                  
Note that under modified_highlights key you can obtain ids of the highlights that were created or updated directly by your request.

Usage/Examples:

  • JavaScript
  • 
    $.ajax({
      url: 'https://readwise.io/api/v2/highlights/',
      type: 'POST',
      contentType: 'application/json',
      beforeSend: function (xhr) {
        xhr.setRequestHeader('Authorization', 'Token XXX');
        },
      data: JSON.stringify({
      'highlights': [
        {
          // A highlight in a book
          'text': 'Call me Ishmael',
          'title': 'Moby Dick',
          'author': 'Herman Melville',
        },
        {
          // Another highlight, later in the same book
          'text': "...but don't ever call me an octopus",
          'title': 'Moby Dick',
          'author': 'Herman Melville',
        },
      ],
      }),
      success: function (result) {console.log(result)},
      error: function (error) {console.log(error)},
    });
              

    Another example of different use cases:

    
    $.ajax({
      url: 'https://readwise.io/api/v2/highlights/',
      type: 'POST',
      contentType: 'application/json',
      beforeSend: function (xhr) {
        xhr.setRequestHeader('Authorization', 'Token XXX');
        },
      data: JSON.stringify({
      'highlights': [
        {
          // A highlight from an article,
          // with an attached note the user made.
          'text': 'To be happy I think you have to be doing... ',
          'title': 'How to Do What You Love',
          'author': 'Paul Graham',
          'source_url': 'http://www.paulgraham.com/love.html',
          'source_type': 'article',
          'note': 'Love this quote',
        },
        {
          // Minimal example: just a text highlight.
          // By default, this will be put into a generic "Quotes" book.
          'text': 'My lovely passage',
        },
      ],
      }),
      success: function (result) {console.log(result)},
      error: function (error) {console.log(error)},
    });
            
  • Python
  • 
    import requests
    requests.post(
        url="https://readwise.io/api/v2/highlights/",
        headers={"Authorization": "Token XXX"},
        json={
            "highlights": [{
                "text": "Call me Ishmael",
                "title": "Moby Dick",
                "author": "Herman Melville",
                "source_type": "book",
                "location_type": "page",
                "location": 3,
                "highlighted_at": "2020-07-14T20:11:24+00:00",
            }]
        }
    )
            
  • Bash
  • 
    $ curl --request POST --url https://readwise.io/api/v2/highlights/ \
      -H 'Authorization: Token XXX' -H 'Content-Type: application/json' \
      --data '{"highlights":[{"text": "Call me Ishmael","title": "Moby Dick","author": "Herman Melville"}]}'
    
    < HTTP/1.1 200 OK
    [{"title":"Moby Dick","highlights_url":"https://readwise.io/bookreview/123"}]
            

Advanced API

The highlight creation endpoint above should be sufficient for most usecases interested in just getting highlights into a user's account. The below endpoints can be used for more complex integrations, that might want to read, query, update, or delete a user's highlights.

Highlights LIST

Request: GET to https://readwise.io/api/v2/highlights/

Parameters: Usual query params:

  • page_size – specify number of results per page (default is 100, max is 1000)
  • page – specify the pagination counter
  • book_id – return highlights of a specifed book (for obtaining book ids, see section below)
  • updated__lt – filter by last updated datetime (less than)
  • updated__gt – filter by last updated datetime (greater than)
  • highlighted_at__lt – filter by the time when highlight was taken (NOTE that some highlights may not have value set)
  • highlighted_at__gt – filter by the time when highlight was taken (NOTE that some highlights may not have value set)

Response:

  • Status code: 200
  • A list of highlights with a pagination metadata:


{
  "count": 1163,
  "next": "https://readwise.io/api/v2/highlights?page=2",
  "previous": null,
  "results": [
    {
      "id": 59758950,
      "text": "The fundamental belief of metaphysicians is THE BELIEF IN ANTITHESES OF VALUES.",
      "note": "",
      "location": 9,
      "location_type": "order",
      "highlighted_at": null,
      "url": null,
      "color": "",
      "updated": "2020-10-01T12:58:44.716235Z",
      "book_id": 2608248
    },
    ...
  ]
}
              

Usage/Examples:

  • JavaScript
  • 
    $.ajax({
      url: 'https://readwise.io/api/v2/highlights/',
      type: 'GET',
      contentType: 'application/json',
      beforeSend: function (xhr) {
        xhr.setRequestHeader('Authorization', 'Token XXX');
      },
      data: {"page_size": 10},
      success: function (result) {
        console.log(result)
      },
      error: function (error) {
        console.log(error)
      },
    });
              
  • Python
  • 
    import requests
    
    # getting highlights from a particular book
    # made after February 1st, 2020, 21:35:53 UTC
    querystring = {
        "book_id": 1337,
        "highlighted_at__gt": "2020-02-01T21:35:53Z",
    }
    
    response = requests.get(
        url="https://readwise.io/api/v2/highlights/",
        headers={"Authorization": "Token XXX"},
        params=querystring
    )
    
    data = response.json()
                        

Highlight DETAIL

Request: GET to https://readwise.io/api/v2/highlights/<highlight id>/

Parameters:
This endpoint doesn't take any parameters.

Response:

  • Status code: 200
  • Representation of a specific highlight:


{
  "id": 13,
  "text": "To be alone for any length of time is to shed an outer skin. The...",
  "note": "",
  "location": 57,
  "location_type": "location",
  "highlighted_at": "2020-02-02T16:46:07Z",
  "url": null,
  "color": "yellow",
  "updated": "2020-04-06T12:30:52.318552Z",
  "book_id": 1337
}
              

Usage/Examples:

  • JavaScript
  • 
    $.ajax({
      url: 'https://readwise.io/api/v2/highlights/13/',
      type: 'GET',
      contentType: 'application/json',
      beforeSend: function (xhr) {
        xhr.setRequestHeader('Authorization', 'Token XXX');
      },
      success: function (result) {
        console.log(result)
      },
      error: function (error) {
        console.log(error)
      },
    });
              
  • Python
  • 
    import requests
    
    response = requests.get(
        url="https://readwise.io/api/v2/highlights/13/",
        headers={"Authorization": "Token XXX"},
    )
    
    data = response.json()
                        

Highlight UPDATE

Request: PATCH to https://readwise.io/api/v2/highlights/<highlight id>/

Parameters: A JSON object with one or more of the following keys:

Key Type Description Required
text string The highlight text, (technically the only field required in a highlight object) no
note string Annotation note attached to the specific highlight no
location string Highlight's location in the source text. Used to order the highlights. If not provided we will fill this based on the order of highlights in the list. If location_type is "podcast", we interpret the integer as number of seconds that elapsed from the start of the recording. no
url string Unique url of the specific highlight (eg. a concrete tweet or a podcast snippet) no
color string Highlight's color tag. One of: yellow, blue, pink, orange, green, purple. no

Response:

  • Status code: 200
  • The detail representation of patched highlight:
                
{
  "id": 13,
  "text": "To be alone for any length of time is to shed an outer skin. The...",
  "note": "",
  "location": 57,
  "location_type": "location",
  "highlighted_at": "2020-02-02T16:46:07Z",
  "url": null,
  "color": "orange",
  "updated": "2020-04-06T12:30:52.318552Z",
  "book_id": 1337
}
              

Usage/Examples:

  • JavaScript
  • 
    $.ajax({
      url: 'https://readwise.io/api/v2/highlights/13/',
      type: 'PATCH',
      contentType: 'application/json',
      beforeSend: function (xhr) {
        xhr.setRequestHeader('Authorization', 'Token XXX');
      },
      data: JSON.stringify({
        "color": "green",
        "note": "This makes me think of what Marcus Aurelius wrote"
      }),
      success: function (result) {
        console.log(result)
      },
      error: function (error) {
        console.log(error)
      },
    });
              
  • Python
  • 
    import requests
    
    payload = {
        "color": "green",
        "note": "This makes me think of what Marcus Aurelius wrote",
    }
    
    response = requests.patch(
        url="https://readwise.io/api/v2/highlights/13/",
        headers={"Authorization": "Token XXX"},
        data=payload
    )
    
    data = response.json()
                        

Highlight DELETE

Request: DELETE to https://readwise.io/api/v2/highlights/<highlight id>/

Parameters:
This endpoint doesn't take any parameters.

Response:

  • Status code: 204

Usage/Examples:

  • JavaScript
  • 
    $.ajax({
      url: 'https://readwise.io/api/v2/highlights/13/',
      type: 'DELETE',
      contentType: 'application/json',
      beforeSend: function (xhr) {
        xhr.setRequestHeader('Authorization', 'Token XXX');
      },
      success: function (result) {
        console.log(result)
      },
      error: function (error) {
        console.log(error)
      },
    });
              
  • Python
  • 
    import requests
    
    response = requests.delete(
        url="https://readwise.io/api/v2/highlights/13/",
        headers={"Authorization": "Token XXX"},
    )
                        

Books LIST

Request: GET to https://readwise.io/api/v2/books/

Parameters: Usual query params:

  • page_size – specify number of results per page (default is 100, max is 1000)
  • page – specify the pagination counter
  • category – return books within a specified category (books, articles, tweets, supplementals or podcasts)
  • num_highlights – filter by the number of highlights in a book (exact)
  • num_highlights__lt – filter by the number of highlights in a book (less than)
  • num_highlights__gt – filter by the number of highlights in a book (greater than)
  • updated__lt – filter by last updated datetime (less than)
  • updated__gt – filter by last updated datetime (greater than)
  • last_highlight_at__lt – filter by the time when highlight was taken (NOTE that some books may not have this value set)
  • last_highlight_at__gt – filter by the time when highlight was taken (NOTE that some books may not have this value set)

Response:

  • Status code: 200
  • A list of books with a pagination metadata:


{
  "count": 9,
  "next": "https://readwise.io/api/v2/books/?page=2",
  "previous": null,
  "results": [
    {
      "id": 1776,
      "title": "Early Retirement Extreme",
      "author": "Jacob Lund Fisker",
      "category": "books",
      "num_highlights": 68,
      "last_highlight_at": "2019-03-19T03:49:23Z",
      "updated": "2020-10-01T17:47:31.234826Z",
      "cover_image_url": "https://readwise.io/static/images/default-book-icon-2.png",
      "highlights_url": "https://readwise.io/bookreview/1776",
      "source_url": null
    },
    {
      "id": 1826,
      "title": "What Is the Point of Universal Basic Income?",
      "author": "David Perell",
      "category": "articles",
      "num_highlights": 3,
      "last_highlight_at": "2020-02-03T09:51:17Z",
      "updated": "2020-03-17T06:44:44.601570Z",
      "cover_image_url": "https://readwise.io/static/images/article4.png",
      "highlights_url": "https://readwise.io/bookreview/1826",
      "source_url": "https://perell.com/fellowship-essay/universal-basic-income"
    },
    ...
  ]
}
              

Usage/Examples:

  • JavaScript
  • 
    $.ajax({
      url: 'https://readwise.io/api/v2/books/',
      type: 'GET',
      contentType: 'application/json',
      beforeSend: function (xhr) {
        xhr.setRequestHeader('Authorization', 'Token XXX');
      },
      data: {"page_size": 500, "category": "articles"},
      success: function (result) {
        console.log(result)
      },
      error: function (error) {
        console.log(error)
      },
    });
              
  • Python
  • 
    import datetime
    import requests
    
    # getting books that were updated last week
    
    a_week_ago = datetime.datetime.now() - datetime.timedelta(days=7)
    
    querystring = {
        "category": "books",
        "updated__gt": a_week_ago.strftime("%Y-%m-%dT%H:%M:%SZ"),
    }
    
    response = requests.get(
        url="https://readwise.io/api/v2/books/",
        headers={"Authorization": "Token XXX"},
        params=querystring
    )
    
    data = response.json()