Reader API
The Reader API just supports saving new documents to Reader. We will add more endpoints in the near future. 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.
Document CREATE
Request: POST
to https://readwise.io/api/v3/save/
Parameters: A JSON object with the following keys:
Key | Type | Description | Required |
---|---|---|---|
url | string | The document's unique URL. If you don't have one, you can provide a made up value such as https://yourapp.com#document1 |
yes |
html | string | The document's content, in valid html (see examples). If you don't provide this, we will try to scrape the URL you provided to fetch html from the open web. | no |
should_clean_html | boolean | Only valid when html is provided. Pass true to have us automatically clean the html and parse the metadata (title/author) of the document for you. By default, this option is false . |
no |
title | string | The document's title, it will overwrite the original title of the document | no |
author | string | The document's author, it will overwrite the original author (if found during the parsing step) | no |
summary | string | Summary of the document | no |
published_date | date | A datetime representing when the document was published in the ISO 8601
format;
default timezone is UTC. Example: "2020-07-14T20:11:24+00:00" |
no |
image_url | string | An image URL to use as cover image | no |
location | string | One of: new , later , archive or feed . Default
is
new .Represents the initial location of the document (previously called triage_status ).
Note: if you try to use a location the user doesn't have enabled in their settings, this value will be set to their default location.
|
no |
category | string | One of: article , email , rss , highlight , note , pdf ,
epub , tweet or video . Default is guessed based on the URL, usually article .
|
no |
saved_using | string | This value represents the source of the document | no |
tags | list | A list of strings containing tags, example: ["tag1", "tag2"] |
no |
notes | string | A top-level note of the document | no |
Response:
- Status code:
201
or200
if document already exist - Created document details:
{
"id": "0000ffff2222eeee3333dddd4444",
"url": "https://read.readwise.io/new/read/0000ffff2222eeee3333dddd4444",
}
Usage/Examples:
- JavaScript
$.ajax({
url: 'https://readwise.io/api/v3/save/',
type: 'POST',
contentType: 'application/json',
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Token XXX');
},
data: JSON.stringify({
"url": "https://example.com/article/",
"html": "<div><h1>This article is awesome</h1><p>content here!</p></div>"
"tags": ["tag1", "tag2"]
}),
success: function (result) {console.log(result)},
error: function (error) {console.log(error)},
});
import requests
requests.post(
url="https://readwise.io/api/v3/save/",
headers={"Authorization": "Token XXX"},
json={
"url": "https://example.com/article/",
# No html is provided, so the url will be scraped to get the document's content.
"tags": ["tag3", "tag4"]
}
)
$ curl -v https://readwise.io/api/v3/save/ -H "Authorization: Token XXX" -X POST -d '{"url": "https://example.com/article/"}' -H "Content-Type: application/json"
Document LIST
Request: GET
to https://readwise.io/api/v3/list/
Parameters: Usual query params:
Key | Type | Description | Required |
---|---|---|---|
id | string | The document's unique id. Using this parameter it will return just one document, if found. | no |
updatedAfter | string (formatted as ISO 8601 date) | Fetch only documents updated after this date | no |
location | string | The document's location, could be one of: new , later , shortlist , archive , feed |
no |
category | string | The document's category, could be one of: article , email , rss , highlight , note , pdf , epub , tweet , video |
no |
pageCursor | string | A string returned by a previous request to this endpoint. Use it to get the next page of documents if there are too many for one request. | no |
Response:
- Status code:
200
- List of documents:
{
"count": 2304,
"nextPageCursor": "01gm6kjzabcd609yepjrmcgz8a",
"results": [
{
"id": "01gwfvp9pyaabcdgmx14f6ha0",
"url": "https://readiwise.io/feed/read/01gwfvp9pyaabcdgmx14f6ha0",
"source_url": "https://www.driverlesscrocodile.com/values/ends-and-meanings-3-alasdair-macintyre-virtue-mortality-and-story-in-heroic-societies/",
"title": "Ends and Meanings (3): Alasdair MacIntyre virtue, mortality and story in heroic societies",
"author": "Stuart Patience",
"source": "Reader RSS",
"category": "rss",
"location": "feed",
"tags": {},
"site_name": "Driverless Crocodile",
"word_count": 819,
"created_at": "2023-03-26T21:02:51.618751+00:00",
"updated_at": "2023-03-26T21:02:55.453827+00:00",
"notes": "",
"published_date": "2023-03-22",
"summary": "Without … a place in the social order, ...",
"image_url": "https://i0.wp.com/www.driverlesscrocodile.com/wp-content/uploads/2019/10/cropped-driverlesscrocodile-icon-e1571123201159-4.jpg?fit=32%2C32&ssl=1",
"parent_id": null,
"reading_progress": 0.15,
},
{
"id": "01gkqtdz9xabcd5gt96khreyb",
"url": "https://readiwise.io/new/read/01gkqtdz9xabcd5gt96khreyb",
"source_url": "https://www.vanityfair.com/hollywood/2017/08/the-story-of-the-ducktales-theme-music",
"title": "The Story of the DuckTales Theme, History’s Catchiest Single Minute of Music",
"author": "Darryn King",
"source": "Reader add from import URL",
"category": "article",
"location": "new",
"tags": {},
"site_name": "Vanity Fair",
"word_count": 2678,
"created_at": "2022-12-08T02:53:29.639650+00:00",
"updated_at": "2022-12-13T20:37:42.544298+00:00",
"published_date": "2017-08-09",
"notes": "A sample note",
"summary": "A woo-hoo heard around the world.",
"image_url": "https://media.vanityfair.com/photos/598b1452f7f0a433bd4d149c/16:9/w_1280,c_limit/t-ducktales-woohoo-song.png",
"parent_id": null,
"reading_progress": 0.5,
},
{
"id": "01gkqt8nbms4t698abcdvcswvf",
"url": "https://readwise.io/new/read/01gkqt8nbms4t698abcdvcswvf",
"source_url": "https://www.vanityfair.com/news/2022/10/covid-origins-investigation-wuhan-lab",
"title": "COVID-19 Origins: Investigating a “Complex and Grave Situation” Inside a Wuhan Lab",
"author": "Condé Nast",
"source": "Reader add from import URL",
"category": "article",
"location": "new",
"tags": {},
"site_name": "Vanity Fair",
"word_count": 9601,
"created_at": "2022-12-08T02:50:35.662027+00:00",
"updated_at": "2023-03-22T13:29:41.827456+00:00",
"published_date": "2022-10-28",
"notes": "",
"summary": "The Wuhan Institute of Virology, the cutting-edge ...",
"image_url": "https://media.vanityfair.com/photos/63599642578d980751943b65/16:9/w_1280,c_limit/vf-1022-covid-trackers-site-story.jpg",
"parent_id": null,
"reading_progress": 0,
}
]
}
Usage/Examples:
- JavaScript
const token = "XXX"; // use your access token here
const fetchDocumentListApi = async (updatedAfter=null, location=null) => {
let fullData = [];
let nextPageCursor = null;
while (true) {
const queryParams = new URLSearchParams();
if (nextPageCursor) {
queryParams.append('pageCursor', nextPageCursor);
}
if (updatedAfter) {
queryParams.append('updatedAfter', updatedAfter);
}
if (location) {
queryParams.append('location', location);
}
console.log('Making export api request with params ' + queryParams.toString());
const response = await fetch('https://readwise.io/api/v3/list/?' + queryParams.toString(), {
method: 'GET',
headers: {
Authorization: `Token ${token}`,
},
});
const responseJson = await response.json();
fullData.push(...responseJson['results']);
nextPageCursor = responseJson['nextPageCursor'];
if (!nextPageCursor) {
break;
}
}
return fullData;
};
// Get all of a user's documents from all time
const allData = await fetchDocumentListApi();
// Get all of a user's archived documents
const archivedData = await fetchDocumentListApi(location='archive');
// Later, if you want to get new documents updated after some date, do this:
const docsAfterDate = new Date(Date.now() - 24 * 60 * 60 * 1000); // use your own stored date
const newData = await fetchDocumentListApi(docsAfterDate.toISOString());
import datetime
import requests # This may need to be installed from pip
token = 'XXX'
def fetch_reader_document_list_api(updated_after=None, location=None):
full_data = []
next_page_cursor = None
while True:
params = {}
if next_page_cursor:
params['pageCursor'] = next_page_cursor
if updated_after:
params['updatedAfter'] = updated_after
if location:
params['location'] = location
print("Making export api request with params " + str(params) + "...")
response = requests.get(
url="https://readwise.io/api/v3/list/",
params=params,
headers={"Authorization": f"Token {token}"}, verify=False
)
full_data.extend(response.json()['results'])
next_page_cursor = response.json().get('nextPageCursor')
if not next_page_cursor:
break
return full_data
# Get all of a user's documents from all time
all_data = fetch_reader_document_list_api()
# Get all of a user's archived documents
archived_data = fetch_reader_document_list_api(location='archive')
# Later, if you want to get new documents updated after some date, do this:
docs_after_date = datetime.datetime.now() - datetime.timedelta(days=1) # use your own stored date
new_data = fetch_reader_document_list_api(docs_after_date.isoformat())
$ curl -v https://readwise.io/api/v3/list/?location=later -H "Authorization: Token XXX" -H "Content-Type: application/json"
Rate Limiting
The default base rate is 20 requests per minute.
You can check Retry-After
header in the 429 response to get the number
of seconds to wait
for.