Skip to content

Configuration ​

These options go in the object you pass to login(). They pick which client emusks pretends to be, which GraphQL host it calls, and which proxy it uses.

Client selection ​

Each official app has its own bearer token and HTTP fingerprint. emusks ships several and sends requests the way the chosen one would.

js
import Emusks from "emusks";

const client = new Emusks();
await client.login({
  auth_token: "your_auth_token_here",
  client: "tweetdeck", // emulate the tweetdeck client (premium-only)
});

Which clients work changes over time, and some may get your account suspended.

Changing the client usually doesn't require getting a new auth token, but you may need to change it if you sign out or get CAPTCHA'd.

The current built-in clients are android, iphone, ipad, mac, old, web, tweetdeck, along with a few leaked ones: advertisers, ads_manager_next, advertiser_interface, corporate_cms, ads_broken, audience_manager_internal, and tweets_manager.

Please be really careful when using alternative bearers. Twitter is aware of them and may ban you for using them. This is really a hit and miss for which bearers Twitter blocks.

Custom clients ​

The default is web, the most common client and the least likely to raise suspicion. To use your own bearer token and fingerprint, pass an object as client. Name the user agent userAgent, since API requests read that key:

js
client: {
  bearer: "AAAAAAAAAAAAAAAAAAAAA…",
  fingerprints: {
    userAgent:
      "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
    ja3: "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,35-5-27-16-0-10-13-23-45-65037-17613-18-65281-51-43-11,4588-29-23-24,0",
    ja4r: "t13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601",
  },
},

GraphQL endpoint ​

Twitter serves the same GraphQL API from several hosts. Pick one with endpoint:

NameBase URLDefault transaction IDs
webhttps://x.com/i/api/graphql/<queryId>/<queryName>on
mainhttps://api.x.com/graphql/<queryId>/<queryName>off
tweetdeckhttps://pro.x.com/i/api/graphql/<queryId>/<queryName>off
web_twitterhttps://twitter.com/i/api/graphql/<queryId>/<queryName>on
main_twitterhttps://api.twitter.com/graphql/<queryId>/<queryName>off
tweetdeck_twitterhttps://pro.twitter.com/i/api/graphql/<queryId>/<queryName>off

The default is web:

js
const client = new Emusks();
await client.login({
  auth_token: "your_auth_token_here",
  endpoint: "main", // "web" | "main" | "tweetdeck" | "web_twitter" | "main_twitter" | "tweetdeck_twitter"
});

You can also change it after login by setting the property directly:

js
client.graphqlEndpoint = "tweetdeck";

Transaction IDs ​

The web endpoint expects an x-client-transaction-id header on every GraphQL request. emusks generates these automatically using the x-client-transaction-id package.

The other endpoints don't need them, so they're off there by default. transactionIds overrides that:

js
const client = new Emusks();
await client.login({
  auth_token: "your_auth_token_here",
  endpoint: "main",
  transactionIds: true, // force transaction IDs on for any endpoint
});

Or toggle it after login:

js
client.transactionIds = false; // disable even on web

When to change endpoints

Endpoints can behave differently under rate limiting. If you keep hitting limits on web, switching to main may help, and the other way around. tweetdeck is the TweetDeck (Twitter Pro) web app's host.

Proxy support ​

proxy sends every request through a proxy.

js
const client = new Emusks();
await client.login({
  auth_token: "your_auth_token_here",
  proxy: "socks5h://user:pass@host:1080", // http, socks4, socks5 and socks5h work
});

emusks doesn't rotate proxies for a logged-in session, and I don't recommend doing it yourself: Twitter will most likely notice one session coming from many IPs. Guest sessions are different, see using a pool.

Handling errors ​

When a GraphQL response has an errors array and no usable data, emusks throws an Error whose message joins the error messages. The error also carries status, query (the operation name) and the raw errors array:

js
try {
  await client.tweets.create("hello");
} catch (e) {
  console.log(e.query, e.status, e.errors?.[0]?.code);
}

A locked account, for example, throws Authorization: Denied by access control: Missing TwitterUserNotSuspended; .... For long-running apps, set up a way to get notified when your account gets locked or rate limited.

GraphQL often returns data together with non-fatal errors. ListAddMember, for example, succeeds but reports a DecodeException on a sub-field. emusks only throws when every field of data is null or missing. Otherwise the call returns normally and the raw response keeps the partial errors.

HTTP errors and rate limits ​

When Twitter answers a GraphQL call with something other than JSON, such as a 429 or an empty body, the thrown error carries status, rateLimit ({ limit, remaining, reset } from the x-rate-limit-* headers) and the raw body:

js
try {
  await client.search.latest("cats");
} catch (e) {
  if (e.status === 429) console.log("resets at", new Date(e.rateLimit.reset * 1000));
}

When transaction ids are on, emusks rebuilds the x-client-transaction-id generator and retries once before throwing, for long-running sessions whose transaction key went stale.

Closing the client ​

await Emusks.close() stops cycletls.

Next steps ​

Pick a helper namespace:

  • Tweets: create, like, retweet and manage tweets
  • Users: look up, follow, block and mute users
  • Search: search tweets, users and media
  • XChat: end-to-end encrypted DMs
  • Running queries: call GraphQL, v1.1 and v2 directly

not affiliated with X Corp.