Public
Likeletterboxd
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data β all from the browser, and deployed in milliseconds.
A simple API to fetch the most recent diary entry from a Letterboxd user's diary page.
This API scrapes the Letterboxd diary page for user "pinjasaur" and returns the most recent film entry in a clean JSON format. Perfect for displaying your latest watched movie on a personal website or blog.
GET /
Returns the most recent diary entry as JSON.
GET /test
Returns a test page that displays the latest movie in a nice UI.
{ "title": "Long Story Short", "year": 2021, "rating": 3, "watchDate": "2026-01-15", "filmUrl": "https://letterboxd.com/pinjasaur/film/long-story-short-2021/", "scrapedAt": "2026-01-16T15:44:06.852Z" }
- title: Film title (string | null)
- year: Release year (number | null)
- rating: Star rating from 0.5 to 5 stars (number | null)
- watchDate: Date watched in YYYY-MM-DD format (string | null)
- filmUrl: Link to the film's Letterboxd page (string | null)
- scrapedAt: ISO timestamp of when the data was scraped (string)
fetch('https://your-val-town-url.web.val.run/')
.then(response => response.json())
.then(data => {
console.log(`Latest watched: ${data.title} (${data.year})`);
console.log(`Rating: ${data.rating}/5 stars`);
});
<div id="latest-movie"></div> <script> async function loadLatestMovie() { try { const response = await fetch('https://your-val-town-url.web.val.run/'); const movie = await response.json(); document.getElementById('latest-movie').innerHTML = ` <div class="movie-card"> <div style="width:150px;height:225px;background:#2c3440;display:flex;align-items:center;justify-content:center;color:#9ab;font-size:24px;">π¬</div> <h3>${movie.title} (${movie.year})</h3> <p>Rating: ${'β '.repeat(movie.rating || 0)}${'β'.repeat(5-(movie.rating || 0))}</p> <p>Watched: ${movie.watchDate}</p> <a href="${movie.filmUrl}" target="_blank">View on Letterboxd</a> </div> `; } catch (error) { console.error('Failed to load latest movie:', error); } } loadLatestMovie(); </script>
The API includes caching headers:
- Cache-Control:
public, max-age=300(5 minutes) - CORS: Enabled for all origins
The API returns appropriate HTTP status codes:
- 200: Success
- 404: No diary entries found
- 500: Server error (network issues, parsing errors, etc.)
Error responses include a descriptive message:
{ "error": "No diary entries found", "message": "The diary appears to be empty or the page structure has changed." }
- Built with TypeScript on Val Town
- Scrapes HTML from
https://letterboxd.com/pinjasaur/diary/ - Uses regex patterns to extract structured data
- Handles various edge cases and fallbacks
- Only works for public Letterboxd profiles
- Currently hardcoded to user "pinjasaur"
- Dependent on Letterboxd's HTML structure (may break if they change their layout)
- Rate limited by Letterboxd's servers
- No authentication or private diary support
- Poster images are not available due to dynamic loading on Letterboxd
Potential improvements could include:
- Support for multiple users via query parameters
- Pagination to get multiple recent entries
- Additional metadata extraction (genres, director, etc.)
- Webhook support for real-time updates
- Better error handling and retry logic