Skip to main content
POST
Tweet viral score API for X posts and drafts
2 credits per analyzed post · All plans from $0.00012/credit · Supports guest paid reads
Viral score reads each post or draft with AI, answers 8 questions about its wording, and turns them into a Viral Score from 0 to 100. The endpoint is POST /api/v1/x/analysis/viral-score. The score rates the wording. It does not predict likes or views.
The snippets print each draft’s score, verdict, and hook. They do not print the full response.

Score drafts before you post

Send up to 100 drafts in texts. Xquik reads nothing from X for them, and each draft costs the same as 1 analyzed post. Drafts come back with the IDs text:1, text:2, and so on. Rewrite a draft with a low hook or clarity first. Those 2 traits carry half of the score.

Read the Viral Score

Hook, clarity, payoff, and the expected reaction raise the score. Payoff is the higher of informative and funny. Wording that reads like generic machine copy lowers it. Hard stops cap the score of likely spam, ragebait, and generic machine copy. viral.stops lists the stops that applied, such as spam. viral.weights names the version of these rules, such as viral_lite:1. viralScore is null when a trait has no answer. Xquik never fills a missing score with a guess.

Compare with real engagement

For posts read from X, viralAlgorithmScore applies X’s published ranking weights to the post’s public counts: like 0.5, reply 5, repost 1, and quote 5. It divides the sum by views and multiplies by 1,000. A post without a view count uses followers. viral.algorithmBasis names the divisor. The AI never sees these counts. It reads the text and its context only. The estimate is not the score X computes, since X uses signals public data does not show. viralActualEngagementRate is log10(1 + weighted engagement per 1,000 followers). analysisSummary.viral.calibration compares it with the Viral Score across the posts.

Score an account’s posts

Send username to score an account’s recent posts. analysisSummary.viral then holds a report per author in accounts and a leaderboard.

Pick the posts to analyze

Send tweetIds, texts, or a search, never more than 1. Sending none, or more than 1, returns 400 invalid_input. A search combines every search field you send into 1 X search. queryType sets the order, Latest or Top. For more posts than 1 call returns, send the same search again with cursor set to next_cursor. Stop when has_next_page is false.

Compare with an earlier answer

Send the results of an earlier call as baseline to see what changed. Each row in the new results gets monitor, and analysisSummary.monitor counts the posts per status. The comparison costs nothing extra.
A baseline row needs the post’s id, or tweet.id, and its answers. Send whole result rows when you have them. Their monitor carries the settings’ fingerprint, so rows from other settings show as not_comparable. A decision counts as changed only when the new answer clearly leaves the old one, so near ties between calls stay unchanged. Keep analysis the same between calls for comparable answers.

Budget the analysis

Each analyzed post costs 2 credits. Each text in texts costs the same. Posts in unanalyzed cost nothing. When credits cover fewer posts than you asked for, fewer come back. The posts left over appear in unanalyzed with insufficient_credits. If credits cover no post, the call returns 402 insufficient_credits.

Body

string[]
Up to 100 post IDs or post URLs, such as x.com/nasa/status/20. A post named twice is analyzed once. Send tweetIds, texts, or a search, not more than 1.
string
X search to analyze, with the operators of Search Tweets.
string
Analyze 1 account’s posts. Send a handle, with or without @, or a profile URL.
string
Analyze a public List’s posts. Send its ID or URL.
string
Analyze the quotes of 1 post. Send its ID or URL.
string
Analyze the replies in 1 post’s conversation. Send its ID or URL.
string
Inclusive start of the search, such as 2026-09-25T00:00:00Z. Unix seconds also work. A time without an offset is UTC.
string
Exclusive end of the search. Unix seconds also work. A time without an offset is UTC.
string
Search order: Latest or Top. Defaults to Latest.
integer
Maximum posts to analyze from the search. Range: 1-100. Defaults to 20. Credits can return fewer.
string
The next_cursor of the page before. Send the same search with it.
string[]
Up to 100 texts of your own, such as drafts. Each must contain words. Reads nothing from X.
object[]
Rows of an earlier answer to compare with, up to 10,000, such as its results. Each needs the post’s id, or tweet.id, and its answers. Each new result’s monitor says whether the post is new, changed, or unchanged. The comparison costs nothing extra.
object
Optional settings. Leave it out to ask the route’s own questions.

Headers

string
Full account API key. An OAuth bearer token also works.
string
Send Bearer xq_your_guest_key_here for an active paid_reads guest key.
string
required
Must be application/json. The body can be up to 256 KB.

Response

200 OK

object[]
Each analyzed post with its answers. Each row costs 2 credits.
object[]
Each post without an analysis. These cost nothing.
object
Totals of the answers across the analyzed posts.
boolean
Whether the search has more posts. Always false for tweetIds and texts.
string
Send it as cursor with the same search for the next page. Empty when there is none.

400 Invalid input

The body names no posts, names more than 1 source, or has an invalid field. The message says what to send instead. The request costs nothing.

401 Unauthenticated

Anonymous requests get WWW-Authenticate: Bearer and a guest wallet checkout action. This is not a Payment challenge.
Missing or invalid API key. Check the x-api-key header value.

402 Payment required

Full account keys can receive no_subscription, subscription_inactive, no_credits, or insufficient_credits with account payment options. Guest keys receive only the guest top-up action. The failed request creates no checkout. Ask the user to confirm before calling any checkout or top-up route.

403 Protected account

A search that needs a protected author returns this. Choose a public account. The request costs nothing.

502 X API unavailable

The read service returned an error. Retry after a short delay.

503 Service busy

Xquik is busy. Wait for the Retry-After header, then send the request again. The request costs nothing.

429 Rate limit exceeded

You exceeded your tier rate limit. Wait for the Retry-After header before retrying.

424 Dependency failed

The normalized v1 response contract can return 424 when the read service is unavailable.

Tweet viral score API questions

How do I check if a tweet will go viral?

Send the draft in texts. viralScore rates its wording from 0 to 100, and viralVerdict says whether to post it, edit it, or wait.

Does the score predict likes or views?

No. It rates the wording only. Timing, followers, and X’s ranking decide the reach. viralAlgorithmScore shows how a posted tweet actually performed.

Why did my draft score low?

Read answers. A low hook or clarity costs the most points. viral.stops names any hard stop, such as spam or ragebait, that capped the score.

Can I score someone else’s posts?

Yes. Send their handle in username, post IDs in tweetIds, or a search in query.

How much does the viral score cost?

Each analyzed post or draft costs 2 credits. Posts in unanalyzed and refused requests cost nothing.

Does this replace X’s official API?

No. This page documents Xquik, an independent third-party service. It does not document X’s official API.
Next steps. Classify posts asks your own questions, or Sentiment analysis reads the attitude of posts.