X post and profile JSON, field by field: a reference for agents

X post and profile JSON, field by field: a reference for agents
Unif Engineering team
Unif Engineering team

Updated

Every field the free UnifAPI X endpoints return for posts, profiles, pages, rate limits and errors, with types and the edge cases to handle.

In short: the X endpoints on UnifAPI, free to use with no login and rate-limited per visitor, return three records (post, profile, and the page wrapper around lists) plus one error shape and three rate-limit headers. Field names are camelCase, ids are strings (they exceed 2^53), dates are ISO 8601 in UTC, counts are integers, and anything X does not report is absent rather than zero. This page lists every field with its type and the cases a program has to handle: a repost that carries the original post plus repostedBy, a quote that nests one level, a protected account that returns its counts but no posts, and views that X reports on most posts but not all.

Models are good at reading JSON they have seen. The difference between an agent that uses a data source correctly and one that guesses field names is whether the reference was in its context or in its training data. This is the reference. It is the same model the human viewer pages render, so what you see on /tools/x-profile-viewer is exactly what /tools/x/profile?handle=… returns.

Conventions

  • Ids are strings. X ids are 64-bit; JSON numbers lose precision above 2^53. Every id is a string, including in URLs and cursors.
  • Dates are ISO 8601, UTC. createdAt and joinedAt look like 2026-10-05T22:33:56.000Z.
  • Counts are integers. Never formatted strings, never nulls. A count X did not report is left out, not set to 0.
  • Optional means absent. A field marked optional is missing from the object when there is no value. Test with "views" in post.metrics, not === 0.
  • URLs are canonical x.com URLs. post.url is https://x.com/<handle>/status/<id>; media URLs point at X's CDN and are never re-hosted.

post

Returned by /tools/x/post (as post), and as each item of posts.items on profile, media, and search, and of replies.items on replies.

FieldTypeMeaning
idstringPost ID
textstringFull text. Long posts (notes) are returned in full; the trailing media shortlink is removed when media is attached
createdAtstring (ISO 8601)Publish time
languagestring, optionalBCP-47 code X detected (en, ja, …)
conversationIdstring, optionalId of the root post of the thread this post belongs to
inReplyToPostIdstring, optionalThe post this one replies to, when it is a reply
possiblySensitivebooleanX's own flag
urlstringCanonical x.com URL
authorprofile or nullThe author; null only when X withheld it
metricsobjectSee below
mediaarray of mediaEmpty when none
repostedByobject or nullSet when this entry is a repost: { id, handle, name } of the account that reposted. The rest of the record is the original post
quotedpost or nullThe quoted post when this is a quote post, nested one level (a quote inside a quote is not expanded)

metrics

FieldTypeMeaning
likesinteger
repostsintegerReposts (retweets), not counting quotes
repliesintegerX's reply count; can exceed the number of readable public replies
quotesintegerQuote posts
bookmarksinteger
viewsinteger, optionalImpressions. X reports them on most posts; absent when it does not

media

FieldTypeMeaning
kindphoto | video | animated_gif
urlstring, optionalFull-size image, or the poster frame for video and GIF
previewUrlstring, optionalSame as url today; reserved for a smaller rendition
width, heightinteger, optionalOriginal dimensions
durationMsinteger, optionalVideo length
videoUrlstring, optionalHighest-bitrate MP4 X exposes, on X's CDN

profile

Returned by /tools/x/profile, /tools/x/media, and /tools/x/followers (as profile), as post.author, and as each item of users.items on followers.

FieldTypeMeaning
idstringUser ID
handlestringWithout the @, in X's casing (NASA)
namestringDisplay name
verifiedbooleanAny verification (blue, government, business)
verifiedTypestring, optionalGovernment, Business, … when X names one
protectedbooleanPosts are not public. The endpoints return counts but no posts
biostringMay be empty
locationstringFree text, may be empty
websitestring, optionalExpanded URL, not the t.co shortlink
joinedAtstring (ISO 8601)
avatarUrlstring, optional400×400 rendition
bannerUrlstring, optional
followersinteger
followinginteger
postsintegerLifetime post count
listedinteger, optionalLists the account appears on, when reported
pinnedPostIdstring, optional
urlstringhttps://x.com/<handle>

/tools/x/profile adds engagementRate: average of likes, replies, reposts, and quotes per original post on the page, divided by followers, as a percentage with four significant digits. Reposts are excluded from the average. For an account with 90 million followers it is a small number (0.0058), which is correct, not a bug.

page

Every list is wrapped:

{ "items": [ … ], "hasMore": true, "nextCursor": "DAAHCgABHUAq3YL__-wLAAIAAAATMj…" }
FieldTypeMeaning
itemsarraypost records, or profile records on followers, or community records
hasMorebooleanTrue exactly when nextCursor is set
nextCursorstring or nullOpaque. Pass it back as cursor= to get the next page

Page sizes: 10 posts on profile, 12 on media, 20 on replies, search, and communities, 50 on followers, 30 trends. Cursors are opaque; some look like 20@, some like X's own DAAH… strings. Never parse them.

/tools/x/trends returns { woeid, locationSlug, generatedAt, trends: [ { rank, name, postCount?, url } ] }. postCount is present only for the trends X reports a volume for; for most locations today that is none, so treat it as optional. url is the live search on x.com.

/tools/x/communities returns { query, communities: page } where each item is { id, name, description, memberCount?, access?, joinPolicy?, createdAt?, url }.

Rate-limit headers

On every response, success or error:

X-RateLimit-Limit: 20
X-RateLimit-Remaining: 17
X-RateLimit-Reset: 1791357984

Reset is Unix seconds when the current 10-minute window ends. A search costs 2 units, everything else 1. A 429 adds Retry-After in seconds.

Errors

Every non-2xx response is:

{ "error": { "type": "not_found", "message": "X user not found: @nobody_here_123" } }
typeHTTPWhen
validation_error400Missing or malformed input (Missing handle: pass ?handle=…)
not_found404No such account or post; suspended accounts say so in the message
rate_limited429Free limit reached; body adds retryAfterSeconds and upgrade
upstream_error502X's data layer answered with an error
upstream_unavailable503Timed out or unreachable
not_configured503The deployment has no upstream credentials

Branch on type; message is for humans and can change.

Markdown mode

Add format=md or send Accept: text/markdown to any endpoint and the same record is rendered as Markdown: a heading with the author, the text, a line of metrics, media links, the URL, and a footer naming the next cursor. It carries every field above except the raw ids inside the author object, and it is the cheapest way to put a profile or a thread into a model's context.

Where the samples live

Each tool's Markdown page carries a captured real response for its endpoint, trimmed to a few items: /tools/x-profile-viewer/index.md, /tools/x-post-viewer/index.md, /tools/x-search/index.md, and so on. /llms.txt lists all eight with an example URL each. The metered X / Twitter API uses the same field names behind a workspace key, so code written against the free endpoints moves without edits.