An API you can curl, or open in a browser, to receive Bluesky data as markdown!
11
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix auth-required error message and replace jcsalterego examples

- lib/respond.ts: detect AuthRequired (401) errors and return a friendly
markdown notice instead of a raw error response
- app/cli/route.ts: fix /feed → /posts in examples; replace jcsalterego
with mackuba.eu; add note about private accounts
- app/page.tsx: replace jcsalterego with mackuba.eu in terminal section;
fix /feed → /posts example path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

jack 8323b2c6 0e65b3bd

+53 -23
+22 -18
app/cli/route.ts
··· 20 20 ## Quick start 21 21 22 22 curl ${base}/profile/j4ck.xyz 23 - curl ${base}/profile/jcsalterego.bsky.social 24 - curl ${base}/profile/jcsalterego.bsky.social/feed 23 + curl ${base}/profile/mackuba.eu 24 + curl ${base}/profile/mackuba.eu/posts 25 25 curl ${base}/profile/j4ck.xyz/followers 26 26 curl "${base}/search?q=atproto" 27 27 curl ${base}/trending ··· 30 30 31 31 ## Endpoints 32 32 33 - /profile/:handle Bio, stats, pinned post 34 - /profile/:handle/feed Recent posts (paginated) 35 - /profile/:handle/post/:rkey Single post with embeds 33 + /profile/:handle Bio, stats, pinned post 34 + /profile/:handle/posts Recent posts (paginated) 35 + /profile/:handle/post/:rkey Single post with embeds 36 36 /profile/:handle/post/:rkey/thread Full thread 37 - /profile/:handle/feed/:rkey Public custom feed 38 - /profile/:handle/likes Posts the user liked 39 - /profile/:handle/followers Follower list 40 - /profile/:handle/following Following list 41 - /search?q=:query Full-text post search 42 - /trending Trending topics right now 43 - /llms.txt Machine-readable API guide (for agents) 44 - /skill.md Agent skill file (Claude, Cursor, Windsurf…) 37 + /profile/:handle/feed/:rkey Public custom feed 38 + /profile/:handle/likes Posts the user liked 39 + /profile/:handle/followers Follower list 40 + /profile/:handle/following Following list 41 + /search?q=:query Full-text post search 42 + /trending Trending topics right now 43 + /llms.txt Machine-readable API guide (for agents) 44 + /skill.md Agent skill file (Claude, Cursor, Windsurf…) 45 45 46 46 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 47 47 48 48 ## More examples 49 49 50 - # A specific post 51 - curl ${base}/profile/j4ck.xyz/post/$(echo "find a rkey at bsky.app/profile/j4ck.xyz") 52 - 53 50 # Thread 54 - curl ${base}/profile/jcsalterego.bsky.social/post/<rkey>/thread 51 + curl ${base}/profile/mackuba.eu/post/<rkey>/thread 55 52 56 53 # Custom feed 57 54 curl ${base}/profile/bsky.app/feed/whats-hot 58 55 59 56 # Pagination 60 - curl "${base}/profile/jcsalterego.bsky.social/feed?limit=5" 57 + curl "${base}/profile/mackuba.eu/posts?limit=5" 61 58 62 59 # Search 63 60 curl "${base}/search?q=%23rust+lang" 64 61 curl "${base}/search?q=open+source" 62 + 63 + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 64 + 65 + ## Note on private accounts 66 + 67 + Some Bluesky users require sign-in to view their content. 68 + bsky.md only has access to public content — those accounts will return a clear notice. 65 69 66 70 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 67 71
+2 -2
app/page.tsx
··· 72 72 73 73 const ENDPOINTS = [ 74 74 { path: '/profile/:handle', desc: 'Bio, stats, avatar/banner', example: '/profile/bsky.app' }, 75 - { path: '/profile/:handle/posts', desc: 'Paginated original posts', example: '/profile/bsky.app/posts' }, 75 + { path: '/profile/:handle/posts', desc: 'Recent posts (paginated)', example: '/profile/bsky.app/posts' }, 76 76 { path: '/profile/:handle/post/:rkey', desc: 'Single post with embeds', example: '/profile/bsky.app/post/3lhreomsy5k2x' }, 77 77 { path: '/…/post/:rkey/thread', desc: 'Full self-reply thread', example: '/profile/bsky.app/post/3lhreomsy5k2x/thread' }, 78 78 { path: '/profile/:handle/feed/:rkey', desc: 'Public custom feed', example: '/profile/bsky.app/feed/whats-hot' }, ··· 351 351 'curl https://bsky-md.vercel.app/profile/j4ck.xyz', 352 352 '', 353 353 '# Recent posts', 354 - 'curl https://bsky-md.vercel.app/profile/jcsalterego.bsky.social/feed', 354 + 'curl https://bsky-md.vercel.app/profile/mackuba.eu/posts', 355 355 '', 356 356 '# Followers', 357 357 'curl https://bsky-md.vercel.app/profile/j4ck.xyz/followers',
+29 -3
lib/respond.ts
··· 49 49 50 50 /** 51 51 * Wrap a route handler with consistent error handling. 52 - * Maps Bluesky API 400 "not found" errors to HTTP 404. 52 + * Maps Bluesky API errors to appropriate HTTP responses. 53 + * Auth-required accounts get a friendly markdown notice instead of a raw error. 53 54 */ 54 55 export async function handleRoute( 55 56 fn: () => Promise<Response>, ··· 59 60 } catch (err: unknown) { 60 61 const e = err as { message?: string; status?: number; error?: string } 61 62 const message = e?.message ?? 'An unexpected error occurred' 63 + let status = e?.status ?? 500 64 + 65 + // Account requires sign-in — return a helpful markdown notice 66 + if ( 67 + status === 401 || 68 + e?.error === 'AuthRequired' || 69 + /auth(entication)? required/i.test(message) 70 + ) { 71 + const body = `# 🔒 Sign-in required 72 + 73 + This Bluesky account has enabled **"Require sign-in to view"** in their privacy settings. 74 + 75 + bsky.md only has access to public content and cannot fetch posts from accounts that require authentication. 76 + 77 + You can view their profile directly on Bluesky: 78 + https://bsky.app 79 + 80 + > **Note:** This is a privacy choice made by the account holder, not an error with bsky.md. 81 + ` 82 + return new Response(body, { 83 + status: 403, 84 + headers: { 85 + 'Content-Type': 'text/markdown; charset=utf-8', 86 + ...CORS_HEADERS, 87 + }, 88 + }) 89 + } 62 90 63 91 // Bluesky returns 400 InvalidRequest for most "not found" cases. 64 - // Map those to 404 so clients can distinguish missing resources from bad requests. 65 - let status = e?.status ?? 500 66 92 if ( 67 93 status === 400 && 68 94 (e?.error === 'InvalidRequest' || e?.error === 'NotFound') &&