npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fxtwitter

v2.0.0

Published

Typed wrapper for the FxTwitter API.

Readme

fxtwitter

npm GitHub Workflow Status GitHub Node

Typed wrapper for the FxTwitter API.

Install

npm install fxtwitter

Usage

import { FxTwitterV2 } from "fxtwitter/v2";

const fx = new FxTwitterV2();

const { status } = await fx.getStatus("20");
const { user } = await fx.getProfile("X");
const { results } = await fx.search("puppies");

Each version ships under its own subpath, where its types are exported alongside the client:

import { FxTwitterV2, type TwitterStatus } from "fxtwitter/v2";
import { FxTwitterV1, type Tweet } from "fxtwitter/v1";

If you need both at once, FxTwitter bundles them. v1 is only constructed — and so only warns — on first access:

import { FxTwitter } from "fxtwitter";

const fx = new FxTwitter({ v1: { silenceDeprecationWarning: true } });
await fx.v2.getStatus("20");
await fx.v1.getStatus("20");

Options

Every client takes the same options:

| Option | Type | Default | Description | | --- | --- | --- | --- | | baseUrl | string | https://api.fxtwitter.com | API host, e.g. a self-hosted instance | | headers | Record<string, string> | — | Extra headers sent with every request | | timeout | number | disabled | Abort a request after this many ms | | retry | number \| false | 1 | Retries for failed requests | | retryDelay | number | 0 | Delay between retries, in ms | | fetch | typeof fetch | global fetch | Custom fetch implementation |

Every method takes an optional signal for per-call cancellation.

The API requires a User-Agent identifying the caller and answers 401 without one. When you don't set one, a header describing the current runtime is sent — Node.js/22.16.0, Bun/1.3.0, Cloudflare-Workers and so on. Set your own to identify your app instead:

new FxTwitterV2({
  headers: { "User-Agent": "MyApp/1.0 (+https://example.com)" },
});

Nothing is sent in browsers, where User-Agent is a forbidden header and the browser supplies its own.

v2

new FxTwitterV2(options?)

| Method | Returns | | --- | --- | | getStatus(id, options?) | A single post | | getThread(id, options?) | A post with its unrolled thread | | getConversation(id, options?) | A post, its thread, and ranked replies | | getStatusReposts(id, options?) | Users who reposted a post | | getStatusQuotes(id, options?) | Posts quoting a post | | getProfile(handle, options?) | A user profile | | getProfileStatuses(handle, options?) | A user's posts | | getProfileArticles(handle, options?) | A user's long-form articles | | getProfileMedia(handle, options?) | A user's posts containing media | | getProfileAbout(handle, options?) | Account metadata | | getProfileFollowers(handle, options?) | A user's followers | | getProfileFollowing(handle, options?) | Accounts a user follows | | search(query, options?) | Post search results | | typeahead(query, options?) | Autocomplete suggestions | | trends(options?) | Trending topics |

Common options: count and cursor paginate list endpoints, lang requests an inline translation, and aboutAccount adds about_account to an author.

// Paginate
let cursor: string | undefined;
do {
  const page = await fx.search("puppies", { feed: "top", count: 50, cursor });
  cursor = page.cursor.bottom ?? undefined;
} while (cursor);

// Look a profile up by numeric ID
import { byUserId } from "fxtwitter/v2";
await fx.getProfile(byUserId("783214"));

The list endpoints report "no results" as a 404 carrying an otherwise normal body. An empty page is a result rather than an error, so it resolves with results: []. The API uses that same 404 for an unknown handle, which it does not distinguish from an empty timeline.

getProfileStatuses resolves to null when since is set without a cursor and nothing is newer — the API's documented 204. Every other method either resolves with a body or throws.

v1

Deprecated. v1 is kept only for backwards compatibility and does not receive new features — use v2 instead. Constructing FxTwitterV1 emits a one-time DeprecationWarning (so --no-deprecation and --throw-deprecation apply); pass silenceDeprecationWarning: true to suppress it. FxTwitter's v1 property only constructs the client, and so only warns, on first access.

new FxTwitterV1(options?)

getStatus(id, options?)

Fetches a single status by its snowflake ID.

const { tweet } = await fx.getStatus("20", { translateTo: "es" });
tweet?.translation?.text;

| Option | Type | Description | | --- | --- | --- | | translateTo | string | Target language — an ISO 639-1 code (es) or locale (zh-cn). Adds translation to the status | | screenName | string | Author handle, for a readable URL. The API resolves the status from id alone and never checks it |

Resolves to { code, message, tweet }. tweet is null when the status could not be retrieved.

getUser(handle, options?)

Fetches a user profile by handle, without a leading @.

const { user } = await fx.getUser("jack");

Resolves to { code, message, user, reason? }. user is absent when the profile could not be retrieved, and reason is "suspended" for a suspended account.

On success, code mirrors the HTTP status and message is one of OK, PRIVATE_TWEET, NOT_FOUND, UPSTREAM_UNAVAILABLE or API_FAIL.

Malformed input is rejected before a request is sent, because v1 answers those cases with HTML or a redirect rather than JSON: a status ID must be 2-20 digits, and a handle passed to getUser must match \w{1,15}. A well-formed handle that does not exist is a normal 404.

Errors

HTTP 4xx/5xx, network failures, timeouts, invalid input, and non-JSON responses are thrown as FxTwitterError:

import { FxTwitterError } from "fxtwitter";

try {
  await fx.getStatus("20");
} catch (error) {
  if (error instanceof FxTwitterError) {
    error.status; // 404
    error.code; // 404
    error.message; // "NOT_FOUND"
    error.body; // parsed response body, when available
    error.cause; // underlying error, when available
  }
}

For a network failure or a timeout there is no response, so status, code and body are all undefined and message is a generic description. The original error is still available as cause.

Requirements

  • Node.js >= 22

Contributing

Contributions are welcome! See CONTRIBUTING.md for details.

License

Distributed under the MIT License.