Folo Client SDK
The official JavaScript/TypeScript client for Folo - the modern RSS reader. This SDK makes it easy to build applications that interact with Folo's RSS management, reading history, and AI-powered features.
Why Use the Folo SDK?
- Cross-Platform: Works in browsers, Node.js, React Native, and other JavaScript environments
- Type-Safe: Full TypeScript support with auto-completion and error prevention
- Production Ready: Used by Folo's official apps with comprehensive error handling
Installation
pnpm add @follow-app/client-sdk
Quick Start
import { FollowClient } from '@follow-app/client-sdk'
// Connect to Folo
const follow = new FollowClient({
baseURL: 'https://api.follow.is',
credentials: 'include', // For web apps
})
// Subscribe to an RSS feed
await follow.subscriptions.create({
url: 'https://example.com/rss',
category: 'Technology',
})
// Get your latest unread articles
const articles = await follow.entries.list({
read: false,
limit: 20,
})
// Mark articles as read
await follow.entries.batch.read({
entryIds: articles.data.map((entry) => entry.id),
})
Core Features
📰 Feed Management
Subscribe to and manage RSS feeds with ease:
// Subscribe to feeds
await follow.subscriptions.create({
url: 'https://blog.example.com/rss',
category: 'Tech Blogs',
})
// Get all your subscriptions
const subscriptions = await follow.subscriptions.get()
// Import OPML files
await follow.subscriptions.import({ opmlContent })
📖 Reading Experience
Track and manage your reading:
// Get unread articles
const unread = await follow.entries.list({
read: false,
limit: 50,
})
// Mark articles as read
await follow.entries.batch.read({
entryIds: ['article-1', 'article-2'],
})
// Save articles to inbox
await follow.entries.inbox.add({
entryId: 'article-123',
note: 'Read later',
})
🤖 AI-Powered Features
Enhance your reading with AI:
// Get article summaries
const summary = await follow.ai.summary({
id: 'article-123',
})
// Chat with AI about your articles
const response = await follow.ai.chat({
messages: [
{ role: 'user', content: 'What are the key trends in this article?' },
],
})
// Get personalized recommendations
const recommendations = await follow.ai.recommendations()
📊 Analytics & Insights
Understand your reading habits:
// Get reading statistics
const stats = await follow.reads.stats()
// Track feed performance
const feedAnalytics = await follow.feeds.analytics({
feedId: 'feed-123',
})
Setup & Configuration
For Web Applications
import { FollowClient } from '@follow-app/client-sdk'
const follow = new FollowClient({
baseURL: 'https://api.follow.is',
credentials: 'include', // For cookie-based authentication
})
For Node.js Applications
import { FollowClient } from '@follow-app/client-sdk'
const follow = new FollowClient({
baseURL: 'https://api.follow.is',
headers: {
Authorization: 'Bearer your-api-token',
},
})
// Or set the token later
follow.setAuthToken('your-api-token')
For React Native
import { FollowClient } from '@follow-app/client-sdk'
const follow = new FollowClient({
baseURL: 'https://api.follow.is',
credentials: 'include',
headers: {
'User-Agent': 'YourApp/1.0.0',
},
})
Error Handling
The SDK provides clear error handling for common scenarios:
import { FollowAPIError, FollowAuthError } from '@follow-app/client-sdk'
try {
const articles = await follow.entries.list()
} catch (error) {
if (error instanceof FollowAuthError) {
// User needs to log in
console.log('Authentication required')
redirectToLogin()
} else if (error instanceof FollowAPIError) {
// Handle API errors (network, server, etc.)
console.log(`Error: ${error.message}`)
showErrorMessage(error.message)
}
}
error.message is what the server answered, fit to show. What the request asked for is kept apart in
error.request for logs and reports: its method, path, query and body, with values that authenticate
the user, such as a password, replaced. It never holds the request's headers.
Specific Error Codes
error.serverCode is the code of the server's answer, one of ExceptionCodeMap, also for an
authentication error, whose code is AUTH_REQUIRED. An answer that is not the API's, such as a
proxy's error page, has none.
import { ExceptionCodeMap, FollowAPIError } from '@follow-app/client-sdk'
try {
await follow.subscriptions.create({ feedId: '41234567890123456' })
} catch (error) {
if (error instanceof FollowAPIError && error.serverCode === ExceptionCodeMap.FeedNoFound) {
console.log('There is no feed with that id')
}
}
Profile Error Codes
Better Auth's update-user and sign-up/email answer a profile field they refuse with a 400 whose code names the field.
Better Auth's client resolves with the error instead of throwing, so read res.error.code:
| Code | Field | Refused value |
|---|---|---|
HANDLE_ALREADY_TAKEN | handle | Another user has the handle, in any letter case. |
HANDLE_INVALID | handle | A character other than A-Z, a-z, 0-9 or _. |
HANDLE_INVALID_CHARACTERS | handle | A space or a character that cannot be seen, such as U+200B. |
HANDLE_TOO_LONG | handle | More than 36 characters. |
NAME_CANNOT_BE_EMPTY | name | A blank name. |
NAME_TOO_LONG | name | More than 64 characters. |
IMAGE_TOO_LONG | image | More than 256 characters. |
BIO_TOO_LONG | bio | More than 256 characters. |
WEBSITE_TOO_LONG | website | More than 256 characters. |
The handle rules apply to a new or changed handle only: a handle saved before them keeps saving while a save sends it back as it is. Surrounding spaces are trimmed, and a blank handle clears it.
Common Use Cases
Building a Personal RSS Reader
// Set up the client
const follow = new FollowClient({
baseURL: 'https://api.follow.is',
credentials: 'include',
})
// Subscribe to feeds
await follow.subscriptions.create({
url: 'https://blog.openai.com/rss/',
category: 'AI & Technology',
})
// Get latest articles
const latest = await follow.entries.list({
read: false,
limit: 20,
})
// Display articles to user, then mark as read
await follow.entries.batch.read({
entryIds: latest.data.map((article) => article.id),
})
Content Curation Tool
// Import existing OPML subscription list
await follow.subscriptions.import({ opmlContent })
// Get articles from specific categories
const techNews = await follow.entries.list({
category: 'Technology',
limit: 100,
read: false,
})
// Filter by keywords
const aiArticles = techNews.data.filter(
(article) =>
article.title.toLowerCase().includes('ai') ||
article.content.toLowerCase().includes('artificial intelligence'),
)
// Save important articles to inbox
for (const article of aiArticles) {
await follow.entries.inbox.add({
entryId: article.id,
note: 'AI trend research',
})
}
AI-Enhanced Reading App
// Get user's reading recommendations
const recommendations = await follow.ai.chat({
messages: [
{
role: 'user',
content: 'What are the most important articles I should read today?',
},
],
})
// Get summary of long articles
const longArticles = await follow.entries.list({
read: false,
limit: 10,
})
for (const article of longArticles.data) {
const summary = await follow.ai.summary({
id: article.id,
})
console.log(`${article.title}\nSummary: ${summary.summary}`)
}
License
MIT License - see LICENSE file for details.
Contributing
We welcome contributions! Please see our contributing guide for details on:
- Setting up the development environment
- Running tests and linting
- Submitting pull requests
- Code style guidelines
Made with ❤️ by the Folo team