XFlog
X Follower Log is an open-source, self-hosted X activity tracker. Keep a live history of follows, unfollows, paid subscriptions, likes, replies, mentions, quotes and reposts. Choose what to track, explore interactions by person, and load and save conversation threads when you need them.
Watch the demo → · 70 seconds · sample data
It offers an alternative to the follower and engagement tracking features found in tools such as Circleboom, Fedica and SocialDog, with your history stored on your own machine.
Bring your own X API credentials. X API charges and any hosting costs still apply.
X prerequisites
The offline demo needs no X account, tokens or X API credits. The installer requires the complete live setup below, including automatic token renewal. To get started, create an app in the X Developer Console, check its X Activity access, and provide the settings below.
| Setting | What to enter |
|---|---|
X_APP_BEARER_TOKEN |
Required. Your developer app’s bearer token. |
X_USER_ACCESS_TOKEN |
Required. An OAuth 2.0 user access token for the account you want to track. |
X_USER_REFRESH_TOKEN |
Required. The refresh token returned with that access token. It lets XFlog renew access without asking you to sign in again. |
X_CLIENT_ID |
Required. The OAuth 2.0 Client ID of the app that issued your user tokens. |
X_CLIENT_SECRET |
Required for a Web App, Automated App or Bot. Use its OAuth Client Secret. Leave blank for a Native App. |
X_USER_ID |
Leave blank to track yourself. XFlog identifies the user token’s account at startup. Only enter a numeric account ID if you need to choose it explicitly. |
Use credentials from the same developer app, and keep the access/refresh tokens together as a pair. These credentials prepare XFlog to use the features you enable; they do not turn on paid options or start recording by themselves. X access, permissions, credits and the data X returns still determine what works.
Responsible use: track your own account, or one whose owner has given you permission. Do not use XFlog to stalk, harass or secretly profile people. Private activity requires that account owner’s authorization through X.
Get the user tokens and permissions
-
Enable OAuth 2.0 in your app’s authentication settings in the Developer Console. Copy its Client ID and, for a Web App, Automated App or Bot, its Client Secret. These are different from the app’s API Key and API Secret.
-
Use a separate OAuth 2.0 sign-in flow to sign in as the account you want to track and approve your app. XFlog does not provide this initial sign-in flow.
-
Request these permissions to cover all current XFlog features:
tweet.read users.read follows.read follows.write like.read offline.access
offline.accessis needed to receive a refresh token.follows.writeallows the optional Follow/Unfollow feature;like.readis needed for likes. Granting them does not enable those features in XFlog—choose what to use in Profile. See X’s permission guide. -
Save the returned access token and refresh token in the settings listed above. If an older token lacks a permission, approve the app again with the updated permissions; refreshing it does not add permissions.
The console’s Access Token and Secret pair is OAuth 1.0a. It is not the OAuth 2.0 access/refresh pair used here. Changing the app’s general Read/Write setting alone does not grant OAuth 2.0 permissions to an existing token.
Automatic renewal and reconnecting after an error
X user access tokens normally expire after about two hours. With the required refresh token and client settings, XFlog renews access when needed and saves the latest token pair privately in the history database. Docker and service restarts reuse that saved pair; .env is not rewritten.
Renewal does not start paused tracking, recover missed events or guarantee uninterrupted access. If X revokes access, rejects renewal, or renewal cannot be saved safely, XFlog shows an error and keeps your history. Approve the app again, replace both user tokens in .env, then apply the environment changes.
- Keep the database and its backups private: they contain saved credentials as well as history.
- After deleting the database or restoring an older backup, obtain a fresh access/refresh pair before starting. An older refresh token may no longer work.
- Use each token pair with one renewing installation. Another installation renewing the same pair can invalidate it.
- Profile lists settings needed by the currently enabled features; it is not a complete renewal checklist. Check all the live-setup settings above even if Profile shows no missing settings.
Account identification and the user-id command
The user ID is the permanent number identifying an X profile. It stays the same if the person changes their @handle; it is not your developer app ID. An app bearer token identifies the app, so it cannot tell XFlog which person is “me.”
Leave X_USER_ID blank for automatic identification. At each startup XFlog makes one GET /2/users/me request using your user token. This can cost X credits even while tracking is paused. If it fails, tracking stays stopped and the error appears in the UI and logs; XFlog does not guess the account or rewrite .env.
Setting X_USER_ID skips that startup lookup. It must match the account that owns your user token; entering someone else’s ID does not authorize access to their private activity.
If you need to look up an ID manually, export your credentials and run:
xflog user-id @yourhandle # Uses X_APP_BEARER_TOKEN
xflog user-id --me # Uses X_USER_ACCESS_TOKEN
From a checkout, load .env explicitly:
uv run --no-sync --env-file .env xflog user-id @yourhandle
For a Docker Compose installation:
docker compose run --rm --no-deps xflog xflog user-id @yourhandle
Each command makes one user lookup, which can cost credits. It prints the ID without starting tracking or changing settings. Replace the handle with --me in either example to identify the supplied user token’s account. The command uses that access token as supplied; it does not renew it.
Local installation
| Method | Install these first |
|---|---|
| Docker | Docker Engine, the Compose plugin and git; your user must be able to run Docker. |
| Native Linux | Bash, git, Python 3.12+ with virtual-environment support, and systemd user services. |
Run as your regular user, without sudo:
curl -fsSL https://raw.githubusercontent.com/aatbit/xflog/main/install.sh | bash
- Choose install, then Docker or native Linux and a new installation folder.
- Choose a port; press Enter for
7860. For live use, enter the required X credentials. The installer asks for your access token, refresh token, Client ID and app type; Web App/Automated App/Bot clients must also supply their Client Secret. Leave the account ID blank to track yourself. For the demo, leave the app bearer blank—the other X questions are skipped. - Choose an optional browser password and confirm. The installer saves the credentials privately in
.env; it won’t overwrite an existing installation. - Open
http://127.0.0.1:7860, replacing7860if you chose another port. Choose Explore the demo, or timeline settings → Workspace → Start tracking to begin recording.
Local use can be password-free: press Enter at the password prompt. If you set one, use at least 16 English letters, numbers or symbols (ASCII); the browser username is xflog. This protects XFlog and is separate from your X password. Secrets are hidden while typing.
The same installer can check for updates, update or uninstall. History and settings are kept unless you explicitly choose to delete them.
Use the released Docker image instead of building locally
Release images are available at ghcr.io/aatbit/xflog:v1.0.0 for Linux AMD64 and ARM64. From a checkout with your private .env configured, run:
docker compose -f compose.yaml -f compose.registry.yaml up -d --no-build --pull always
This uses the same localhost port, security settings and saved history as the normal Compose setup. Keep both -f options when managing this installation. To upgrade, set XFLOG_IMAGE_TAG in .env to the new version tag and run the command again. Back up your history first. The interactive installer continues to build from source.
Read the installer before running it
Download, inspect, then run:
curl -fsSL https://raw.githubusercontent.com/aatbit/xflog/main/install.sh -o install-xflog.sh
less install-xflog.sh
bash install-xflog.sh
Checking for updates, updating and uninstalling
Run the installer again and enter your existing installation folder.
- Check for updates (
check) reports app and installer changes separately without changing your installation. Docker need not be running. It can offer to run the updated installer first; a script saved elsewhere stays untouched. If an app update is available, it prints the update command. - Update: choose your install type and folder. Back up history first. Existing
.envvalues, including blanks, and custom Docker network settings are kept; missing settings get defaults. Older installations are not prompted for new credentials during an update; add any missing renewal settings from the setup checklist to.envand apply the changes. The installer asks before restarting and stops if you have edited the app’s source files. - Uninstall: pause tracking in the UI first, then choose the same install type and folder. Files, settings and history remain by default. Deleting history or
.envrequires typingDELETEat a separate prompt for each. Shared networks and X tokens are left alone. Update can reactivate a retained installation.
Saved renewal tokens stay with the history database. Deleting only .env keeps that saved copy; revoke the app’s access in X if you want to disconnect permanently.
Restarting can resume tracking if it was previously on. Pause first if you want it to stay stopped.
Automatic startup, storage and advanced installer options
- Docker restarts XFlog with Docker unless explicitly stopped. Make sure Docker starts at boot. You can join an existing HTTPS-proxy network; XFlog also keeps its own private network.
- Native Linux creates a separate Python environment and
xflog.serviceuser service. Startup without logging in may need OS administrator approval. Python needsvenv/ensurepip; the installer does not install system packages. - Native history defaults to
~/.local/share/xflog/data/history.sqlite3; Docker uses a persistent volume. - Checks and updates use
main. For another branch, tag or commit, download the script and runXFLOG_REF=<ref> bash install-xflog.sh. Checks compare file contents, not version numbers; an older source can also show differences. README-only changes do not count as app updates. docker compose downkeeps history;docker compose down -vdeletes it.- Remove a stale
.xflog-maintenance.lockonly after checking that no updater or uninstaller is still running.
Which features use X credits?
Self-hosting does not remove X’s API fees. Before live tracking, add credits, set a spending limit and check automatic top-ups in the X Developer Console.
| Feature | X API cost |
|---|---|
| Offline demo | None; no X requests. |
Account discovery and user-id lookup |
One user read, which can be billed. With X_USER_ID blank, discovery runs once at every startup. |
| Groups, filters, layouts, zoom, statistics and notifications | No extra X requests or charges. Live events you record still follow X’s event pricing. |
| Live follower tracking | Follow and unfollow events are billed on X’s pay-per-use plan. |
| Paid-subscription tracking | New-subscription and expiry events are currently listed as not billed; setup requirements still apply. |
| Allow Follow/Unfollow | Extra relationship checks and actions can be billed, even if you check someone without pressing Follow. |
| Interaction history | Off by default. $0.005 per delivered reply, mention, quote, repost or own-post event; $0.001 per like event. Turning on your posts also receives standalone posts. |
| Thread loading | Only on click: $0.005 per returned post, up to 100 per page. A missing conversation may need one extra post lookup. Account verification can add a user read. Saved copies reopen without X requests. |
Checked 4 November 2026 against X’s event prices and API pricing. Prices can change. XFlog’s activity counts are not a bill or spending limit. Hosting costs are separate.
Preferences and notifications
Open Profile with the person icon, change your choices, then Save preferences.
| Setting | Default | What it does |
|---|---|---|
| Follower events | On | Records follows and unfollows. |
| Paid subscription events | Off | Records new subscribers and expirations. |
| Activity notifications | Off | Shows in-app notices and, with browser permission, browser alerts while the page is open. |
| Allow Follow/Unfollow | Off | Adds an action to person details and the selected-person readout when space allows. Adds API usage; Unfollow asks for confirmation. |
| Resolve names & handles | Off | Fills missing names once per person through X user lookups, then saves them. Extra X charges can apply. |
| Interaction history | Off | Choose replies, mentions, quotes, reposts, likes received, your likes or your posts and replies. Each choice shows its X event price. |
Saving does not pause recording or restart paused tracking. To stop everything, use timeline settings → Workspace → Pause tracking before turning all event options off. To resume, enable at least one event option, save, then choose Start tracking.
Saved interactions and thread updates
Open a person’s details, then Interaction history. XFlog saves new interactions in its database while tracking runs; it cannot recover earlier interactions. The choices cover your account’s activity, including people outside your follower log. Outgoing replies and quotes can be linked to someone only when X supplies their ID or the referenced post.
Threads stay unloaded until you press Load thread · X reads. XFlog saves a dated copy; reopening it uses the database. Load more requests another page and Refresh checks X again. These requests need X search access and credits; a thread can contain other participants’ posts and may be incomplete.
Enabled interactions also appear in the constellation and journal, including people with no recorded follow event. The open app checks its local database every 10 seconds and updates the timeline and open interaction history automatically. New posts received by tracking are added to matching saved threads without another X request. Full threads are still loaded only when you ask: unseen replies, edits or deletions need a new X read. Cached posts remain when tracking is turned off and can be outdated.
Resolve names & handles in Profile is optional and off by default. Enable it to fill missing display names and @handles using X user lookups. X can charge per profile, even when several IDs share one request. XFlog checks each new person once, saves the result in SQLite and reuses it across the constellation, journal and HUD. It requests only identity fields, not extra grouping data. Profiles supplied with events are reused first, without a lookup. To retry unavailable profiles, switch this option off and on again; successful cached profiles are kept. Names are not checked repeatedly for changes, but later event-supplied profiles can update them.
Notifications and saving problems
Your browser may ask for notification permission when you save. Alerts need localhost or HTTPS and permission in each browser/device. If blocked or unsupported, in-app notices still work. Neither works after the page is closed. Changing only notifications does not change the events requested from X.
If saving shows a warning, follow it before assuming your change is active. If choices were saved but could not be applied on X, fix the access or disk problem and save again. If they could not be saved, make your choices again after fixing the problem.
Exploring your history
Open timeline settings with the sliders icon. Choose Followers or Paid subscribers, filter All/Gained/Lost, or switch between Constellation (one star per person) and Journal (every recorded event). These viewing choices do not change what you record.
In Constellation, scroll up for older history and down for newer history. The person nearest the center becomes highlighted as you move, and their details appear in the readout. The side arrows move one person at a time. Use the bottom sliders to move through history or change zoom, which starts at 80%. Select a person’s name or readout for details. Journal uses normal vertical scrolling and hides the sliders.
Motion, statistics and connection history
- Ambient motion controls light trails and shooting stars; Motion speed ranges from 0.25× to 2×. Neither changes recording speed. Your device’s reduced-motion setting is respected.
- The clock button selects 1h, 6h, 12h, 24h, 3d, 7d, 30d or 90d for totals, hourly rates and Growth ratio. Hover or keyboard-focus a reading’s label for help. Paid-subscriber readings show new subscribers and expirations.
- Growth ratio is gains divided by losses: 7 follows and 2 unfollows gives 3.5. A dash means no ratio can be calculated, such as when there are no losses.
- Changes compare the previous equal-length period. Green means more gains or fewer losses; red means the reverse. New means the previous period had no activity.
- Readings use the full saved log and the time XFlog received events, regardless of zoom, filters or groups. They are recorded changes, not your current follower total.
- The timeline updates automatically from saved records; notification settings do not control these updates. Your zoom and selected person are preserved. Follow/unfollow readings count only audience changes, not interactions. Workspace → Connection history shows interruptions; earlier activity and events missed during gaps cannot be reconstructed. Explore demo uses sample data.
Audience groups
Open timeline settings → Audience Groups to enable groups, then choose one with the spyglass button. Switches save automatically without pausing tracking. Activity is enabled by default, but the picker starts on Ungrouped; other groups start off.
Each constellation has its own colored connections and a separate section when zoomed in. The small hint at the top names the current constellation. Groups keep their order and colors as you zoom; Ungrouped returns to the normal timeline.
Groups make no extra X requests or charges. Missing details stay Unknown; XFlog does not look up profiles to fill them in.
Available groups and what they mean
| Group | What it shows |
|---|---|
| Activity | Follows, unfollows, new subscriptions or expirations. |
| Previous encounters | First appearance in this log, seen before, or followed again after a recorded unfollow. Uses the full saved history, including older records. |
| Account size | Under 100, 100–999, 1K–9,999 or 10K+ followers, when supplied. |
| Account age | Under 1 year, 1–3 years or 3+ years old when the event was recorded. |
| Verification | Verified/unverified and blue, business or government badge types when supplied. |
| Profile location | Similar location text, tidied for spacing, capitalization and clear aliases such as NYC → New York City and Bangalore → Bengaluru. |
Zoom out to see groups in Constellation. Journal uses collapsible sections; one person can appear in multiple sections. Groups cover the history you have loaded, so older records can add people or change what you see.
Account size, age, verification and location use only details delivered with new events while enabled. Old missing details stay Unknown; turning a group off keeps saved history. Profile facts describe the recorded event, not a live profile check. When space allows, enabled Verification, Account size and Account age add facts to the selected-person readout. Known verified accounts get a checkmark beside their name in Constellation and the readout when Verification is enabled.
First appearance does not mean first on X. Followed again needs a recorded unfollow followed by a follow; gaps can hide that sequence. Location is profile text, not a verified address: London, UK and London, Ontario stay separate. Missing or unusable labels stay Unknown; XFlog does not guess where someone lives.
How optional Follow/Unfollow checks work
Enable Allow Follow/Unfollow in Profile and save only if you want the extra API usage. XFlog verifies the token’s account, checks the selected person when an action button can be shown, and checks again before an action. While scrolling, the highlight and readout update locally; the relationship check waits until movement settles. XFlog does not download your following list or poll relationships in the background. Recent checks are kept in a small temporary cache, so returning to someone is fast. Selecting someone after five minutes can refresh the check without hiding the last result. A slow check times out with a Retry button; it does not pause tracking.
Retry only checks the relationship; a failed write is not automatically repeated. Pending requests and blocked accounts must be managed on X. The offline demo cannot perform actions.
Turn the option off and save to stop further checks/actions; an action already sent may still complete. Following someone does not add an incoming follower event to your log.
Environment configuration
The installer saves credentials in .env in your installation folder; keep it private. For manual Python runs, set environment variables before starting XFlog. Profile lists missing settings and shows the .env path when provided; token values are never shown.
Apply changes to .env
For Docker, run from the installation folder:
docker compose up -d --force-recreate
This keeps history and loads the new values. docker compose restart does not load changed environment settings.
For an installer-managed native service, run as its owner:
systemctl --user restart xflog.service
For manual Python runs, stop and relaunch with the updated environment. Restarting can resume tracking if it was previously on.
Ports, storage and advanced environment settings
| Setting | Purpose |
|---|---|
XFLOG_ADMIN_PASSWORD |
Browser password. See password rules; remote access requires one. |
XFLOG_ORIGIN |
The exact browser address; defaults to http://127.0.0.1:7860. Remote access needs HTTPS, with no trailing slash or extra path. |
XFLOG_PUBLISH_PORT |
Docker’s local published port, default 7860. Update the origin if you open a different port directly. |
XFLOG_HOST, XFLOG_PORT |
Native bind address/port, default 127.0.0.1:7860. Docker listens internally on 0.0.0.0:7860, with its published port restricted to localhost. |
XFLOG_DATABASE |
SQLite history path. Docker defaults to /data/history.sqlite3; keep it under /data to retain history across container replacement. |
XFLOG_ENV_FILE |
The host configuration-file path shown in Profile; the installer fills it in. This only displays the path; it does not load the file. |
XFLOG_LOOPBACK_PUBLISHED |
Supplied Compose sets 1 for its localhost-only published port. Other container setups may set it only with the same restriction. Native localhost runs do not need it. |
Without XFLOG_ENV_FILE, Profile says no path was provided. Follow the complete live-setup checklist; Profile’s missing-settings list does not check every renewal setting.
The installer gives .env owner-only permissions (0600), but it is not encrypted; administrators can read it.
Hosting remotely and using HTTPS
For another device or a VPS, use your own domain, an HTTPS proxy and a browser password. Keep XFlog’s HTTP port private. The installer can join an existing Docker network but does not set up a domain, proxy or certificates.
Step-by-step remote setup with Caddy
This example runs Caddy directly on the same server as XFlog. Caddy handles HTTPS and forwards visitors to XFlog’s private local port.
1. Point your domain at the server
Add an A record such as xf pointing to the server’s public IPv4 address. An optional AAAA record must reach the same server over IPv6. Allow ports 80 and 443 through both server and hosting-provider firewalls; keep port 7860 private. Caddy obtains and renews the certificate when DNS and these ports are ready. See Caddy’s HTTPS guide.
2. Set the public address and password in .env
XFLOG_ORIGIN=https://xf.example.com
XFLOG_ADMIN_PASSWORD="replace-with-your-own-long-password"
Use your own domain and a password following the password rules. Follow the address rules and apply the .env changes.
3. Set up Caddy
Install Caddy for your Linux distribution. With its standard Linux service, add this block to /etc/caddy/Caddyfile, keeping existing sites:
xf.example.com {
reverse_proxy 127.0.0.1:7860
}
Use your domain and replace 7860 if you chose another local port. Validate and load it:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl enable --now caddy
sudo systemctl reload caddy
These commands assume Caddy’s standard Linux service; see its service guide for other setups.
4. Open your HTTPS address
Visit your domain and enter username xflog and the password from .env. No X sign-in callback URL is needed.
Docker proxy or connection problems
- Proxy in Docker: join the same trusted network and forward to
http://xflog:7860; Caddy usesreverse_proxy xflog:7860. In a container,127.0.0.1means the proxy itself. Keep XFlog private and use the same public origin/password. See Caddy’s Docker guide. - Site/HTTPS won’t open: check DNS, ports 80/443 and whether another web server is using them.
- 502 Bad Gateway: check XFlog is running and the proxy address/port matches. For Docker, run
docker compose psanddocker compose logs --tail=50 xflogfrom its installation folder. - Login/settings fail: use the exact address in
XFLOG_ORIGIN. A different proxy must preserve the originalHostandAuthorizationheaders.
Use it in your own applications
Use XFlog as a Python library without installing the browser interface. Python 3.12+ is required. Install the package from PyPI:
python -m pip install xflog
# Or, add it to a uv project:
uv add xflog
The base package installs only the tracking and storage dependencies. In an existing Python environment, uv pip install xflog works too.
For the browser app, install xflog[ui] instead, then run xflog (or uv run xflog in a uv project). Set your environment settings before running it; a plain Python launch does not load .env automatically. Docker and the interactive installer include the UI automatically. Installing the package does not create an OS service.
Upgrades and installing from source
Use python -m pip install --upgrade xflog or uv lock --upgrade-package xflog followed by uv sync. Keep the [ui] extra in your pip command if you use the browser app. Stop XFlog and back up history before upgrading, then restart it with the same settings.
For unreleased development code, use python -m pip install "xflog @ git+https://github.com/aatbit/xflog.git@main". Prefer a released version for normal use.
Use XFlog from another application
CoreConfig needs only a database path and your X settings. It ignores browser origin, port and password settings. Supply values directly or use CoreConfig.from_env(); this reads the process environment, not an .env file.
import asyncio
from xflog import CoreConfig, Runtime
async def main():
async with Runtime(CoreConfig.from_env()) as xf:
page = await xf.store.history(xf.config.user_id, {"limit": 25})
for event in page["events"]:
print(event["name"], event["kind"])
asyncio.run(main())
A fresh database leaves tracking paused. With the live credentials configured and user_id blank, entering the context first makes one X user read to identify the user token’s account. The resolved ID is available as xf.config.user_id; the input configuration and .env stay unchanged. An existing database keeps its saved Start/Pause choice: entering the context can resume collection. Closing cancels local work and closes the database and HTTP client; it does not pause future starts or remove X subscriptions. Use await xf.collector.stop() to pause explicitly. The same feature costs apply to library callers.
Create a fresh Runtime for each context; instances are single-use. Both library and browser entry points reject a second runtime for the same database or app token. These are host-local locks for the same OS user. Containers or separate machines need shared ownership coordination; they must not independently consume the same developer app's stream. For automatic renewal, use the complete live setup. CoreConfig.from_env() reads these settings; direct configuration uses user_refresh_token, client_id and, for a confidential OAuth client, client_secret. Library callers may omit renewal settings and supply fresh access tokens themselves. Private features still need a user token with the right permissions; the installer’s stricter requirements do not change the library API.
The context returns Runtime. These services are available inside it:
| Call | Return value |
|---|---|
await xf.store.history(account_id, query) |
dict: events is a list of event dictionaries; next is an integer cursor or None. Query keys: kind (all, gained, lost), subject (followers, subscriptions), before (cursor) and limit (1–50). |
await xf.store.summary(account_id) |
dict[str, int]: counts keyed by event type. Local reads only. |
await xf.collector.save_preferences(selection) |
dict: saved: bool, message: str. Supply followers, subscriptions, notifications booleans, plus optional interactions choices. Preserves running/paused state; a saved change may still have a warning. |
await xf.collector.start() / stop() |
None. Start requests background collection; inspect xf.collector.state and message for connection/failure status. Stop pauses and attempts subscription cleanup. |
await xf.follow_actions.configure(enabled) |
None. Opt-in only; requires the user token when enabling. |
await xf.follow_actions.relationship(event_id) / change(event_id, following) |
dict: following, pending, blocked booleans. These can make X requests; use an event ID from the account's saved history. |
await xf.interactions.history(event_id, before=0) |
dict: interactions list and next integer cursor or None. Saved data only. |
await xf.interactions.thread(event_id, options) |
dict: posts list, total and offset integers, more_saved: bool, conversation_id and checked_at strings, and more_available / fetched booleans. |
For threads, import ThreadOptions from xflog. Its required sequence comes from a saved interaction. action="cached" reads locally; "open" uses the saved copy if available, otherwise requires accepted_costs=True before X reads. "refresh" and "more" also need that consent and the current checked_at value, so stale requests cannot repeat a paid refresh. offset pages through saved posts. Interaction tracking must be enabled before loading from X.
Handle these errors at your application's boundary:
ValueError: invalid configuration orThreadOptionsvalues.RuntimeBusyError(aSetupError): another runtime owns the app or database; it has not been started or displaced.RuntimeError: aRuntimewas entered more than once.SetupError: missing access, invalid saved state, disabled feature or missing thread-read consent. Its message explains the problem.ProviderError: rejected X requests in the transport layer;statusis the HTTP status anddelaythe bounded retry delay in seconds. Feature services can translate these toSetupError.httpx.HTTPError,sqlite3.ErrororOSError: network or local storage failure. Cleanup still runs when context entry or the body fails, including cancellation. Background collection reports its failures throughstateandmessage.
CoreConfig, Runtime, ThreadOptions, SetupError, RuntimeBusyError and ProviderError are exported from xflog. Lower-level modules remain grouped by feature:
xflog.tracking: X transport, event parsing, collection and follow actions.xflog.history: saved events, statistics, filters and demo data.xflog.interactions: interaction parsing, saved conversations and thread loading.xflog.audience_groups: the existing grouping registry.xflog.web: HTTP request models and browser mounting.
xflog.app.create_app remains the self-hosted entry point, using xflog.config.Config for its additional browser settings. Its runtime is available as app.state.runtime; Runtime.lifespan(app) is the ASGI adapter for the same async context.
To run the full development suite from a checkout, use uv sync --extra ui --extra dev then uv run --no-sync pytest. To check the SDK without web packages, create a separate environment, install the base package plus pytest and pytest-asyncio, then run python -m pytest tests/test_runtime.py tests/test_identity.py tests/test_cli.py. No tests make live X requests.
Releases and contributing
Pull requests to main and updates on main run lint, formatting and the mock-data tests on Python 3.12–3.14. No X credentials are needed. See the changelog and release notes for published changes.
How maintainers publish a release
- Update the version in
pyproject.toml, runuv lock, and update the image version incompose.registry.yaml. Merge the passing release PR intomain. - Tag that commit, for example
git tag v1.0.0, thengit push origin v1.0.0. - Actions repeats the checks and verifies the tag matches the package version and belongs to
main. It builds and checks the Python packages, publishes them to PyPI, and builds the version-tagged AMD64/ARM64 image on GHCR. Once both publications succeed, it creates a GitHub release with package downloads and automatically generated change notes.
PyPI uses Trusted Publishing from this repository’s release.yml workflow and pypi environment; no long-lived PyPI token is stored in GitHub. Actions are pinned to full commit hashes, with Dependabot checking for updates. If a job fails, inspect it and rerun the failed jobs; do not reuse a published version for different files. Changing a version tag does not replace files already published to PyPI.
License
XFlog is licensed under Apache 2.0. Bundled IBM Plex fonts keep their SIL Open Font License.
Metadata
Release files for xflog 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| xflog-1.0.0.tar.gz | 418.1 kB | With provenance | Details for xflog-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| xflog-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for xflog-1.0.0-py3-none-any.whl |
Total release size: 853.0 kB
Release files / xflog-1.0.0.tar.gz
| Download URL | xflog-1.0.0.tar.gz |
|---|---|
| Size | 418.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
999162f915a21b7818867eeb8da8ad0906591847541ec491721a44493770625c
|
|
BLAKE2b-256 checksum How to use checksums |
14f9a6bfb94a34e2ec3a27b245b2145a5b82844264d6280b1afd231c80b86e06
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logRelease files / xflog-1.0.0-py3-none-any.whl
| Download URL | xflog-1.0.0-py3-none-any.whl |
|---|---|
| Size | 434.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4e9537619b63d8658366a063110b359797d73056577e6c7239b22bb367638442
|
|
BLAKE2b-256 checksum How to use checksums |
e82826b30d1ec7905fd4c3c5dacc7972fb2d7f19c498cd3174114a17f8ef76c7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logSecurity for xflog 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for xflog-1.0.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.