Skip to main content
Glama
raindrop.service.ts12.4 kB
// Simple, clean openapi-fetch REST client import createClient from 'openapi-fetch'; import type { components, paths } from '../types/raindrop.schema.js'; import { createLogger } from '../utils/logger.js'; type Bookmark = components['schemas']['Bookmark']; type Collection = components['schemas']['Collection']; type Highlight = components['schemas']['Highlight']; export default class RaindropService { private client; constructor(token?: string) { this.client = createClient<paths>({ baseUrl: 'https://api.raindrop.io/rest/v1', headers: { Authorization: `Bearer ${token || process.env.RAINDROP_ACCESS_TOKEN}`, }, }); this.client.use({ onRequest({ request }) { if (process.env.NODE_ENV === 'development') { // Use project logger instead of console to avoid polluting STDIO const logger = createLogger('raindrop-service'); logger.debug(`${request.method} ${request.url}`); } return request; }, onResponse({ response }) { if (!response.ok) { let errorMsg = `API Error: ${response.status} ${response.statusText}`; if (response.status === 401) { errorMsg += '. Check your RAINDROP_ACCESS_TOKEN'; } else if (response.status === 429) { errorMsg += '. Rate limited - wait before making more requests'; } throw new Error(errorMsg); } return response; } }); } /** * Fetch all collections * Raindrop.io API: GET /collections */ async getCollections(): Promise<Collection[]> { const { data } = await this.client.GET('/collections'); return [...(data?.items || [])]; } /** * Fetch a single collection by ID * Raindrop.io API: GET /collection/{id} */ async getCollection(id: number): Promise<Collection> { const { data } = await this.client.GET('/collection/{id}', { params: { path: { id } } }); if (!data?.item) throw new Error('Collection not found'); return data.item; } /** * Fetch child collections for a parent collection * Raindrop.io API: GET /collections/{parentId}/childrens */ async getChildCollections(parentId: number): Promise<Collection[]> { const { data } = await this.client.GET('/collections/{parentId}/childrens', { params: { path: { parentId } } }); return [...(data?.items || [])]; } /** * Create a new collection * Raindrop.io API: POST /collection */ async createCollection(title: string, isPublic = false): Promise<Collection> { const { data } = await this.client.POST('/collection', { body: { title, public: isPublic } }); if (!data?.item) throw new Error('Failed to create collection'); return data.item; } /** * Update a collection * Raindrop.io API: PUT /collection/{id} */ async updateCollection(id: number, updates: Partial<Collection>): Promise<Collection> { const { data } = await this.client.PUT('/collection/{id}', { params: { path: { id } }, body: updates }); if (!data?.item) throw new Error('Failed to update collection'); return data.item; } /** * Delete a collection * Raindrop.io API: DELETE /collection/{id} */ async deleteCollection(id: number): Promise<void> { await this.client.DELETE('/collection/{id}', { params: { path: { id } } }); } /** * Share a collection * Raindrop.io API: PUT /collection/{id}/sharing */ async shareCollection(id: number, level: string, emails?: string[]): Promise<{ link: string; access: any[] }> { const body: any = { level }; if (emails) body.emails = emails; const { data } = await this.client.PUT('/collection/{id}/sharing', { params: { path: { id } }, body }); return { link: data?.link || '', access: [...(data?.access || [])] }; } /** * Fetch bookmarks (search, filter, etc) * Raindrop.io API: GET /raindrops/{collectionId} or /raindrops/0 */ async getBookmarks(params: { search?: string; collection?: number; tags?: string[]; important?: boolean; page?: number; perPage?: number; sort?: string; tag?: string; duplicates?: boolean; broken?: boolean; highlight?: boolean; domain?: string; } = {}): Promise<{ items: Bookmark[]; count: number }> { const query: any = {}; if (params.search) query.search = params.search; if (params.tags) query.tag = params.tags.join(','); if (params.tag) query.tag = params.tag; if (params.important !== undefined) query.important = params.important; if (params.page) query.page = params.page; if (params.perPage) query.perpage = params.perPage; if (params.sort) query.sort = params.sort; if (params.duplicates !== undefined) query.duplicates = params.duplicates; if (params.broken !== undefined) query.broken = params.broken; if (params.highlight !== undefined) query.highlight = params.highlight; if (params.domain) query.domain = params.domain; const endpoint = params.collection ? '/raindrops/{id}' : '/raindrops/0'; const options = params.collection ? { params: { path: { id: params.collection }, query } } : { params: { query } }; const { data } = await (this.client as any).GET(endpoint, options); return { items: data?.items || [], count: data?.count || 0 }; } /** * Fetch a single bookmark by ID * Raindrop.io API: GET /raindrop/{id} */ async getBookmark(id: number): Promise<Bookmark> { const { data } = await this.client.GET('/raindrop/{id}', { params: { path: { id } } }); if (!data?.item) throw new Error('Bookmark not found'); return data.item; } /** * Create a new bookmark * Raindrop.io API: POST /raindrop */ async createBookmark(collectionId: number, bookmark: { link: string; title?: string; excerpt?: string; tags?: string[]; important?: boolean; }): Promise<Bookmark> { const { data } = await this.client.POST('/raindrop', { body: { link: bookmark.link, ...(bookmark.title && { title: bookmark.title }), ...(bookmark.excerpt && { excerpt: bookmark.excerpt }), ...(bookmark.tags && { tags: bookmark.tags }), important: bookmark.important || false, collection: { $id: collectionId }, pleaseParse: {} } }); if (!data?.item) throw new Error('Failed to create bookmark'); return data.item; } /** * Update a bookmark * Raindrop.io API: PUT /raindrop/{id} */ async updateBookmark(id: number, updates: Partial<Bookmark>): Promise<Bookmark> { const { data } = await this.client.PUT('/raindrop/{id}', { params: { path: { id } }, body: updates }); if (!data?.item) throw new Error('Failed to update bookmark'); return data.item; } /** * Delete a bookmark * Raindrop.io API: DELETE /raindrop/{id} */ async deleteBookmark(id: number): Promise<void> { await this.client.DELETE('/raindrop/{id}', { params: { path: { id } } }); } /** * Batch update bookmarks * Raindrop.io API: PUT /raindrops */ async batchUpdateBookmarks(ids: number[], updates: { tags?: string[]; collection?: number; important?: boolean; broken?: boolean; }): Promise<boolean> { const body: any = { ids }; if (updates.tags) body.tags = updates.tags; if (updates.collection) body.collection = { $id: updates.collection }; if (updates.important !== undefined) body.important = updates.important; if (updates.broken !== undefined) body.broken = updates.broken; const { data } = await this.client.PUT('/raindrops', { body }); return !!data?.result; } /** * Fetch tags for a collection or all * Raindrop.io API: GET /tags/{collectionId} or /tags/0 */ async getTags(collectionId?: number): Promise<{ _id: string; count: number }[]> { const endpoint = collectionId ? '/tags/{collectionId}' : '/tags/0'; const options = collectionId ? { params: { path: { id: collectionId } } } : undefined; const { data } = await (this.client as any).GET(endpoint, options); return data?.items || []; } /** * Fetch tags for a specific collection * Raindrop.io API: GET /tags/{collectionId} */ async getTagsByCollection(collectionId: number): Promise<{ _id: string; count: number }[]> { return this.getTags(collectionId); } /** * Delete tags from a collection * Raindrop.io API: DELETE /tags/{collectionId} */ async deleteTags(collectionId: number | undefined, tags: string[]): Promise<boolean> { const endpoint = collectionId ? '/tags/{collectionId}' : '/tags/0'; const options = { ...(collectionId && { params: { path: { id: collectionId } } }), body: { tags } }; const { data } = await (this.client as any).DELETE(endpoint, options); return !!data?.result; } /** * Rename a tag in a collection * Raindrop.io API: PUT /tags/{collectionId} */ async renameTag(collectionId: number | undefined, oldName: string, newName: string): Promise<boolean> { const endpoint = collectionId ? '/tags/{collectionId}' : '/tags/0'; const options = { ...(collectionId && { params: { path: { id: collectionId } } }), body: { from: oldName, to: newName } }; const { data } = await (this.client as any).PUT(endpoint, options); return !!data?.result; } /** * Merge tags in a collection * Raindrop.io API: PUT /tags/{collectionId} */ async mergeTags(collectionId: number | undefined, tags: string[], newName: string): Promise<boolean> { const endpoint = collectionId ? '/tags/{collectionId}' : '/tags/0'; const options = { ...(collectionId && { params: { path: { id: collectionId } } }), body: { tags, to: newName } }; const { data } = await (this.client as any).PUT(endpoint, options); return !!data?.result; } /** * Fetch user info * Raindrop.io API: GET /user */ async getUserInfo(): Promise<{ email: string;[key: string]: any }> { const { data } = await this.client.GET('/user'); if (!data?.user) throw new Error('User not found'); return data.user; } /** * Fetch highlights for a specific bookmark * Raindrop.io API: GET /raindrop/{id}/highlights */ async getHighlights(raindropId: number): Promise<Highlight[]> { const { data } = await this.client.GET('/raindrop/{id}/highlights', { params: { path: { id: raindropId } } }); if (!data?.items) throw new Error('No highlights found'); return [...data.items]; } /** * Fetch all highlights across all bookmarks * Raindrop.io API: GET /raindrops/0 */ async getAllHighlights(): Promise<Highlight[]> { const { data } = await this.client.GET('/raindrops/0'); if (!data?.items) return []; return data.items.flatMap((bookmark: any) => Array.isArray(bookmark.highlights) ? bookmark.highlights : []); } /** * Create a highlight for a bookmark * Raindrop.io API: POST /highlights */ async createHighlight(bookmarkId: number, highlight: { text: string; note?: string; color?: string; }): Promise<Highlight> { const { data } = await this.client.POST('/highlights', { body: { ...highlight, raindrop: { $id: bookmarkId }, color: (highlight.color as any) || 'yellow' } }); if (!data?.item) throw new Error('Failed to create highlight'); return data.item; } /** * Update a highlight * Raindrop.io API: PUT /highlights/{id} */ async updateHighlight(id: number, updates: { text?: string; note?: string; color?: string; }): Promise<Highlight> { const { data } = await this.client.PUT('/highlights/{id}', { params: { path: { id } }, body: { ...(updates.text && { text: updates.text }), ...(updates.note && { note: updates.note }), ...(updates.color && { color: updates.color as any }) } }); if (!data?.item) throw new Error('Failed to update highlight'); return data.item; } /** * Delete a highlight * Raindrop.io API: DELETE /highlights/{id} */ async deleteHighlight(id: number): Promise<void> { await this.client.DELETE('/highlights/{id}', { params: { path: { id } } }); } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/adeze/raindrop-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server