GuidesGOODREADS

Goodreads API without a key

Goodreads stopped issuing API keys on December 8, 2020, but every public shelf is still an RSS feed you can read from the browser: prefix https://proxy.cors.dev/ to https://www.goodreads.com/review/list_rss/USER_ID?shelf=read and parse the XML with DOMParser. It's free, needs no signup or key, returns 100 books per request and allows 600 requests per minute per client IP. Goodreads sends no Access-Control-Allow-Origin header, so the same fetch() without the proxy is blocked by CORS. For your own library, the CSV export is quicker.

What is left of the Goodreads API

The notice is still on the Goodreads developer pages: as of December 8, 2020, no new developer keys, and plans to retire the API. Keys that went 30 days without use were deactivated. Today /api redirects to the homepage and legacy XML endpoints such as /search/index.xml answer 401. Three kinds of public URL still work without a key, and none of them sends Access-Control-Allow-Origin.

Goodreads URLs through cors.dev, checked October 2026
URLReturnsSize
/review/list_rss/1?shelf=readRSS, 100 books with ratings and reviews582 KB
/review/list_rss/1?shelf=currently-readingRSS, 9 books41 KB
/review/list_rss/1?shelf=to-read&per_page=200RSS, 200 books821 KB
/review/list_rss/1?shelf=read&per_page=200502 response_too_large (1,330 KB upstream)-
/user/updates_rss/1RSS, 10 latest updates26 KB
/book/auto_complete?format=json&q=duneJSON, 5 books5.3 KB
/search/index.xml?q=dune401, the legacy API needs a key-

Read a shelf with fetch

A shelf feed lives at https://www.goodreads.com/review/list_rss/USER_ID?shelf=SHELF. Use the shelf slug from the profile: read, currently-reading, to-read or any custom shelf; shelf=%23ALL%23, or no shelf parameter, mixes every shelf. Prefix the URL with https://proxy.cors.dev/ and parse the text as XML.

fetchShelf(): read a public shelf from RSS
async function fetchShelf(userId, shelf = 'read', page = 1) {
  const feedUrl = `https://www.goodreads.com/review/list_rss/${userId}?shelf=${shelf}&per_page=100&page=${page}`;
  const response = await fetch(`https://proxy.cors.dev/${feedUrl}`, { credentials: 'omit' });
  if (response.status === 401) throw new Error('This Goodreads profile is private');
  if (!response.ok) {
    throw new Error(`Shelf request failed: ${response.headers.get('X-Cors-Error') ?? response.status}`);
  }

  const feed = new DOMParser().parseFromString(await response.text(), 'application/xml');
  if (feed.querySelector('parsererror')) throw new Error('The feed is not valid XML');

  return [...feed.querySelectorAll('item')].map((item) => {
    const text = (tag) => item.querySelector(tag)?.textContent.trim() ?? '';
    return {
      bookId: text('book_id'),
      title: text('title'),
      author: text('author_name'),
      rating: Number(text('user_rating')),
      readAt: text('user_read_at') ? new Date(text('user_read_at')) : null,
      cover: text('book_large_image_url'),
      shelves: text('user_shelves').split(', ').filter(Boolean),
      link: text('link'),
    };
  });
}

const books = await fetchShelf('1', 'read');
console.log(books.length, books[0]);

user_read_at and user_date_added are RFC 822 dates such as Tue, 18 Aug 2026 00:00:00 +0000, which new Date() parses. Render titles and reviews with textContent; the description element holds HTML.

Fields in each shelf item

Goodreads RSS item fields on the read shelf of user 1, checked October 2026
ElementContains
book_idGoodreads book ID, as in /book/show/ID
title, author_nameBook title and first author
user_ratingStars from 1 to 5; 0 when unrated (14 of 100 books)
user_read_atDate finished; empty when not set (17 of 100)
user_date_addedDate the book was shelved
user_shelvesCustom shelves, comma separated; empty when none
user_reviewReview text; empty when none (48 of 100)
average_ratingCommunity average, such as 4.38
book_publishedPublication year; sometimes empty
isbnISBN-10; empty on 33 of 100
book_small_image_url to book_large_image_urlCover image URLs in 4 sizes
book > num_pagesPage count

Big shelves and the 1 MiB limit

Feeds return 100 books by default. per_page goes up to 200; larger values silently fall back to 100. page=2, page=3 and so on walk the rest, and a page past the end is an empty feed of 1.1 KB. Reviews make items heavy: user 1's read shelf averages about 5.8 KB per book, so 200 books come to 1,330 KB, and the free tier, capped at 1 MiB, answers 502 with X-Cors-Error: response_too_large.

