Skip to content
x

Graph discovery

Walk the graph linked from a tweet or user breadth-first with x discover: follow authors, quotes, replies, mentions, likers, retweeters, the follow graph, and more.

Every other read answers one question about one object. x discover chains them: it starts at a tweet or a user and follows that object's links outward, hop by hop, streaming every node it reaches. It is a breadth-first walk of the X graph.

x discover 1234567890        # what is this tweet linked to?
x discover nasa              # what is this account linked to?

A seed is any tweet or user reference: a tweet id or status URL, or a handle or profile URL. Pass more than one to start the walk from several places at once.

The graph one record already carries

Before walking anywhere, look at what a single read is worth. x edges fetches the references you name and prints the claims those records make, as from predicate to with the URL each claim came from:

x edges 1903142823316049977 --fields from,predicate,to -o table
from                           predicate   to
x://tweet/1903142823316049977  replies_to  x://tweet/1903136743634723031
x://tweet/1903142823316049977  mentions    x://user/jack
x://tweet/1903142823316049977  mentions    x://user/marmoushera
x://user/guyfishermoney        authored    x://tweet/1903142823316049977
x://user/marmoushera           authored    x://tweet/1903136743634723031

That is five edges about five nodes for one anonymous request, and one of them names the author of a tweet nobody has fetched. Every row also carries the tier and surface it cost, so a graph assembled from mixed reads can be filtered by how much you trust each claim. --conflicts narrows the output to claims two sources cannot both be right about, printing both sides with a marker on the one that wins on provenance instead of quietly picking a side.

Those same five claims plus the five nodes they address come back as one value from x graph, which is the shape to hand to something that wants a graph rather than a table:

x graph 1903142823316049977 -o json

Nodes the read carried whole come with their record, the tweet and its author here, and nodes that were only named come with just an address. That is the honest shape: a mention is a claim about an account nobody fetched, and dropping it would lose four fifths of the graph a single request just paid for.

The same graph in RDF is x rdf, which is what to reach for when it has to travel:

x rdf 1903142823316049977 --format nq

The vocabulary is schema.org where a term exists and x: where none does, and the namespace page defines every x: term. nq and jsonld carry the URL each claim was read from, so two crawls can be merged without losing which read said what.

A hop is not an edge, and the difference matters once you start walking. An edge is a claim, pointing the way the claim points. A hop is a direction of travel, and half of them run against the arrow: the liker hop goes from a tweet out to the accounts that liked it, while the edge underneath points from each account back at the tweet.

What gets followed

The default follows a post's content and stays entirely on Tier 0, so it works with no token: the author, the tweet it quotes or retweets, its reply parent, the accounts it mentions, and (for a user) their pinned tweet.

Choose what to follow with --follow. It takes a preset:

x discover <ref> --follow content      # author, quote, retweet, reply, mention, pinned (default)
x discover <ref> --follow thread       # author, reply, replies, quote
x discover <ref> --follow engagement   # liker, retweeter, quotedby
x discover <user> --follow network     # following, followers
x discover <user> --follow timeline    # timeline, pinned, author
x discover <ref> --follow all          # everything

or a comma-separated list of individual hops:

x discover <ref> --follow author,quote,mention
x discover <ref> --follow replies,liker

The full hop vocabulary:

Hop From → to Tier What it follows
author tweet → user 0 who wrote the tweet
quote tweet → tweet 0 the tweet it quotes
retweet tweet → tweet 0 the original it retweets
reply tweet → tweet 0 the tweet it replies to
mention tweet → user 0 accounts it @-mentions
pinned user → tweet 0 the account's pinned tweet
timeline user → tweet 0 the account's recent tweets
replies tweet → tweet 0 the replies the status page renders
liker tweet → user session accounts that liked it
retweeter tweet → user session accounts that retweeted it
quotedby tweet → tweet session tweets that quote it
following user → user session accounts it follows
followers user → user session accounts that follow it
likes user → tweet session tweets it liked

The Tier-0 hops work with nothing. The rest read the GraphQL surface, which X answers for your own session (x auth import) and not for a guest token. When you ask for a hop you have no tier for, x discover drops it with a one-line note on stderr and keeps going on what it can reach, rather than failing the whole walk. The one exception is when every hop you asked for needs a tier: then there is nothing to do and it exits 4 with the tier to add.

How far and how wide

x discover <ref> --depth 2            # follow two hops from the seed (default 1)
x discover <ref> --fanout 50          # up to 50 neighbors per hop (default 25)
x discover <ref> --fanout 0           # no per-hop cap
x discover <ref> -n 1000              # stop after 1000 nodes total (default 500)
x discover <ref> --budget 20          # stop after 20 upstream requests

--depth is how many hops to follow. --fanout caps how many neighbors each hop contributes per node, so one hop never pages a whole follower graph unless you raise it. -n/--limit is the total node budget, the hard stop on a deep or wide walk.

--budget is the other kind of ceiling: requests rather than nodes. Nodes are not all the same price, since a list read hands back whole records and costs nothing extra to visit, while a mention costs a fetch. Requests are the unit the rate limits are written in, so that is the unit a careful walk counts:

$ x discover 1903142823316049977 --follow thread --depth 2 --budget 2 -o url
https://x.com/GuyFisherMoney/status/1903142823316049977
https://x.com/GuyFisherMoney
stopped early (request budget of 2 spent): 4 nodes left unexpanded

The last line goes to stderr, and it is the point of the flag: a walk that stopped short says what it left behind, so a partial crawl is never mistaken for a finished one. An exhausted rate-limit window ends a walk the same way, with exit 5 and the bucket named.

Reading the output

x discover streams one row per node, tagged with how it was reached:

depth  via      kind   id   who     summary                   url
0               tweet  20   @jack   just setting up my twttr  https://x.com/jack/status/20
1      author   user   12   @jack   jack                      https://x.com/jack

Because it streams through the same formatter as every read, it shapes and pipes the same way. The JSON forms carry the full node, with the nested tweet or user:

x discover <ref> -o json | jq -r '.via + " -> " + (.tweet.id // .user.username)'
x discover <user> --follow network -o jsonl | jq -r '.user.username' | sort -u
x discover <ref> --fields depth,via,who,url -o table

Persisting a walk

Add --store to write every node and edge into the local store as the walk streams, so you keep the graph as well as see it:

x discover nasa --follow network --depth 2 --store
x db query "select predicate, count(*) from edges group by 1 order by 2 desc"

What lands in edges is claims, not hops. Most of them come out of the records themselves, and four come out of the walk because nothing else asserts them: liker, retweeter, following, and followers are listings rather than records, so a likers page is the only place that says an account liked a tweet.

When you want the dataset rather than the live answer, reach for x crawl, which is the same walk pointed at the store instead of stdout. See the local store for inspecting and exporting what you collect.