xrpc-to-akko#
A Mastodon/Akkoma-compatible API bridge that translates Mastodon client API requests into AT Protocol (Bluesky) XRPC calls. This allows standard Mastodon/Akkoma clients to interact with Bluesky.
Architecture#
┌──────────────────┐ Mastodon API ┌──────────────────┐ XRPC / atproto ┌────────────────┐
│ Mastodon Client │ ──────────────────▶ │ xrpc-to-akko │ ──────────────────▶ │ Bluesky PDS / │
│ (Elk, Tusky, …) │ ◀────────────────── │ (this bridge) │ ◀────────────────── │ AppView │
└──────────────────┘ └──────────────────┘ └────────────────┘
The bridge presents itself as an Akkoma 3.13.3-compatible instance, supporting both Mastodon clients and Pleroma/Akkoma-aware clients (akkoma-fe, Husky, etc.). Instance metadata, nodeinfo, and Pleroma-specific API endpoints are provided.
Translation Layers#
| Layer |
Bluesky side |
Mastodon side |
| Auth |
com.atproto.server.createSession (app-password) |
OAuth password & authorization-code grants |
| Accounts |
app.bsky.actor.* |
Mastodon Account objects |
| Statuses |
app.bsky.feed.post records |
Mastodon Status objects (including quote posts) |
| Timelines |
app.bsky.feed.getTimeline / getFeed |
Paginated status lists |
| Notifications |
app.bsky.notification.listNotifications |
Mastodon notification types |
| Search |
app.bsky.actor.searchActors / app.bsky.feed.searchPosts |
v1 & v2 search |
| Media |
com.atproto.repo.uploadBlob |
Mastodon media attachments |
| Relationships |
app.bsky.graph.* (follow/mute/block) |
Mastodon relationship actions |
Proxy Implementation#
All authenticated app.bsky.* requests are routed through the user's PDS with the atproto-proxy: did:web:api.bsky.app#bsky_appview header, causing the PDS to forward requests to the Bluesky AppView.
Public (unauthenticated) reads use https://public.api.bsky.app directly.
Identity Mapping#
| Concept |
Representation |
| Account ID |
User DID (did:plc:…) |
| Status ID |
Base64url-encoded AT URI |
acct |
Bluesky handle (e.g., alice.bsky.social) |
Setup#
Requirements#
- Python ≥ 3.11
- uv package manager
Installation#
# Install dependencies
uv sync
# Run the server
uv run uvicorn main:app --reload --host 0.0.0.0 --port 8000
Point your Mastodon client to http://localhost:8000.
Authentication#
Use your Bluesky handle (e.g., alice.bsky.social) as username and, ideally, an app password as password. Generate app passwords at Bluesky Settings.
Supported authentication flows:
- Password grant:
POST /oauth/token with grant_type=password
- Authorization code: Browser-based
GET /oauth/authorize → login form → redirect with code → exchange via POST /oauth/token
Both routes support manually specifying alternative PDSes if necessary. If using a client that requires a password grant, such as akkoma-fe, specify your username as handle:pds to use a custom PDS.
Configuration#
Configure via environment variables:
| Variable |
Default |
Description |
DEBUG |
false |
Enable Litestar debug mode |
BSKY_PDS |
(auto-discover) |
Force requests to specific PDS URL |
BSKY_ENTRYWAY |
https://bsky.social |
Entryway for createSession |
BSKY_PUBLIC_API |
https://public.api.bsky.app |
Public AppView for unauthenticated reads |
BSKY_APPVIEW_DID |
did:web:api.bsky.app#bsky_appview |
Service DID for atproto-proxy header |
# Custom PDS example
BSKY_PDS=https://pds.example.com uv run uvicorn main:app --host 0.0.0.0 --port 8000
API Endpoints#
OAuth & Apps#
| Method |
Path |
Notes |
POST |
/api/v1/apps |
Synthetic app credentials |
GET |
/oauth/authorize |
HTML login form |
POST |
/oauth/authorize/submit |
Form submission handler |
POST |
/oauth/token |
Password & authorization-code grants |
POST |
/oauth/revoke |
Deletes session |
Instance & Discovery#
| Method |
Path |
Notes |
GET |
/api/v1/instance |
Akkoma-compatible instance info |
GET |
/api/v2/instance |
Mastodon v2 instance info |
GET |
/.well-known/nodeinfo |
Nodeinfo discovery |
GET |
/nodeinfo/2.0, /nodeinfo/2.0.json |
Nodeinfo 2.0 |
GET |
/nodeinfo/2.1.json |
Nodeinfo 2.1 |
Accounts#
| Method |
Path |
Notes |
GET |
/api/v1/accounts/verify_credentials |
Current user profile |
GET |
/api/v1/accounts/lookup |
Lookup by acct |
GET |
/api/v1/accounts/search |
Search accounts by query |
GET |
/api/v1/accounts/relationships |
Follow/mute/block status |
GET |
/api/v1/accounts/{id} |
Profile by DID or handle |
GET |
/api/v1/accounts/{id}/statuses |
User's posts |
GET |
/api/v1/accounts/{id}/followers |
Follower list |
GET |
/api/v1/accounts/{id}/following |
Following list |
POST |
/api/v1/accounts/{id}/follow |
Follow |
POST |
/api/v1/accounts/{id}/unfollow |
Unfollow |
POST |
/api/v1/accounts/{id}/mute |
Mute |
POST |
/api/v1/accounts/{id}/unmute |
Unmute |
POST |
/api/v1/accounts/{id}/block |
Block |
Statuses#
| Method |
Path |
Notes |
GET |
/api/v1/statuses/{id} |
Single status |
GET |
/api/v1/statuses/{id}/context |
Thread (ancestors + descendants) |
POST |
/api/v1/statuses |
Create post (with quote-post support) |
DELETE |
/api/v1/statuses/{id} |
Delete post |
POST |
/api/v1/statuses/{id}/favourite |
Like |
POST |
/api/v1/statuses/{id}/unfavourite |
Unlike |
POST |
/api/v1/statuses/{id}/reblog |
Repost |
POST |
/api/v1/statuses/{id}/unreblog |
Undo repost |
POST |
/api/v1/statuses/{id}/bookmark |
Stub (no Bluesky equivalent) |
POST |
/api/v1/statuses/{id}/unbookmark |
Stub |
GET |
/api/v1/statuses/{id}/favourited_by |
Who liked |
GET |
/api/v1/statuses/{id}/reblogged_by |
Who reposted |
Timelines#
| Method |
Path |
Notes |
GET |
/api/v1/timelines/home |
Authenticated home feed |
GET |
/api/v1/timelines/public |
"What's Hot" feed as public timeline |
GET |
/api/v1/timelines/tag/{tag} |
Hashtag search results |
GET |
/api/v1/timelines/list/{id} |
Stub (empty) |
GET |
/api/v1/timelines/bubble |
Akkoma bubble timeline (stub) |
GET |
/api/v1/timelines/direct |
Direct timeline (stub, empty) |
Notifications#
| Method |
Path |
Notes |
GET |
/api/v1/notifications |
List notifications |
GET |
/api/v1/notifications/{id} |
Single notification |
POST |
/api/v1/notifications/clear |
Clear all |
POST |
/api/v1/notifications/{id}/dismiss |
Dismiss one |
Search#
| Method |
Path |
Notes |
GET |
/api/v2/search |
Accounts, statuses, and hashtags |
GET |
/api/v1/search |
Legacy search (same backend) |
| Method |
Path |
Notes |
POST |
/api/v1/media |
Upload blob to Bluesky |
POST |
/api/v2/media |
Same, returns 202 Accepted |
GET |
/api/v1/media/{id} |
Retrieve media metadata |
Pleroma/Akkoma Compatibility#
These endpoints support Pleroma-aware clients (akkoma-fe, Husky, etc.). Most return stub responses with sensible defaults.
| Method |
Path |
Notes |
GET |
/api/pleroma/frontend_configurations |
akkoma-fe boot config |
GET |
/instance/panel.html |
Static instance panel |
GET |
/static/config.json, /static/stickers.json |
Static config stubs |
GET |
/api/v1/pleroma/emoji |
Custom emoji (empty) |
GET |
/api/v1/pleroma/healthcheck |
Health check |
GET |
/api/v1/pleroma/captcha |
Captcha (disabled) |
POST |
/api/v1/pleroma/notifications/read |
Mark read (stub) |
POST |
/api/v1/pleroma/accounts/{id}/subscribe |
Subscribe (stub) |
POST |
/api/v1/pleroma/accounts/{id}/unsubscribe |
Unsubscribe (stub) |
GET |
/api/v1/pleroma/accounts/{id}/favourites |
Account favourites (stub) |
GET/PUT |
/api/v1/pleroma/mascot |
Mascot (stub) |
PUT |
/api/pleroma/notification_settings |
Notification settings (stub) |
GET/PUT/DELETE |
/api/v1/pleroma/statuses/{id}/reactions/{emoji} |
Emoji reactions (stub) |
PUT |
/api/v1/statuses/{id}/emoji_reactions/{emoji} |
Fedibird-compat reactions (stub) |
POST |
/api/v1/pleroma/conversations/read |
Conversations (stub) |
GET/POST |
/api/v1/pleroma/backups |
Backups (stub) |
Mastodon Stubs#
Empty data endpoints: /api/v1/custom_emojis, /api/v1/filters, /api/v2/filters, /api/v1/lists, /api/v1/markers, /api/v1/announcements, /api/v1/preferences, /api/v1/followed_tags, /api/v1/bookmarks, /api/v1/favourites, /api/v1/mutes, /api/v1/blocks, /api/v1/domain_blocks, /api/v1/endorsements, /api/v1/conversations, /api/v1/trends/tags, /api/v1/trends/statuses, /api/v1/trends/links, /api/v1/suggestions
Development#
# Install with dev dependencies
uv sync --group dev
# Run tests
uv run pytest
# Run with coverage
uv run pytest --cov=app
# Type checking (pyrefly)
uv run pyrefly check app/
Limitations#
- No streaming – WebSocket/SSE streaming is not implemented
- No DMs –
chat.bsky.* APIs are not bridged
- Approximate pagination – Bluesky's opaque cursors require caching for Mastodon pagination
- Stub features – Bookmarks, polls, and several other Mastodon features have no Bluesky equivalent
- Session storage – Sessions are maintained in memory only
- Single-process – Session and cursor state is per-process