An open source browser extension for Chrome, Firefox and Safari. It records X (Twitter) rate limits from the requests x.com already makes in your browser. It doesn't send any requests of its own and doesn't automate any actions.
For every response from https://x.com/i/api/* it records:
- the timestamp, method, status and URL path (the query string is dropped)
- headers whose names contain
rate,limitorretry, such asx-rate-limit-limit/remaining/resetandretry-after - for tracked actions, or any error status: the
errors[].codeandmessagefrom the JSON body
These actions are tracked by default:
| Action | Request |
|---|---|
| Follow | POST /i/api/1.1/friendships/create.json |
| Unfollow | POST /i/api/1.1/friendships/destroy.json |
| Post | POST /i/api/graphql/<id>/CreateTweet |
| Like | POST /i/api/graphql/<id>/FavoriteTweet |
GraphQL actions are matched by operation name, because the query ID (for example wR5Z_lEibLvkew3tF9w-Sg) changes whenever X deploys a new web client.
- Home shows a card for each action you've put on the home screen: remaining / limit, reset countdown, 24h counts and the last limit hit. Below the cards are the "Does it return 429?" check and links to the other pages.
- Endpoints lists every X API endpoint that has sent rate-limit headers, sorted by how close each is to its limit. Click Track on any row to turn it into a tracked action.
- Requests is the log of captured responses. You can filter it to all requests, tracked actions only, or errors only.
- Settings (the gear icon):
- Actions: for each action, choose whether it's tracked (recorded and checked for limit errors) and whether it appears on Home. Remove custom actions with ×.
- Add an action: enter a GraphQL operation name (
CreateRetweet), an/i/api/…path, or a full URL, plus an optional label. - Recording: turn off recording of non-tracked API calls, or turn off reading error codes from the page.
- Data: export the history as JSON, or clear it.
- The only permissions are
webRequest,storageand the hosthttps://x.com/*. - Request headers are never read, so cookies and
authorizationare never seen. - Response headers are filtered against an allowlist. A second denylist (
cookie,auth,token,csrf,session, …) always wins, soset-cookieis never stored. - Response bodies are parsed only to pull out error codes. Nothing else from the body is kept.
- Everything stays in
storage.local: the last 1000 tracked actions, the last 300 other API calls, and the latest limit for each endpoint. Turn off Record all other X API calls to keep only tracked actions.
x.com page ─ fetch/XHR ─▶ hook.js (page world)
│ postMessage: status, limit headers, error codes
content.js ─ runtime.sendMessage ─┐
▼
webRequest.onHeadersReceived ── status + headers ──▶ background.js ──▶ storage.local ──▶ popup
The two sources do different jobs:
- webRequest gives reliable status codes and headers, but it can't see response bodies.
- The page hook can see bodies. That matters because X often reports a limit through an error code instead of HTTP 429:
161on a 403 for follows,344or185on a 200 for posts. Safari also relies mostly on the hook, because its MV3 webRequest doesn't reliably expose response headers.
When both sources report the same response within 5 seconds, it is stored as one record.
The extension is written in TypeScript. tsdown bundles each entry point into one self-contained IIFE per browser, tsc type-checks, and tests run on the sources through tsx.
npm install
cp .env.example .env # optional: API keys and build settings, see the file
npm run check # tsc type-check + unit and sandboxed runtime tests
npm run build # dist/chrome, dist/firefox, dist/safari
npm run build:chrome # one target (also :firefox, :safari)
npm run safari # build + wrap dist/safari into an Xcode project at dist/safari-xcode- Chrome / Edge / Brave: open
chrome://extensions, turn on Developer mode, click Load unpacked, and choosedist/chrome. - Firefox: open
about:debugging#/runtime/this-firefox, click Load Temporary Add-on, and choosedist/firefox/manifest.json. Firefox makes host access opt-in, so click Grant x.com access in the popup, or allow it under the extension's Permissions tab. - Safari: run
npm run safariand opendist/safari-xcode/Rate Limit Tracker for X/Rate Limit Tracker for X.xcodeproj. Build and run the macOS target. In Safari, go to Settings → Advanced, turn on "Show features for web developers", then Develop → Allow Unsigned Extensions. Enable the extension and allow it on x.com.
- Chrome Web Store:
npm run zip:chromebuilds the upload package. store/CHROME_WEB_STORE.md has the listing text, graphics and privacy answers. - Firefox Add-ons:
npm run sign:firefoxsubmits the package and its source code with your AMO API keys. store/FIREFOX_ADDONS.md covers both that route and the manual one. - PRIVACY.md is the privacy policy.
website/ is a VitePress site with a guide and the privacy policy. The privacy page includes PRIVACY.md directly, so edit the policy there.
npm run docs:dev # local dev server
npm run docs:build # static site in website/.vitepress/distThe output is static files, so any static host works. Set BASE when serving from a sub-path, for example BASE=/x-ratelimit/ npm run docs:build for a GitHub Pages project site.
.github/workflows/ci.ymlruns on every push and pull request: type-check, tests, build for all three browsers, a Firefox lint, and the store zips (downloadable from the run asextension-zips)..github/workflows/website.ymlbuilds the website whenever it or its sources change, and publishes it to GitHub Pages frommain. To turn on publishing, set Settings → Pages → Source to GitHub Actions once.
- Load the extension and reload any open x.com tabs, so the page hook is installed before X's scripts run.
- Open the popup. Under Does it return 429?, select Follow.
- Follow accounts the way you normally would. Each follow appears on the Requests page with its status, the remaining count (if X sent rate-limit headers) and any error code.
- Read the result:
- "Yes: X returned HTTP 429": the limit can be detected from the status. The headers on that response are shown too, including the
x-rate-limit-resettime. - "Limited, but not with a 429: HTTP 403 + error 161": X enforced the limit through an error code. Status-only monitoring would miss it, but this extension catches it through the page hook.
- "No limit response observed yet": the extension lists every status it has seen, and says whether the endpoint sends
x-rate-limit-*headers at all.
- "Yes: X returned HTTP 429": the limit can be detected from the status. The headers on that response are shown too, including the
- Use Settings → Export JSON to save the raw records as evidence.
Use your own normal activity, or a test account, to reach the limit. Deliberately bursting follows can get an account flagged (error 226) or temporarily locked (326). The extension only observes requests and won't send any.
src/manifest.json shared MV3 manifest (background is set per browser by the build)
src/background.ts webRequest listener, message handling, storage, badge
src/content.ts injects hook.js, sends it the tracked list, relays its reports
src/hook.ts page-world fetch/XHR observer (must not import lib/api.ts)
src/popup/ Home, Endpoints, Requests and Settings views
src/lib/types.ts shared types: records, settings, messages
src/lib/endpoints.ts endpoint definitions, matching, settings validation
src/lib/records.ts header filtering, record building, merging, summaries
src/lib/state.ts storage shape
src/lib/api.ts `browser` / `chrome` namespace
tsdown.config.ts one IIFE build per entry and browser
scripts/targets.ts browser targets, entry list, manifest + icon output
scripts/icons.ts draws the PNG icons from simple geometry
scripts/zip.ts zips a built target for store upload
scripts/zip-source.ts zips the committed source for AMO review
scripts/sign-firefox.ts submits to addons.mozilla.org with web-ext
scripts/env.ts loads .env (see .env.example)
store/ store listing text and graphics
website/ VitePress site (guide + privacy policy)
.github/workflows/ CI and website deployment
scripts/website-assets.ts renders the site's logo and favicon
test/ node:test suites (run on the .ts sources)
MIT. Contributions are welcome. Run npm run check before opening a pull request.
This project isn't affiliated with or endorsed by X Corp. "X" and the X logo are trademarks of X Corp.