You paste an x.com link into Discord and get a sad little card: one image if you are lucky, no video playback, no poll results, no quote tweet. The fix, as millions of users know it, is almost embarrassingly small - swap the domain. Put fx before twitter.com, fixup before x.com, or fx before bsky.app, and suddenly the video plays inline, the poll renders, the translation appears. FxEmbed is the machine behind that magic trick, and its repository is the home of three projects you have probably used without realizing they share one codebase: FxTwitter, FixupX, and FxBluesky.
What the README says in one line - “Embed videos, polls, quotes, translations, & more on Discord, Telegram, and others!” - undersells the engineering. FxEmbed is a TypeScript application deployed as a Cloudflare Worker, built on the Hono router, with i18next for localization and zod-powered OpenAPI validation for its JSON API. It serves several distinct jobs from one deployment: bot-facing embed HTML for chat platforms, a documented v2 JSON API for developers, RSS/Atom profile feeds, direct media links, and a multi-provider “Atmosphere” API covering X, Bluesky, TikTok, Instagram, Mastodon, and Threads. It is MIT-licensed, maintained by dangered wolf and a crowd of contributors, and it runs at serious scale on the public instances.
The source deserves a tour because it is a working answer to a question most side projects never survive: how do you build a reliable service on top of platforms that are actively hostile to you? X gates its GraphQL endpoints behind rate limits and transaction IDs. TikTok signs its app API in native code. Instagram requires session cookies that expire and burn. FxEmbed’s answer - a realm-based router, a shared provider layer with pluggable transports, weighted fallbacks, and rotating credential pools - is documented honestly in its own AGENTS.md, and reading it teaches you more about production web scraping and API design than most tutorials ever will.
High-level overview: one Hono worker rewrites every incoming URL by Host header into a realm; the realm routers converge on a shared status embed builder, which renders og: meta through photo, video, and instant-view renderers and fetches content through the @fxembed/atmosphere data layer and its platform providers.
Reading the overview from left to right: everything enters through src/worker.ts, a single Hono application whose custom getPath inspects the hostname and rewrites the request onto a realm prefix - /twitter, /bluesky, /tiktok, /instagram, plus the three API realms. A read-through cache middleware (src/caches.ts) wraps every realm before routing. The four embed realms all funnel into one builder, src/embed/status.ts, which knows the difference between a Tweet, a Bluesky post, a TikTok video, and an Instagram post via a DataProvider enum, then hands the result to the renderers in src/render/. On the right, packages/atmosphere is the data layer: a workspace package exporting transports, a proxy-relay client, and the per-platform providers that actually talk to X, Bluesky, TikTok, and Instagram. Keep that shape in mind - one edge, seven realms, one embed brain, many providers - because the rest of the post zooms into each box.
Why You Need This
The core problem is that platforms deliberately cripple their own link previews. X restricts its API and serves crawlers a bare-bones card; videos hosted on video.twimg.com will not play inside Discord’s embed container if you link them directly; polls, quote tweets, multi-image galleries, and article previews simply do not survive the default pipeline. FxEmbed rebuilds the preview from the platform’s own data and serves the crawler a page whose og: metadata says exactly what the chat client wants to hear - a playable og:video:url, gallery images stitched into one canvas, poll options with counts, translated text for localized clients.
The mechanism only works because of a subtle property of chat platforms: when you paste a link, it is the platform’s crawler that fetches it, not you. FxEmbed exploits that by splitting its audience. A request whose User-Agent matches its bot regex (Discordbot, TelegramBot, WhatsApp, iFrame resolvers, and an extensive list in src/constants.ts) gets the full embed HTML; a human hitting the same URL gets a clean 302 redirect to the original post on x.com or bsky.app. Same link, two audiences, handled in the first few lines of the status route.
Developers get a second, less visible product: the JSON API. The api realm exposes a v2 OpenAPI-documented surface - statuses, profiles, search, trends, typeahead - that requires nothing but a descriptive User-Agent header, and it publishes its own openapi.json so clients can be generated mechanically. There are also oEmbed endpoints, RSS/Atom profile feeds (/:handle/feed.xml, media feeds), and direct-media domains where appending .mp4 or .jpg to a status URL streams the file itself - the trick behind d.fxtwitter.com download links.
Finally, self-hosters get a deployment story that is one container rather than a microservice zoo. The Docker image runs the actual Workers runtime (workerd) through Wrangler on port 8787, realm routing happens by Host header, and branding - names, colors, domains - is configurable per realm through branding.json. One codebase, many branded front doors.
How It Works
The detailed diagram maps the real modules of the repository, from the Host-header rewrite at the edge down to the credential pool behind a single tweet fetch.
Detailed architecture: the Hono worker dispatches by Host header into realm routers; Twitter, Bluesky, TikTok, and Instagram routes feed handleStatus, which renders through photo, video, instant-view, and mosaic helpers; data arrives via @fxembed/atmosphere transports and providers, with the Twitter stack zoomed into its GraphQL orchestrator, fetch layer, account proxy, and encrypted credential pool.
Understanding the Architecture
One worker, many realms. The entry point src/worker.ts creates a Hono app with a custom getPath that maps the request hostname onto a realm: API hosts land on /api and /blueskyapi, the multi-provider Atmosphere host on /atmosphere, and the standard domains on /twitter, /bluesky, /tiktok, or /instagram. The domain lists come from environment variables via src/constants.ts, which is why connecting a new branded domain is a config change, not a code change. Middleware applies response headers, logging, per-realm branding lookups (src/helpers/branding.ts), and the cache layer in src/caches.ts - a Cloudflare Cache API read-through whose cache key is varied by bot type (&telegram, &discord, &multibot) so different clients never see each other’s quirks, with PURGE and DELETE honored as cache-busting methods.
The embed pipeline. Every embed realm eventually calls handleStatus in src/embed/status.ts with a DataProvider discriminator. Before that, the status route (src/realms/twitter/routes/status.ts) assembles an InputFlags bag from the hostname and URL: direct media requests (the d. domains or a .mp4/.jpg suffix), text-only embeds, forced instant view, gallery mode, force-mosaic. handleStatus then produces render instructions through src/render/photo.ts, src/render/video.ts - which clamps dimensions up or down because Discord renders oversized videos small and undersized ones smaller - and src/render/instantview.ts for Telegram’s instant-view templates. Supporting cast includes src/helpers/mosaic.ts, the multi-image combiner that stitches up to four photos into one canvas, plus quote handling, AI-assisted and standard translation helpers, and i18next with ICU message format for localized strings.
@fxembed/atmosphere, the shared data layer. The packages/atmosphere workspace package is the architectural center of gravity. It defines a unified SocialThread envelope so a Tweet, a skeet, and a TikTok look structurally alike to the embed layer; a transport model (transports/atmosphere-transport.ts) with four modes - public (unauthenticated), anonymous-proxy (your own credential pool), proxy-relay (call another FxEmbed host’s /2 API via createRelayFetch), and an authenticated OAuth stub; and run-with-fallbacks.ts, which walks a primary transport plus fallbacks until one succeeds. The worker wires concrete environment values and proxy runtimes into the package at boot through set*ProviderEnv and set*ProxyRuntime calls, so the package stays runtime-agnostic while src/providers/*/build-host-adapter.ts glues realms to it.
The Twitter data stack. Twitter/X remains the hardest upstream, and the repo treats it accordingly. packages/atmosphere/src/providers/twitter/graphql/orchestrator.ts executes requests through weighted endpoint methods - each GraphQL query carries a weight, a response validator, and an optional fallbackOnly flag - so traffic is spread across endpoint variants for rate-limit leveling and failures degrade gracefully to fallback queries. Requests flow through fetch.ts, which either uses a guest token or routes authenticated calls through the account proxy (proxy/handler.ts): a layer that proxies requests to api.x.com with session credentials, computes x-client-transaction-id headers, classifies upstream errors, and rotates to a different account across up to nine attempts. The accounts themselves live in an encrypted credential pool (src/providers/twitter/proxy/credentials.ts), managed by the tools/credential-tools.mjs encrypt/push/pull scripts and decrypted at runtime with a CREDENTIAL_KEY secret.
Per-platform realism. The other providers are each shaped by what their platform allows. Bluesky is the friendly one: the public AppView at public.api.bsky.app plus a complete OAuth client stack (PKCE, DPoP, PAR) under providers/bluesky/auth/. TikTok is the opposite - its Android API is request-signed in native code (X-Gorgon/X-Ladon/X-Argus), so the provider reads everything from public server-rendered pages and /embed/v2/:id endpoints instead, a decision documented in detail in providers/tiktok/constants.ts. Instagram and Threads run through an account proxy that authenticates with sessionid cookies from the credential pool, rotating accounts on 401/403/429, and routes that genuinely require credentials answer a clean 501 when none are configured rather than failing mysteriously.
APIs, feeds, and honesty under failure. The api realm is a real OpenAPI application: src/realms/api/router.ts registers v2 routes on OpenAPIHono with a zod validation hook, serves /2/openapi.json, requires a User-Agent, and keeps the legacy v1 status endpoints riding the same embed pipeline. The Atmosphere realm registers Mastodon, Instagram, Threads, and TikTok provider routes and forwards /2/twitter and /2/bluesky to the same in-process apps that serve the standalone API hosts. Failure behavior is deliberate everywhere: errors are returned with a 200 status to embedding clients so chat apps display the error card, deleted posts get a localized tombstone treatment, and an optional Discord webhook receives exception alerts.
One request, end to end. Follow a single link: someone pastes fixupx.com/user/status/123 into Discord. Discordbot fetches it; getPath rewrites the hostname onto the twitter realm; the cache middleware checks for a cached response keyed for Discord. The status route matches /:handle/status/:id, sets DataProvider.Twitter, and hands the id and flags to handleStatus. That calls constructTwitterThread in the atmosphere package, whose GraphQL orchestrator picks a weighted endpoint method and fires it through twitterFetch - guest token, or the account proxy rotating through the encrypted credential pool. The processor normalizes the response into an APITwitterStatus; the video renderer emits og:video:url instructions with Discord-friendly dimensions; the HTML goes back to Discord, which renders a playable video. The response is cached in Cloudflare’s cache for the next bot that asks - and if a human clicks that same link instead, they never see any of this, just a 302 to the original post.
Advantages
- Videos, polls, and quotes that actually render. The whole point: direct video URLs for chat clients, dimension clamping in
src/render/video.ts, GIF transcode routing, mosaic image combining, and localized poll/quote strings. - One codebase, many brands. Host-header realm routing plus per-realm branding means FxTwitter, FixupX, FxBluesky, FixTok, and FxInstagram are all the same worker with different domain lists and
branding.jsonvalues. - Rate-limit leveling as architecture, not patchwork. Weighted GraphQL endpoint methods with validators and fallback-only variants, transport fallback chains in
runWithTransports, and account rotation in the proxy layer. - A genuinely reusable data layer.
@fxembed/atmosphereis an npm workspace package with typed transports and a proxy-relay client, so another app can consume the same providers or relay to an existing FxEmbed host’s OpenAPI. - A real, documented API. OpenAPI v2 with a served spec and zod validation, oEmbed endpoints, RSS/Atom profile feeds, and direct-media links - not just an embed page.
- Honest degradation. Credential-gated routes answer
501instead of lying, embed clients get error cards with HTTP 200, deleted posts get tombstones, and optional Discord webhook alerting covers the rest.
Benefits
- Zero-effort link fixing for users. Adding
fxorfixupto a link requires no account, no bot, and no configuration - the embed improvement is invisible until you need it. - Read access to social data without paid keys. The public API and the guest/credential strategies mean hobbyist bots and dashboards can fetch statuses, profiles, and trends without API subscriptions.
- Cheap, boring operations. A single Cloudflare Worker or one Docker container running
workerdon port 8787; no database cluster, no queue infrastructure to babysit. - Localization built in. i18next with ICU message format and URL-level language segments mean embeds speak the viewer’s language, with translations for tweet text itself.
- Testable without touching upstreams. Tests run in Miniflare via
@cloudflare/vitest-pool-workersagainst extensive fixtures and mocks intest/mocks/- no real credentials, no rate-limit roulette in CI. - A masterclass in scraping responsibly. The code comments and
AGENTS.mddocument exactly why each platform is accessed the way it is - which endpoints are signed, which are logged-out-safe, and which routes simply cannot exist without credentials.
Usage
The prefix trick, from the README, is the whole user-facing API:
twitter.com -> add fx before it: fxtwitter.com
x.com -> add fixup before it: fixupx.com
bsky.app -> add fx before it: fxbsky.app
To self-host with Docker, first copy and edit the configuration files (needed for custom domains, branding, or credentials):
cp .env.example .env
cp wrangler.example.toml wrangler.toml
cp branding.example.json branding.json
Build and run with Docker Compose:
docker compose up -d --build
The worker listens on http://localhost:8787. Because FxEmbed routes by the Host header, test a specific realm like this:
curl -H "Host: fxtwitter.com" -H "User-Agent: Discordbot/2.0" "http://localhost:8787/user/status/123"
You can also open http://localhost:8787/ without a Host header to see the local realm prefixes. Environment variables from .env are bundled at Docker build time, so rebuild the image after changing build-time configuration. Stop the service with:
docker compose down
For development without Docker, the repository uses the standard Node.js workflow (npm install, then npx wrangler dev --local for a local server on port 8787), with npm run build-local for the esbuild bundle and npm run test for the Miniflare-backed test suite.
Conclusion
FxEmbed is one of those repositories that looks like a utility and reads like a systems course. The realm-routing pattern solves multi-tenancy on a single worker; the atmosphere package shows how to keep six platform integrations behind one typed envelope with swappable transports; the Twitter stack is a case study in staying alive under rate limits with weighted fallbacks, transaction IDs, and rotating credentials; and the render layer demonstrates that “just some og: tags” is, done properly, an exercise in per-client quirk management. Whether you want to fix embeds for your community, build on its JSON API, or study how a high-traffic service survives hostile upstreams, the source is well worth the read - and it is MIT licensed, so the answer to “can I run my own?” is a documented deployment guide away.
Links:
- FxEmbed on GitHub - the repository covered in this post
- Documentation - docs site built from the
docs/folder in the repo - API Reference
- Self-Hosting Guide
Enjoyed this post? Never miss out on future posts by following us