How do you get data about an X Space?
Last updated August 24, 2026
One endpoint covers this: spaces/info takes the token at the end of a x.com/i/spaces/<id> URL and returns the Space title, its state, the host profile, topics, timing and the admin, speaker and listener rosters. It answers for a scheduled, running or ended Space, costs $0.0008 per call, and returns metadata only, never the audio.
Every rate here is the pricing TwitterAPIs publishes. The billed rate is $0.0008 per call; $0.04 per 1,000 tweets is derived from it at a full 20-tweet page, which is the default page size rather than a guaranteed yield (source: twitterapis.com/pricing).
The id, and where you get one
The only required parameter is id, and it is the trailing token of a x.com/i/spaces/<id> link, a short mixed case string such as 1RKZzjkoYRAKB rather than the long numeric snowflake a post carries. There is no discovery route in this family: nothing lists or searches Spaces, so the id has to come from a URL you already hold, whether somebody shared it with you or you pulled it out of a post you were already reading. If the id resolves to nothing the response is a 404 rather than an empty object, and that covers a Space the host has since deleted, so treat 404 as gone rather than as a transport fault worth retrying.
The two flags, both on by default
Two optional booleans sit beside the id and both default to true, which means an ordinary call already returns everything there is. with_listeners pulls the per person listener roster. with_replays pulls replay availability and the metadata attached to it. Neither is billed separately, so there is no saving in switching one off and no penalty for leaving both on. The one thing worth knowing is that with_listeners cannot recover data X no longer holds: on a room that has already finished, the listener array comes back empty whatever you set the flag to. Setting it to true and seeing nothing is therefore the expected shape for an archive rather than a bug worth chasing.
The lifecycle state machine
space.state is a plain string with four values: Scheduled, NotStarted, Running and Ended. That is how you separate a room somebody has announced from one that is live from one that is already over, with no timestamp arithmetic and no second lookup. Because it comes through from X rather than being derived locally, a state machine keyed on those four strings needs nothing else from the response to drive it. The practical pattern is to poll the same id on a schedule: a Scheduled room flips to Running when the host opens it and to Ended when they close it, and the id itself never changes across those transitions, so the record you started with is the record you finish with.
Audio rooms against video rooms
space.content_type tells you which kind of room it was. It reads audio for an ordinary Space, and visual_audio when the host turned video on. The distinction matters if you are cataloguing rooms, because the two present very differently to an audience and a listing that flattens them together loses that. Like state, it is a passthrough string rather than something computed here, so a value you have not seen before means X added one rather than the response being malformed. The documented example capture comes back as visual_audio, which confirms that video rooms are reported through this same field rather than through a separate flag you would have to go looking for.
Who was in the room
Four participant fields arrive on the object. space.creator is the host's user profile. space.admins holds hosts and co hosts, each row carrying user_id, screen_name and community_role, so a co host who also holds a moderator role surfaces that in the same row. space.speakers holds the accounts that actually took the microphone, and it is legitimately empty on a room where only the host spoke. space.listeners holds the audience while the room is open. Between creator, admins and speakers, a single request is enough to log who ran a Space and who contributed to it, with no follow up profile calls needed just to get the identifiers. Note the shapes differ: creator is a user profile, while in the documented capture the admin rows carry user_id, screen_name and community_role rather than a bio and follower counts, so a richer display needs a separate profile lookup.
Audience numbers survive, the roster does not
total_live_listeners is the peak concurrent audience recorded while the Space was live, and total_replay_watched is the cumulative replay view count afterwards. Both survive the end of the room and keep being returned on every later read. The listeners array does not survive: X drops the per person roster once a Space finishes. In practice that splits your jobs cleanly in two. Anything that needs to know who listened has to run while the room is still open, and there is no way to recover it later. Anything that only needs to know how many people listened can run at any point afterwards, and the replay figure will keep climbing between reads.
Timestamps you can subtract directly
started_at and ended_at both arrive as millisecond epoch numbers, so a duration is one subtraction and a division by 60000, with no date parsing anywhere in the path. That uniformity is a normalisation rather than a passthrough, and the reason is worth knowing. X itself sends started_at as a number and ended_at as a string inside the very same payload, a mismatch that produces a silent type error in any client that trusts the shape it is given, and both are coerced before the response leaves. Both are millisecond precision rather than seconds, so a stray divide by 1000 anywhere in your chain yields a duration a thousand times too short. Read space.state before doing the arithmetic anyway, since a duration only means something once the room has actually ended.
Replays, media keys and topics
Three more fields round out the record. is_space_available_for_replay is the boolean answer to whether the recording can still be played, which is a different question from whether anybody has played it, and the two are easy to confuse when total_replay_watched is sitting right beside it. media_key is X's own identifier for the recording, distinct from the Space id you passed in. topics is an array of objects shaped as topic_id and name, so a room tagged Technology arrives with both a human label and a stable id you can group on rather than free text you would have to normalise yourself. With title and creator, that is enough to build a browsable archive.
Throughput, price and the hard limit
One flat read charge covers the whole record, rosters and counts included, with nothing extra for either flag. Polling fifty scheduled rooms every five minutes for an hour is 600 calls, which comes to under fifty cents, and it sits comfortably inside the ceiling of 600 requests a minute and 20 concurrent that applies to every route on one key. Authentication is an Authorization header carrying Bearer and your key, with x-api-key accepted as an alternative if that suits your stack better. What you will not get from here is audio. The response describes a Space, it never delivers one, and there is no file and no stream anywhere in it. That figure is ours: $0.0008 a call, per our pricing page.
What survives after a Space ends
| Field | What it holds | After the Space ends |
|---|---|---|
| space.state | Scheduled, NotStarted, Running or Ended | Reads Ended |
| space.listeners | The per person listener roster | Empty, X does not retain it |
| total_live_listeners | Peak concurrent listeners | Still returned |
| total_replay_watched | Cumulative replay views | Keeps counting |
| space.admins | Hosts and co hosts, with community_role | Still returned |
| space.speakers | Accounts that took the microphone | Still returned |
Spaces allow expression and interaction via live audio conversation.
Questions and answers
- Where do I find a Space id?
- It is the trailing token in a x.com/i/spaces/<id> URL, a short mixed case string such as 1RKZzjkoYRAKB, not a long numeric id like a post carries. Pass it as the id parameter. If it resolves to nothing, including a room the host has deleted, the response is a 404 rather than an empty object, so a 404 here means gone rather than temporarily unavailable.
- Can I search for Spaces by topic or host?
- No. This family has exactly one route and it is a lookup by id, so there is nothing that lists or searches rooms. Ids have to come from URLs you already hold, whether that is a link somebody sent you or one you extracted from a post. Once you have an id, the topics array on the response gives you a topic_id and a name you can group archived rooms on.
- Can I get the audio or a recording?
- No. The response describes a Space rather than delivering it: title, state, host, topics, timing, counts and rosters, and nothing else. There is no audio stream and no downloadable file in it. What you can read is whether a replay still exists, since is_space_available_for_replay comes back on the object and with_replays defaults to true, and media_key gives you X's own identifier for that recording.
- Why is the listeners array empty?
- Because the room has finished. X keeps the per person listener roster only while a Space is running and drops it at the end, so setting with_listeners to true changes nothing on an archive. The audience numbers are unaffected and the admin and speaker lists still come back, which is usually what an analytics job wanted anyway. If you need the roster itself, the read has to happen while the room is open.
- How do I work out how long a Space ran?
- Subtract started_at from ended_at. Both are millisecond epoch numbers on the response, already normalised, so no date parsing is involved even though X itself sends one as a number and the other as a string. Divide by 60000 for minutes. Read space.state first, because the arithmetic only means anything once the room has actually reached Ended rather than still being Running.
- Does it work on a Space that has not started?
- Yes. state comes back as Scheduled or NotStarted, and the title, host profile and topics are already set, which is enough to build a listing of upcoming rooms before any of them opens. Poll the same id later and the state moves to Running and then to Ended without the id ever changing, so one stored record tracks a room across its whole life.
- What is the difference between admins and speakers?
- admins holds the host and any co hosts, each row carrying user_id, screen_name and community_role, so it answers who was running the room. speakers holds the accounts that actually took the microphone, which is a different question and can legitimately be empty when only the host talked. creator sits alongside both as the host's full user profile, so you get identity and role from one request.
- Does the replay count keep going up after the room closes?
- Yes. total_replay_watched is cumulative replay views rather than a snapshot, so reading the same Space a week apart gives you two figures you can difference. total_live_listeners does not move, since it records the peak concurrent audience while the room was open. Reading both on a schedule is how you separate live pull from long tail listening on the same Space.
- What does content_type visual_audio mean?
- It means the host turned video on. content_type reads audio for an ordinary Space and visual_audio for a room with video, and it is passed straight through from X rather than derived here. It matters when you are cataloguing rooms, because an audio room and a video room are different products to an audience, and a listing that collapses the two loses a real distinction.
- What does a Space lookup cost?
- It bills as an ordinary read, one flat charge of $0.0008 per call with rosters and counts included and no extra fee for the with_listeners or with_replays flags. Tracking fifty rooms once a day is four cents a day. Every route on a key shares one ceiling of 600 requests a minute and 20 concurrent, so polling a large set of rooms is bounded by that rather than by any per endpoint quota.
Keep reading
Start with $0.50 in free credits
No credit card. Roughly 12,500 tweets to test every endpoint.