Goodreads feed size by page size for user 1, checked October 2026
QueryBooksSizeFree tier
shelf=read&per_page=5050303 KB200
shelf=read (default)100582 KB200
shelf=read&per_page=100&page=2100781 KB200
shelf=read&per_page=150150986 KB200
shelf=read&per_page=2002001,330 KB502 response_too_large
shelf=to-read&per_page=200200821 KB200
shelf=read&per_page=100&page=9901.1 KB200, empty feed
Load a whole shelf, 100 books per request
async function fetchWholeShelf(userId, shelf) {
  const books = [];
  for (let page = 1; page <= 100; page++) {
    const batch = await fetchShelf(userId, shelf, page);
    books.push(...batch);

    // A page with fewer than 100 books is the last one
    if (batch.length < 100) break;
  }
  return books;
}

const read = await fetchWholeShelf('1', 'read');
console.log(`${read.length} books, ${read.filter((book) => book.rating === 5).length} rated 5 stars`);

That loop read all 629 books on user 1's read shelf in 7 requests. If a page of long reviews still crosses 1 MiB, drop to per_page=50; Pro raises the cap to 6 MiB.

Find a user ID

  1. Open the profile

    Open the member profile or any of their shelves on goodreads.com.

  2. Copy the number

    The ID is the number in the URL: goodreads.com/user/show/1-otis-chandler is user 1. Shelf pages use the same number after /review/list/.

  3. Check that the profile is public

    A private profile answers 401 with "Sorry, that person's shelf is private", through the proxy too. An unknown ID answers 404.

A private shelf stays private: the free tier forwards only Accept, If-None-Match and If-Modified-Since upstream, never cookies.

Goodreads export: your library as CSV

For your own books the export beats any feed: one file, every shelf, no paging. It only runs on the desktop site.

  1. Open My Books

    Sign in on goodreads.com in a desktop browser and click My Books.

  2. Open Import and export

    In the left column, under Tools, click Import and export.

  3. Export Library

    Click Export Library. When the file is ready, a link "Your export from [date] - [time]" appears below the button and downloads goodreads_library_export.csv.

Each row is one book, with columns such as Book Id, Title, Author, ISBN13, My Rating, Average Rating, Number of Pages, Original Publication Year, Date Read, Date Added, Bookshelves, Exclusive Shelf, My Review, Private Notes and Read Count. The file never leaves your machine, so reading it in the browser needs no proxy.

Open Library, Hardcover and Google Books

  • Open Library: https://openlibrary.org/search.json?q=dune&limit=5 answered with Access-Control-Allow-Origin: * in 6.5 KB, so call it directly, no proxy. Results carry title, author_name, first_publish_year, isbn and cover_i; covers load from covers.openlibrary.org/b/id/COVER_I-M.jpg.
  • Hardcover: a GraphQL API at api.hardcover.app/v1/graphql with open CORS, but every query needs a token in the Authorization header and Hardcover's docs say queries must not run in the browser. Keep the token in a server or Worker, never in page code.
  • Google Books: the volumes API allows browser calls, but keyless requests share one daily quota. On our test day every keyless call returned 429 Quota exceeded, direct and through the proxy.

Free tier limits for Goodreads

  • Size: 1 MiB per decoded response; 100 books with reviews came to 582 to 781 KB.
  • Rate: 600 requests per minute per client IP, with no daily or monthly quota.
  • Methods: GET and HEAD, which is all these feeds need.
  • Errors: proxy failures carry an X-Cors-Error header; Goodreads 401 and 404 responses pass through unchanged.

Good questions.

Does Goodreads still have an API?

Not for new developers. Goodreads stopped issuing API keys on December 8, 2020 and says it plans to retire the API; goodreads.com/api now redirects to the homepage. Public shelf RSS feeds, update feeds and the search autocomplete still work without a key.

How do I get a Goodreads API key?

You cannot get a new one. Goodreads has issued none since December 2020, and keys unused for 30 days were deactivated. Read public shelves from RSS, or export your own library to CSV.

How do I export my Goodreads library?

On the desktop site open My Books, click Import and export under Tools, then Export Library. Goodreads builds a CSV with one row per book, including your rating, date read, shelves and review.

Why does fetch() to goodreads.com fail with a CORS error?

Goodreads sends no Access-Control-Allow-Origin header on its feeds or autocomplete, so the browser blocks the response. Prefix the URL with https://proxy.cors.dev/ and the response comes back with the header added; free, no signup, 600 requests per minute per client IP.

How many books does a Goodreads RSS feed return?

100 by default. per_page accepts up to 200; larger values fall back to 100. Add page=2, page=3 and so on until a page has fewer books. On the free tier keep 100 per page, because 200 reviewed books can pass the 1 MiB response cap.

Can I read a private Goodreads shelf?

No. A private profile answers 401, "Sorry, that person's shelf is private", to every anonymous request, including through the proxy. The owner can export the library to CSV instead.

What is the best free alternative to the Goodreads API?

Open Library. Its search.json endpoint needs no key and sends Access-Control-Allow-Origin: *, so it works straight from the browser. Hardcover has a richer GraphQL API, but its token belongs on a server.