An Akkoma/Mastodon compatible API bridge that translates Mastodon/Akkoma client API requests into ATProto XRPC calls.
4
fork

Configure Feed

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

Python 100.0%
Other 0.1%
5 1 0

Clone this repository

https://tangled.org/fizz.allura.moe/xrpc-to-akko https://tangled.org/did:plc:opevav6c7osvmoxffyolutkn/xrpc-to-akko
git@tangled.org:fizz.allura.moe/xrpc-to-akko git@tangled.org:did:plc:opevav6c7osvmoxffyolutkn/xrpc-to-akko

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz
README.md

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
Method Path Notes
GET /api/v2/search Accounts, statuses, and hashtags
GET /api/v1/search Legacy search (same backend)

Media#

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 DMschat.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