# Twitter API Cost by Workload: What Sentiment Analysis, Bot Detection and Lead Scoring Actually Bill > Every vendor publishes a per-call rate. Nobody publishes the call graph a workload needs, so the buyer cannot turn a price sheet into a monthly bill. Here are the call graphs, priced. - **URL:** https://www.twitterapis.com/blogs/twitter-api-cost-by-workload-2026 - **Published:** 2026-08-31 - **Author:** Emma - **Tags:** twitter api cost by workload, twitter sentiment analysis api cost, bot detection api cost, twitter lead scoring cost, api call budget --- > **TL;DR:** A per-call rate cannot tell you what a workload costs, because a workload almost never consumes one priced call per unit of work. On the standard $0.0008 read rate, search-only brand sentiment costs about $0.105 per 1,000 posts, the same 1,000 posts with author hydration costs about $0.905, lead scoring costs about $1.705 per 1,000 prospects, and screening 1,000 accounts for bot signals costs about $3.20. That is a thirty times spread on one rate, and the fan-out you design is what causes it. Ask what a Twitter API costs and every answer you get is a per-call number. Ask what sentiment analysis costs and there is no answer anywhere, because the second question requires knowing how many calls a post takes, and the only people who know that are the ones already running the pipeline. This post writes down the call graphs. ::directive{id="img-1"} Six workloads, each with an explicit call sequence, each priced on the live rate and each stated so you can substitute your own numbers. Where a figure is a measurement from our own billing logs, it says so. Where it is arithmetic on a stated assumption, it says that instead. Where the honest answer is that something cannot be known, it is recorded as unknown rather than given a plausible number, because a plausible number in a budget is worse than a gap. ## Why a per-call rate cannot tell you what a workload costs A rate card prices resources. A workload consumes a sequence of resources, and the ratio between them is a property of your design rather than of the price sheet. The distance between those two facts is where nearly every social data budget goes wrong, and it goes wrong in the same direction every time, because the intuitive assumption is one call per thing and the real number is almost never one. Consider the simplest possible version. You want to know what people are saying about a brand. The naive model is: one post equals one unit of cost. Multiply posts by the rate, get a bill. That model is wrong twice over, in opposite directions, and the two errors do not cancel. The first error is that search does not cost one call per post. Search pages, so one call returns several posts, which makes the real cost per post considerably lower than the rate card suggests. Good news, and the reason a lot of first estimates come in high. The second error is that a scoreable record is usually not just the post. If you weight sentiment by who said it, and most useful implementations do, you also need the author. If the target of the sentiment is ambiguous without context, you need the parent post too. Each of those is an additional priced call, and unlike the search leg they are per-subject rather than paged, so they do not amortise at all. Put those together and the same 1,000 posts costs $0.105 or $0.905 depending on a design decision that has nothing to do with which vendor you chose [Source: derived from the live per-endpoint price table using the call graphs in this post.]. Nine times, on one rate card, from one flag in your pipeline. This is the shape of the problem across every workload below, and it is why the useful unit of analysis is a named workload rather than a per-call price or an abstract thousand tweets. Engineers do not budget in tweets. They budget in "monitor twelve competitor handles", "score inbound leads", "flag amplification on a launch". Those are the units this post prices. Our [pricing walkthrough](/blogs/twitter-api-cost) covers the rate card itself, and the [companion post on per-user cost](/blogs/x-api-cost-per-user-cogs-2026) runs this same method with a seat rather than a workload as the denominator. This one is about jobs. ## How many API calls does sentiment analysis actually need? Sentiment analysis needs somewhere between 0.13 and roughly 2.1 calls per post depending on how much context your scoring model consumes, and the range matters far more than the midpoint. A search-only pipeline pays for pages of matching posts and nothing else. A pipeline that weights by author, resolves conversation context, or checks amplification pays per post for each of those legs. ::directive{id="img-2"} Here is the sequence in full, for a pipeline that scores mentions of a brand. **Leg one, discovery.** A search query returns matching posts, paged. On our measured search yield of 7.62 posts per call, 1,000 matching posts take about 131 calls [Source: biller usage logs, n=396817 calls, 2026-08-13 to 2026-08-17.]. That is the cheapest leg by a wide margin and it is the one every estimate includes. **Leg two, author hydration.** If sentiment from a 400,000-follower account should weigh more than sentiment from an account created last week, you need the account. That is one profile read per distinct author. The word distinct is doing real work: in a typical brand mention stream a meaningful share of posts come from repeat accounts, so deduplicating before hydration is the single largest saving available in this workload, and it costs nothing but a set. **Leg three, context resolution.** A reply saying "this is terrible" carries no usable sentiment about your brand unless you know what it replied to. Resolving that is another call per ambiguous post. How many posts are ambiguous is a property of your query: a query on a distinctive brand term produces mostly self-contained posts, a query on a generic term produces mostly replies. **Leg four, amplification.** If you care about reach rather than count, you need engagement figures, and for anything that spread you may want the reposters. This leg is optional and it is where costs run away, because it is per-post and unbounded. The design lesson is that legs two through four are all optional and all per-subject, while leg one is mandatory and paged. Everything you can push out of the per-subject legs and into the paged leg makes the workload cheaper by a multiple rather than a percentage. ::directive{id="img-11"} ```python # The two versions of the same pipeline, and the fan-out each produces. # Rates from the live per-endpoint price table. READ = 0.0008 SEARCH_YIELD = 7.62 # measured, biller usage logs, n=396,817 def sentiment_search_only(n_posts): calls = n_posts / SEARCH_YIELD return calls, calls * READ def sentiment_with_authors(n_posts, distinct_author_ratio=1.0): """distinct_author_ratio is the lever. 1.0 means every post is a different account, which is the worst case and the one most estimates silently assume.""" search_calls = n_posts / SEARCH_YIELD author_calls = n_posts * distinct_author_ratio calls = search_calls + author_calls return calls, calls * READ for ratio in (1.0, 0.6, 0.3): calls, usd = sentiment_with_authors(1000, ratio) print(f"distinct authors {ratio:.0%}: {calls:>7.0f} calls ${usd:.4f}") ``` Run that and the dedup lever is obvious immediately. Dropping the distinct-author ratio from one to 0.3 takes a 1,131 call workload to 431, which is a 62 percent reduction from a single set membership check [Source: derived from the live per-endpoint price table using the call graph above.]. For the modelling side of sentiment rather than the fetching side, [VADER](https://github.com/cjhutto/vaderSentiment) remains the standard lexicon baseline and the [Twitter-tuned RoBERTa model](https://huggingface.co/cardiffnlp/twitter-roberta-base-sentiment-latest) is the usual transformer starting point. Neither changes the call graph, which is the point: your model choice and your API bill are independent, and only one of them is usually planned. Our [Python sentiment walkthrough](/blogs/twitter-sentiment-analysis-python) covers the implementation, and the [buyer guide for sentiment providers](/blogs/best-twitter-api-for-sentiment-analysis-2026-buyer-guide) covers the vendor question separately. ## What does each workload cost per 1,000 subjects? Here are all six workloads with their call sequences priced. The subject differs per row, and that is deliberate, because the subject is the unit the engineer running the workload actually counts. ::directive{id="dt-workload-cost"} ::directive{id="img-3"} The spread is thirty eight times from the cheapest row to the most expensive, on one unchanged rate [Source: derived from the live per-endpoint price table.]. Nothing about the vendor produces that spread. It comes entirely from two properties of each workload: whether the endpoint it depends on returns many subjects per call or exactly one, and whether it sits on the standard tier or a premium one. ::directive{id="dt-fanout"} ::directive{id="img-4"} ::directive{id="cb-fanout-per-subject"} Read those two tables together and the pattern is clean. Workloads that read posts get to amortise, because posts arrive in pages. Workloads that read accounts do not, because an account read returns one account. That single structural fact explains why bot signal screening costs roughly thirty times more per subject than search-only sentiment despite using the same standard rate on every call [Source: derived from the live per-endpoint price table.]. The premium tiers add a second axis. Thread expansion sits at $0.004 rather than $0.0008, so a thread-mining workload pays five times the standard rate on every subject before any fan-out is counted [Source: live per-endpoint price table, origin/main f489f96b.]. That is usually the correct trade, because one expansion call replaces an unknown number of manual pagination calls plus the stitching code, but it is a trade you should make knowingly rather than discover. ::directive{id="img-12"} ## How much does bot detection cost per account screened? Screening one account for authenticity costs about $0.0032 on the standard rate, or $3.20 per 1,000 accounts [Source: live per-endpoint price table, origin/main f489f96b.], because it needs four separate per-account reads and none of them batches [Source: derived from the live per-endpoint price table using the call graph in this section.]. That makes it the most expensive of the common workloads per subject, and the reason is structural rather than a pricing decision. The four legs are the profile itself, the account's recent timeline, and both directions of the follower graph. Each answers a different question. The profile carries the cheap signals: account age, whether the display name and handle look generated, follower to following ratio, whether the bio is empty or templated. A meaningful share of obvious automation is caught here alone, which is why it should always run first. The timeline carries the behavioural signals: posting cadence, whether every post is a repost, whether the intervals between posts are suspiciously regular. This is the leg that catches the automation that bothered to fill in a bio. The follower graph in both directions carries the network signals, which are the expensive and most reliable ones: whether the account's followers are themselves plausible, whether the following list looks like a purchased block, whether the account sits inside a cluster that all appeared in the same week. ```python # Screen in cost order, not in accuracy order. Every account you # reject early is three calls you never make. def screen(account_id, budget_tier="full"): profile = call("user/info", account_id) # $0.0008 verdict = cheap_signals(profile) if verdict.confident: # most obvious cases exit here return verdict timeline = call("user/tweets", account_id) # $0.0008 verdict = verdict.combine(behavioural_signals(timeline)) if verdict.confident or budget_tier == "cheap": return verdict followers = call("user/followers", account_id) # $0.0008 following = call("user/following", account_id) # $0.0008 return verdict.combine(network_signals(followers, following)) ``` That ordering is the entire cost optimisation for this workload. Screening every account through all four legs costs $3.20 per 1,000 [Source: live per-endpoint price table, origin/main f489f96b.]. Exiting confidently at leg one for half of them and at leg two for a quarter of the rest takes the same 1,000 accounts to well under half that, with no loss on the cases that mattered, because the cases that exit early are the ones nothing else was going to change [Source: derived from the live per-endpoint price table using the four-leg call graph above.]. Worth naming clearly, because the search results for this topic are dominated by a different meaning of the phrase: this is bot detection in the sense of judging whether an account interacting with your brand is authentic. It is not bot detection in the sense of filtering automated traffic from your own website, which is an entirely different product category with entirely different economics. If you searched for one and found the other, that is why. Our [bot detection guide](/blogs/twitter-bot-detection-guide) covers the signal design in depth. ## What does lead scoring cost per prospect? Lead scoring off social signals costs about $1.71 per 1,000 prospects on the standard rate, and the cost sits almost entirely in the two per-prospect legs rather than in discovery [Source: derived from the live per-endpoint price table using the call graph in this section.]. That distribution is the opposite of what most people expect, and it changes the design. The sequence is discovery, hydration, then evidence. Discovery finds candidate accounts by searching for the signal that suggests intent, which pages and is therefore cheap: about 131 calls per 1,000 candidates found. Hydration reads each candidate's profile to establish who they are, which is one call each. Evidence reads each candidate's recent timeline to establish whether the intent is real and current, which is another call each. Two thousand of the 2,131 calls are in the last two legs [Source: derived from the live per-endpoint price table.]. Discovery is six percent of the bill. ::directive{id="img-7"} Which means the lever is qualification order. Every prospect you eliminate before hydration saves two calls rather than one, and the elimination signal you want is whatever you can read from the search result itself without a further call. Search results carry enough to reject a large fraction of candidates: the post text, basic account identity, recency. Rejecting on those first, and only hydrating survivors, is the difference between a pipeline you can run daily and one you run monthly and complain about. ```python # Qualify on what the search result already gave you, THEN hydrate. def score_leads(query, max_hydrations): candidates = [] for page in search_pages(query): # paged, cheap for post in page: if not cheap_qualifier(post): # zero additional calls continue candidates.append(post) # Only now do we spend two calls per surviving candidate. scored = [] for post in candidates[:max_hydrations]: profile = call("user/info", post.author_id) # $0.0008 timeline = call("user/tweets", post.author_id) # $0.0008 scored.append(score(post, profile, timeline)) return scored ``` The `max_hydrations` cap is not a nicety. It is the difference between a job whose cost is bounded by your budget and one whose cost is bounded by how many results the query happened to return that day, which is a number nobody controls. There is a second-order point here that applies to every workload in this post. A cap that truncates is bad, because you silently lose the tail. A cap that ranks first and then truncates is fine, because you lose the least valuable tail deliberately. The cheap qualifier should therefore produce a score rather than a boolean, so that the truncation is a decision instead of an accident. For the pipeline mechanics, our [advanced search operators reference](/blogs/twitter-advanced-search-operators) covers building a discovery query that rejects more at zero cost, and the [pagination guide](/blogs/twitter-api-pagination) covers the loop-termination bugs that turn a bounded job into an unbounded one. ## What do the premium workloads cost, and when are they worth it? Two workloads sit on premium tiers rather than the standard read rate, and both are frequently the correct choice despite costing more per call. Thread expansion bills at five times the standard rate and full account history at three times, and in each case the premium buys work that would otherwise be several standard calls plus the code to stitch them together [Source: live per-endpoint price table, origin/main f489f96b.]. **Thread mining.** One expansion call returns a full conversation. At $0.004 per call that is $4.00 per 1,000 threads, making it the most expensive per-subject workload short of a full history pull [Source: derived from the live per-endpoint price table.]. Whether it is worth it depends entirely on a measurement almost nobody takes, which is how deep the conversations you care about actually are. If your threads average three posts, manual pagination on the standard rate would cost less. If they average thirty, the expansion is cheaper and far simpler. The measurement is a sample of a few hundred threads and it settles the question permanently for your corpus. There is a second argument for the premium call that has nothing to do with price. A manual thread walk is a loop, loops have termination bugs, and a thread walk that terminates early produces a conversation that looks complete and is not. That failure is invisible downstream: the analysis runs, the numbers come out, and they are quietly wrong. Paying five times the rate to move that correctness risk into somebody else's code is often the better trade even when the arithmetic is close. **Full-history corpora.** Walking an account back to its earliest posts is a genuinely different operation from reading a recent page, which is why it carries its own tier at $0.0024 per call [Source: live per-endpoint price table, origin/main f489f96b.]. The cost per account depends on how many pages that account requires, which is a function of how much they have posted, so this is the one workload in this post whose per-subject cost cannot be stated as a single number without an assumption attached. At a modelled twelve pages per account, 1,000 accounts costs $28.80 [Source: derived from the live per-endpoint price table using the stated twelve-page assumption.]. At forty pages it is more than three times that. The design implication is that a full-history pull should almost always be a one-time operation per account with its result stored, never something on a schedule. If your pipeline re-walks history to find new posts, it is paying the most expensive rate in the catalogue to answer a question the cheapest one answers, and the fix is a stored high-water mark per account plus an incremental read. ```python # History once, then increments forever. The stored watermark is the # whole optimisation: without it, every run pays full-history rates. def sync_account(account_id): watermark = store.get_watermark(account_id) if watermark is None: # first sight of this account posts = list(paged("user/tweets/complete", # $0.0024/call budget_calls=HISTORY_PAGE_BUDGET, userId=account_id)) else: posts = [p for p in paged("user/tweets", # $0.0008/call budget_calls=INCREMENTAL_PAGE_BUDGET, userId=account_id) if p.id > watermark] if posts: store.set_watermark(account_id, max(p.id for p in posts)) return posts ``` Two separate budgets rather than one is deliberate. A first-sight backfill and a routine increment have completely different cost profiles and completely different acceptable truncation behaviour, and giving them a single shared constant guarantees that one of the two is wrong. The backfill can afford a large page budget because it happens once. The increment cannot, because it happens on every cycle for every account forever. ::directive{id="img-15"} The general rule these two workloads illustrate is that a premium rate is not a penalty, it is a price on a different operation. The mistake is not using a premium endpoint. The mistake is using one on a schedule where a standard endpoint answers the same question, which is a design error that a rate card comparison will never surface because both endpoints look like a line in the same table. Our [thread fetching guide](/blogs/fetch-full-twitter-thread-api-2026) covers the expansion mechanics, and the [history scraping walkthrough](/blogs/scrape-tweet-history-api-2026) covers the watermark pattern in more detail. ## What does one call actually return, and why it decides the bill One standard read call returns a variable number of items rather than a fixed page, and that variability is the single largest source of error in workload sizing. You are billed per call and you count subjects, so the conversion between those two units is a measurement rather than a constant, and it differs by an order of magnitude across the paths a single workload uses. ::directive{id="dt-yield-b"} ::directive{id="img-5"} Those figures are from our own billing logs across 396,817 successful item-returning read calls in a five day window, not from a specification [Source: biller usage logs, n=396817 calls, 2026-08-13 to 2026-08-17.]. Timeline reads averaged 18.78 items per call. All bulk reads excluding single-item detail averaged 12.96. Advanced search averaged 7.62, and 29.5 percent of those search calls returned nothing at all. The zero-return figure is the one that changes sizing decisions. A search call that matches nothing costs exactly what a full page costs. If nearly a third of your search calls come back empty, then any estimate built on a full page understates the search leg by more than a factor of two, and search is the leg every sentiment estimate starts from. There is a widely quoted per-thousand figure of $0.04 that comes from spreading the standard call rate over a full twenty item page [Source: live per-endpoint price table, origin/main f489f96b.]. Twenty is the page size the client requests by default, clamped between one and one hundred. It is a ceiling rather than a yield. Timeline reads land close to it. Search reads, on the measurement above, do not come close. The practical instruction is the same as it was for the seat model, and it is cheap. Before you commit to a budget, run your intended query shape a few hundred times and record how many items come back. It costs a few cents, it takes an afternoon, and it replaces the largest single assumption in the model with a measurement. If you cannot do that yet, size on a yield you can defend and label it as an assumption, so that when the invoice disagrees you know exactly which term to correct. Our [cost benchmark across providers](/blogs/twitter-api-cost-benchmark-2026) runs the like-for-like version of this comparison, and the [per-thousand-tweet ranking](/blogs/cheapest-twitter-api-2026-8-providers-ranked-by-real-per-1000-tweet-cost) shows how far headline figures drift once yield is included rather than assumed. ## What does the free signup credit cover, per workload? The $0.50 signup credit buys 625 standard calls, and what that means depends entirely on which workload you point it at [Source: live per-endpoint price table, origin/main f489f96b.]. The same credit covers 4,762 posts of search-only sentiment analysis and 125 thread expansions, which is a thirty eight times difference on identical money. ::directive{id="dt-freecredit-b"} ::directive{id="img-6"} That table is the clearest single argument in this post for sizing by workload rather than by rate. Two engineers can look at the same credit, the same rate card and the same documentation, form completely different expectations of what they can accomplish, and both be correct, because they are running different jobs. It is also a practical guide to what a free tier is for. Four thousand posts of search-only sentiment is enough to design a query, see what it actually matches, measure your real yield, and discover whether the signal you hoped for exists at all. That is exactly the right use. One hundred and twenty five thread expansions is enough to find out whether conversation context adds anything to your scoring, which is a smaller question but a genuinely useful one to answer before you build for it. What none of these budgets supports is production. That is not a criticism of the credit, it is arithmetic: a continuous workload consumes calls on a clock, and any fixed amount of credit divided by a continuous rate gives you a number of days rather than a mode of operation. The [free tier walkthrough](/blogs/is-twitter-api-free) covers what is available without a card across the market. ## Where does the fan-out hide? Fan-out hides in three places, and all three are invisible in a rate card and obvious in a call log. Naming them is worth more than any amount of general advice about being careful, because each one has a distinctive shape you can search for. **Hydration.** Any time your record needs a field that did not come back with the item you already fetched, that is a second call, and it is per-item rather than paged. Author details on a post. Full profile on a search hit. Parent post on a reply. Each feels like a detail and each doubles a leg. **Pagination that is not bounded by anything.** A function documented as fetching "recent posts" may issue three calls or three hundred, depending on how the loop terminates. A loop that runs until the cursor is exhausted has no cost ceiling, and on a high-volume account that is a very large number. Every pagination loop wants an explicit page cap, and the cap wants to be a parameter rather than a constant so that a backfill and a refresh can use the same code with different budgets. **Retries.** A transient failure retried three times bills three times. Retry logic usually lives in a shared client that nobody has read recently, and error rates drift upward slowly without anybody watching. This is the line item most likely to be entirely absent from a written estimate and materially present on an invoice. ```python # Every pagination loop wants an explicit budget, not just a cursor. def paged(endpoint, budget_calls, **params): """Yields items and stops at the budget. The budget is a parameter so a backfill and a refresh can share the code path.""" cursor, spent = None, 0 while spent < budget_calls: page = call(endpoint, cursor=cursor, **params) spent += 1 items = page.get("items") or [] yield from items cursor = page.get("next_cursor") if not cursor or not items: # empty page ends it too break if cursor: log.warning("%s truncated at %d calls, more data remains", endpoint, spent) ``` The warning at the end matters as much as the cap. A silent truncation is a completeness bug wearing a cost control's clothing, and downstream analysis has no way to tell a query that genuinely matched nothing from one that hit its budget. Log it, and carry the flag through to whatever consumes the result. The wider engineering community argues about exactly this trade constantly, and the argument is usually more useful than any vendor's sizing page: https://www.reddit.com/r/webscraping/comments/1kjvv68/the_real_costs_of_web_scraping/ The thread is about scraping generally rather than one API, and the conclusion it converges on transfers directly: the headline unit price is rarely the term that decides a real bill. Bandwidth, retries, failure rates and the sheer number of requests a naive design issues all move the total more than the price per request does. ## What can no API give you at any price? Some things a workload plan assumes are not expensive, they are absent, and pricing them is worse than admitting the gap. Recording an absence honestly is the difference between a plan that survives month two and one that gets rewritten. ::directive{id="dt-unknowns"} ::directive{id="img-8"} Three of the four rows in that table are tagged unknown deliberately, and the tag is doing real work. An absence cannot be measured, only observed not to appear, and a table that reported them as a confident zero would be claiming more than the evidence supports. If somebody can produce one of those fields from a public endpoint, the row is wrong and should be corrected. The one that catches the most plans is historic engagement. A post returns its current like and repost counts. It does not return what those counts were last Tuesday. Any longitudinal analysis therefore has to record its own snapshots as it goes, and a snapshot you did not take is not expensive to recover, it is impossible. This is a design decision that has to be made before the data you want exists, which is a very unusual constraint and one that is easy to miss until somebody asks for a trend chart. The practical response is a single extra column. Store the timestamp at which each row was captured, on every row, from the first day. It costs nothing, it is invisible until the moment somebody asks a question about change over time, and at that moment it is the difference between an answer and an apology. Our [post on monitoring coverage](/blogs/twitter-monitoring-api-coverage-honesty-2026) works through the related question of how to tell a quiet feed from a broken one, which is the same class of problem: distinguishing an absence of data from an absence of events. ## Do you actually need the volume you are budgeting for? Probably not, and this is the largest single saving available in social data work. Most listening questions are answered by a narrow query run frequently, not by a wide stream sampled and mostly discarded, and the wide version is usually chosen because it feels safer rather than because anybody checked. The contrarian case is worth engaging seriously rather than dismissing, because it is where a cost-sensitive engineer lands first and it is often right: https://x.com/alextalksai/status/2092848101828522157 The position, roughly, is that paying substantial money for an agent to read posts is absurd when the volume genuinely needed is small. For research and prototyping that is straightforwardly correct, and the author says as much in the same post. Where it stops being correct is production at scale, where reliability, terms compliance and not having your access disappear mid-quarter start to matter more than the unit price. The more interesting version of the argument is the one that does the arithmetic first and then rejects the workload rather than the price: https://x.com/phosphenq/status/2094149263940862446 That post works out a monthly ceiling from a unit price and a platform cap, lands on a real number, and then makes the point that reading everything is useless if you do not know what you are looking for. That is the correct instinct, and it generalises well past this one platform. The expensive workload is almost never the one you needed. It is the one you designed before you knew which question you were asking. The platform's own move to consumption billing is what makes per-workload sizing unavoidable rather than optional, because a fixed tier used to absorb a badly shaped call graph and a metered one does not: https://x.com/XFreeze/status/1980486304975487353 Three practical forms of this. **Filter at the source, not downstream.** A query that matches ten thousand posts of which you use two hundred costs fifty times what a tighter query costs, and the tighter query is usually one or two operators away. Time spent on query design pays back immediately and permanently. **Sample deliberately if a sample will do.** If your analysis reports a proportion, a sample answers it to within a confidence interval you can state. Fetching the full population to compute a number you would have got from a tenth of it is a real cost paid for a false sense of completeness. Decide the sample at ingest, record the sampling rate, and carry it through so nobody downstream mistakes it for a census. **Separate the archive question from the realtime question.** These have opposite cost shapes. Realtime wants a narrow filter run often. Archive wants a wide pull run once. Products that try to serve both with one pipeline end up paying realtime frequency on archive breadth, which is the most expensive quadrant available. The same question comes up whenever anybody tries to run large volume on a small budget, and the answers are consistently more useful than the question: https://www.reddit.com/r/webscraping/comments/1hcuuws/to_scrape_10_millions_requests_per_day/ ## What changes as volume climbs? Nothing about the per-call rate changes with volume, which means the model in this post scales linearly and the interesting changes are all structural. Three things shift as you move from ten thousand subjects a month to a million, and each one changes which optimisation is worth doing. ::directive{id="img-9"} **At ten thousand subjects a month, nothing matters except correctness.** Search-only sentiment at that volume costs about a dollar a month [Source: derived from the live per-endpoint price table.]. Any engineering time spent optimising it costs more than the entire annual bill. Build the simple version, measure it, and move on. The most common mistake at this scale is premature caching, which adds a class of staleness bugs to save an amount of money that rounds to nothing. **At a hundred thousand, deduplication and qualification start paying for themselves.** The author-dedup lever and the filter-before-hydrate lever each save a multiple rather than a percentage, and at this volume that is real money against a few hours of work. This is also the scale at which per-account attribution stops being optional, because the distribution of cost across your own customers or jobs has become uneven enough to matter. **At a million, the shape of the workload is the whole conversation.** Storage, processing and the analysis itself have usually overtaken the fetch cost, and the question becomes which parts of the pipeline can be moved off the per-subject path entirely. This is where shared reads across customers, incremental rather than full refreshes, and honest sampling stop being optimisations and become the architecture. There is a fourth threshold that is not about volume at all, and it is worth naming because the model exists to find it. At some point, for some workloads, the correct answer stops being a per-call API and becomes a bulk arrangement or a different data strategy entirely. The arithmetic tells you where that line sits for your workload, which is the only way to have that conversation without it turning into a debate about vendor loyalty. If the numbers say a per-call rate is the wrong shape at your volume, that is a finding. For the operational patterns that make high volume survivable, our [rate limit guide](/blogs/twitter-api-rate-limit-guide) covers what happens at the boundaries and the [best practices guide](/blogs/twitterapis-best-practices) covers client-side concurrency and backoff. Standard tooling helps here: [concurrent.futures](https://docs.python.org/3/library/concurrent.futures.html) is enough for most fan-out, [session-level connection pooling](https://requests.readthedocs.io/en/latest/user/advanced/) matters more than people expect at volume, and understanding [HTTP 429 semantics](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) is the difference between backing off correctly and hammering a limit. ## How do you instrument a pipeline so the model corrects itself? Log one row per outbound call carrying the workload name, the endpoint, the billed rate, the item count returned and whether it succeeded. Do it at the HTTP client rather than at the feature, because attribution added per feature misses retries, shared helpers and background jobs, which is precisely where the unexpected cost lives. ```python # The five columns that answer every question you will have. # workload_name is the one people leave out, and it is the one that # makes per-workload cost queryable instead of guessable. def call(endpoint, *, workload, **params): started = time.time() r = requests.get( f"{BASE}/{endpoint}", headers={"x-api-key": os.environ["TWITTERAPIS_KEY"]}, params=params, timeout=30, ) payload = r.json() if r.ok else {} usage_log.write( workload=workload, # "sentiment" | "bot_screen" | ... endpoint=endpoint, billed_usd=RATES.get(endpoint, 0.0008), items_returned=len(payload.get("tweets") or payload.get("users") or []), ok=r.ok, latency_ms=int((time.time() - started) * 1000), ) r.raise_for_status() return payload ``` With that in place, the question this whole post is about becomes a query rather than an estimate. ```sql -- Actual cost per workload, and the actual fan-out, for any window. -- Compare calls_per_item against the modelled fan-out in this post; -- where they disagree, the model is wrong and this is right. SELECT workload, COUNT(*) AS calls, SUM(billed_usd) AS usd, SUM(items_returned) AS items, COUNT(*)::float / NULLIF(SUM(items_returned),0) AS calls_per_item, SUM(billed_usd) / NULLIF(SUM(items_returned),0) * 1000 AS usd_per_1k_items, SUM(CASE WHEN NOT ok THEN billed_usd ELSE 0 END) AS wasted_usd FROM usage_log WHERE called_at >= now() - interval '30 days' GROUP BY workload ORDER BY usd DESC; ``` Two columns in that query are worth more than the rest combined. `calls_per_item` is your measured fan-out, and it is the number to compare against the modelled figures in this post. If your sentiment workload models at 1.13 calls per post and measures at 2.4, something in your pipeline is issuing calls nobody wrote down, and that gap is almost always retries or an unbounded pagination loop. `wasted_usd` is spend on failed calls. It should be near zero and it drifts. Putting it in its own column turns a slow degradation into a number somebody can watch, and it is the first thing to check when a bill moves without a corresponding change in volume. A [simple table](https://www.postgresql.org/docs/current/sql-createtable.html) is enough for this at most volumes, and a [sorted set](https://redis.io/docs/latest/develop/data-types/sorted-sets/) is a reasonable place to hold per-workload running totals if you want a live ceiling rather than an after-the-fact report. At genuinely large volume the log itself becomes a cost, at which point [columnar pricing models](https://cloud.google.com/bigquery/pricing) are worth reading before you pick where it lands. ## Six sizing mistakes, and what each one looks like **Counting one call per subject.** The default assumption and wrong in both directions at once, because paged endpoints make it pessimistic and hydration legs make it optimistic. The two do not cancel, and which way you are wrong depends on your workload. The fix is writing the sequence down. **Sizing search on a full page.** Nearly a third of search calls returned nothing in our measurement window [Source: biller usage logs, n=396817 calls, 2026-08-13 to 2026-08-17.]. Any estimate that assumes twenty items per search call is understating the search leg by more than double. **Forgetting that account reads do not batch.** Post reads amortise across a page. Account reads do not. A workload that reads accounts has a per-subject cost floor that volume cannot improve, and no amount of scale changes it. **Ignoring the premium tiers.** Thread expansion at five times the standard rate is usually the right call and it is never free [Source: live per-endpoint price table, origin/main f489f96b.]. A workload that expands every conversation it sees is on a different cost curve from one that expands only the conversations that matter, and the difference is a filter, not a vendor. **No cap on pagination.** A loop that runs until the cursor is exhausted has no cost ceiling. On a high-volume account this is the difference between three calls and three hundred, and it is invisible in feature-level analytics because it looks like one function call. **Estimating once and never measuring.** Yields drift, error rates drift, query shapes drift as the product changes. A model that is never checked against the call log is a document rather than an instrument, and only one of those is worth maintaining. ::directive{id="img-16"} The one that catches experienced teams is the third. Everybody eventually learns that search pages. Far fewer internalise that this makes post-reading workloads structurally cheap and account-reading workloads structurally expensive, regardless of price, and that no negotiation with any vendor changes it. It is a property of the data model rather than of the commercial arrangement. For the implementation side of building against this, the [Python tutorial](/blogs/python-twitter-api-tutorial) and the [Node walkthrough](/blogs/twitter-api-nodejs-tutorial) cover the client patterns, and the [error code reference](/blogs/twitter-api-error-codes) covers what a failed call means, which matters because retrying the wrong class of error is pure waste. Anyone building the analysis layer on top will also want a view of how to structure the dataset itself, and the [datasheets for datasets](https://arxiv.org/abs/1803.09010) framing is a good discipline for recording what your corpus does and does not contain. ## Which workload shape are you actually running? Most confusion about social data cost dissolves once you can name which of four shapes your job is, because the shape determines which lever works and every other lever is wasted effort. The four differ on their unit of work, on whether volume helps them, and on which leg ends up dominant, and none of that is visible from a price sheet. ::directive{id="cg-workload-shape"} The grid is worth reading down the rows rather than across the columns, because the rows are the questions and the columns are only four answers to them. **Unit of work** decides everything downstream. A workload whose unit is a post inherits paging and gets cheaper per subject as volume rises. A workload whose unit is an account does not, and no amount of volume changes that. This is the single most useful thing to establish about a job, and it takes one sentence. **Whether volume batches** is the second-order version of the same question and it is where intuition most often fails. People expect bulk operations to get cheaper, because that is how almost every other kind of infrastructure behaves. Account reads do not, because there is no page of accounts to fetch. A budget built on an assumption of bulk discount is wrong by exactly the factor the discount was expected to provide. **The dominant cost leg** is where optimisation effort should go and it is rarely where people put it. In sentiment work the dominant leg is author hydration, not search, so query tuning feels productive and moves very little. In lead scoring it is the two per-prospect legs, so improving discovery is similarly beside the point. Find the dominant leg by measurement before spending a week on the wrong one. **The worst assumption** column is the one to read before writing any estimate, because each of those four assumptions is both extremely common and quietly expensive. One call per post. Batching exists. Search is the cost. Threads are shallow. Every one of them produces an estimate that is wrong by a multiple rather than a margin. A workload that does not fit any of the four columns cleanly is usually two workloads sharing a pipeline, and separating them is normally both cheaper and clearer. A brand monitor that also enriches leads is doing post-reading and account-reading work at once, and the account-reading half will dominate the bill while the post-reading half dominates the design attention. ## Sizing a real month, end to end Abstract per-thousand figures are useful for comparison and useless for a budget conversation, so here is one worked month at a stated volume, with every term visible and every assumption named. ::directive{id="pm-workload-panel"} The scenario: a brand monitor tracking a moderately busy consumer brand, matching 120,000 posts a month, scoring each one weighted by author. That volume is chosen because it is high enough for the structure to be visible and low enough to be a real early-stage product rather than an enterprise deal. **Discovery.** 120,000 posts at the measured search yield of 7.62 posts per call is about 15,749 calls, which at the standard rate is $12.60 for the month [Source: derived from the live per-endpoint price table using the measured search yield.]. **Author hydration.** If every post came from a distinct account, that is 120,000 profile reads at the standard rate, which is $96.00 [Source: derived from the live per-endpoint price table.]. Together with discovery the month costs $108.60, and hydration is 88 percent of it. ::directive{id="img-13"} That 88 percent figure is the finding, and it inverts where most teams put their effort. Query tuning improves the twelve percent. Author deduplication improves the eighty eight. If half your matched posts come from accounts you have already hydrated this month, and on a real brand stream that is a conservative assumption, then a cache keyed on account id with a sensible expiry takes the hydration leg to $48.00 and the month to $60.60, which is a 44 percent reduction from one dictionary [Source: derived from the live per-endpoint price table using the stated deduplication assumption.]. Three refinements make that number better still, in descending order of value. **Cache profiles across time, not just within a run.** Follower counts move slowly. A profile fetched yesterday is almost certainly good enough for scoring today, and the expiry you can tolerate is a product decision rather than a technical one. Teams routinely cache within a batch and refetch the same accounts tomorrow, which captures a fraction of the available saving. ::directive{id="img-14"} **Hydrate lazily rather than eagerly.** If a post's sentiment is neutral or the account is irrelevant to the report, its author details were never needed. Scoring in two passes, a cheap pass on the post text and an expensive pass only on what survives, converts a fixed hydration cost into a variable one that tracks how much of the stream actually matters. **Tighten the query before anything else.** Everything above optimises the cost of processing what you fetched. A query that matches 120,000 posts where 40,000 would have answered the same question is paying a third more on every leg simultaneously, and query design is the only lever that improves all of them at once. None of the three requires a vendor conversation, a plan change or a negotiation. All three are afternoon-sized changes to a pipeline, and together they routinely take a workload like this to a third of its naive cost. That is the practical answer to the question this post exists to answer, and it is why the call graph rather than the rate card is the thing worth writing down. ## Objections worth taking seriously **"This is a lot of ceremony for a monitoring script."** At ten thousand subjects a month it genuinely is, and the honest answer is to build the simple version and move on. The test is not whether the cost is small today but whether the mechanism that generates it is bounded. A small cost from an unbounded loop is a large cost that has not happened yet, and the twenty minutes it takes to write the call sequence down is what tells you which one you have. **"We will just cache everything."** Caching is the correct instinct and it is not a complete answer, because it helps exactly the legs that repeat and does nothing for the legs that exist to discover something new. Profile reads cache beautifully. Search does not, since a cache hit on the discovery query means you did not look. Since the balance between those two differs per workload, caching changes the size of the bill without changing its shape, and you still need to know the shape. **"Our vendor's page says the price per thousand, we will use that."** Any per-unit figure that is not the unit you are billed in is a model with assumptions inside it. The per-thousand figures in this post are stated with their basis attached for exactly that reason, and the measured yields are the input that makes them real. Use somebody's per-thousand number if you like, but know which yield it assumes and check that yours resembles it. **"We are on a fixed monthly plan so this does not apply."** It applies with one substitution. Your marginal cost is zero until you hit the ceiling and then it becomes the price of the next tier divided by the headroom it buys, which is a step function rather than a line. Step functions are harder to reason about, not easier, and the workload that pushes you over a boundary costs you the whole increment. Model headroom consumed per unit of work instead of dollars per unit, and everything else in this post transfers unchanged. **"We need completeness, sampling is not an option."** For compliance archives and anything where a single missed item is a material failure, that is correct and the cost is the cost. For the large majority of analytical questions it is not, and the belief that it is usually survives because nobody has been asked to defend it. The useful discipline is to write down what a ten percent sample would fail to answer. Sometimes the list is real and short, which settles it. More often the list is empty, which settles it the other way. ## How the numbers in this post were produced Every figure here falls into one of three categories, and each is labelled where it appears rather than in a footnote, because a provenance note at the bottom of a long page is a note nobody reads at the moment they are copying a number out. **Measured** means it was read from our own billing records. That covers the yield figures: 18.78 items per call on timeline reads, 12.96 across bulk reads excluding single-item detail, 7.62 on advanced search, and the 29.5 percent of search calls that returned nothing, all across 396,817 successful item-returning read calls between 2026-08-13 and 2026-08-17 [Source: biller usage logs, n=396817 calls, 2026-08-13 to 2026-08-17.]. That window is the entire period since item counts began being recorded, so it is a census of the available data rather than a sample of it. Anyone can falsify these by running the same query over the same window and getting different answers. **Derived** means arithmetic on a stated input. Every workload cost in this post is derived: a call sequence we state, multiplied by a rate read from the live per-endpoint price table, multiplied by a volume we state. The call sequences are worked examples drawn from how these pipelines are normally built, not measurements of any customer's traffic, and they are written out step by step precisely so you can disagree with one and recompute. If your sentiment pipeline does not hydrate authors, the row that assumes it does not apply to you and the row above it does. **Unknown** means we could not establish it and declined to estimate. Three rows in the absent-capability table carry that tag. An absence cannot be measured, only observed not to appear, and tagging it honestly is more useful than a confident zero, because a confident zero closes an investigation that should stay open. What is deliberately not in this post: any claim about what a competitor charges for a workload, because that would require running their pipeline rather than reading their price sheet, and reading a price sheet is exactly the shortcut this post argues against. The comparison this post makes is between workloads on one rate, not between vendors on one workload. ## The pre-build checklist Eight questions decide whether a workload budget holds. Every one is answerable before a line of pipeline code exists, and each is dramatically cheaper to answer now than after the first invoice. ::directive{id="img-10"} **1. What is one unit of work?** A post, an account, a prospect, a conversation. Every subsequent number depends on this and it is often left implicit. **2. What is the full call sequence for one unit?** Written down, with an endpoint per step. If the list has one line, check it again, because it almost never does. **3. Which steps batch and which do not?** The batched steps get cheaper with volume. The per-subject steps never do. **4. What is the measured yield on each batched step?** Not the documented page size. The number your own queries actually return. **5. Where is the cheapest qualifier?** Whatever lets you drop a subject before the expensive legs run. This is usually the single largest saving available. **6. Is every pagination loop capped?** With the cap as a parameter, and a warning when it truncates. **7. What does this workload assume that no API can provide?** Deleted content, historic engagement, private accounts. Record these as unknown rather than pricing them. **8. Can you query actual cost by workload today?** If not, add the workload name to your call log before you need it. Working through those eight takes an afternoon. It converts the largest unknown in a data pipeline into a number you can defend, and it does it before anybody has committed to an architecture that assumes the wrong answer. ## What we would actually do If we were sizing a new social data workload today, the sequence would start with the cheapest possible version and buy information rather than coverage. Start search-only. No hydration, no context resolution, no amplification. Run the query, look at what comes back, and find out whether the signal you are hoping for is present at all. At the volumes most projects start with, this costs less than a coffee and it answers the only question that matters early, which is whether the project is worth building. Measure the yield on that first run. Record items per call on your real query rather than inheriting an assumption. Every downstream number depends on it, and it is the cheapest measurement in the whole exercise. Add exactly one leg at a time, and measure the cost delta each time. Hydration first, because it usually carries the most scoring signal per call. If the scoring improvement does not justify the multiple, stop there. Most pipelines end up with fewer legs than their designers expected, which is a good outcome that only happens if the legs were added one at a time. Cap everything from the first day. Page budgets, hydration limits, per-run ceilings. A cap that never fires costs nothing. A missing cap costs exactly once, and it is memorable. And instrument by workload name from the first call, so that in three months the question "what does bot screening cost us" is a query rather than an argument. For the mechanics of building a sentiment pipeline end to end, this walkthrough is a reasonable starting point and it covers the modelling side this post deliberately does not: https://www.youtube.com/watch?v=pgZcP852dMg One last framing, because it is the thing most likely to be useful six months after reading this. The call graph is a design artifact, not a finance artifact. It happens to produce a number that finance cares about, which is why it usually gets written during a budget conversation, but its real value is upstream of that. A written call sequence tells you which parts of your pipeline are load-bearing, where a failure would be silent, which legs could be removed without losing anything, and what your product would still be able to do if one endpoint disappeared next quarter. Every one of those is an engineering question, and the cost figure is close to a side effect. That is also the honest answer to anyone who finds this whole exercise excessive for a job that costs a few dollars a month today. The arithmetic is not the point. The list is the point, and the list is worth having at any volume, because a team that can produce it on request is a team that understands its own data dependency well enough to change it deliberately. Teams that cannot produce it are usually one platform announcement away from finding out which parts of their product were resting on an assumption. The underlying point is one sentence. A rate card prices calls, a workload consumes sequences, and every social data budget that surprised somebody was a sequence nobody wrote down. Writing it down is an afternoon, and it is the difference between a number you can defend and a number you will have to explain. ## Frequently Asked Questions ### How many API calls does sentiment analysis actually need? More than one per post, and the ratio is the whole answer. A minimal pipeline needs only the search calls that return matching posts, which on our measured search yield of 7.62 posts per call is about 131 calls per 1,000 posts. A pipeline that weights sentiment by who said it also hydrates each distinct author, which adds up to 1,000 more calls for the same 1,000 posts and multiplies the cost by roughly nine. The fan-out ratio, not the per-call rate, is what decides your bill, and it is a property of your scoring design rather than of the vendor you pick. ### What does brand sentiment monitoring cost per 1,000 posts? On the standard $0.0008 read rate, a search-only pipeline costs about $0.105 per 1,000 matching posts, because the measured search yield is 7.62 posts per call and 29.5 percent of search calls return nothing at all. Adding author hydration for every distinct account takes the same 1,000 posts to about $0.905. That is a nine times swing driven entirely by one design decision, and it is the reason a per-call rate quoted on its own cannot tell you what monitoring a brand will cost you per month. ### Why is bot detection more expensive than sentiment analysis per subject? Because bot detection reads accounts rather than posts, and accounts do not come in pages. Screening one account for authenticity typically needs a profile read, a timeline read, and both directions of the follower graph, which is four calls per account with no batching to amortise them. That is $0.0032 per account on the standard rate, or $3.20 per 1,000 accounts screened, roughly thirty times the per-subject cost of search-only sentiment. The unit of work is the difference, not the price. ### What does lead scoring off social signals cost? A pipeline that finds candidates by search, hydrates each profile, and reads each timeline for intent signals costs about $1.71 per 1,000 prospects on the standard rate. The search leg is cheap because it pages, and the two per-prospect legs dominate at 1,000 calls each. The practical lever is qualification order: filter on the cheap paged signal first and only hydrate the survivors, because every prospect you drop before hydration saves two calls rather than one. ### How do I size an API budget before writing the pipeline? Write the call sequence for one logical unit of work, count the calls by endpoint, multiply by the billed rate for each, then multiply by your expected volume. Do that before any code exists and it takes twenty minutes. The step people skip is counting the fan-out: a workload almost never consumes one priced resource per unit, it consumes several at different rates, and the ratio between them is where budgets break. Then measure the real yield once you have a prototype, because the assumed page size is always more optimistic than the observed one. ### Do I need the full firehose for social listening? Almost never, and this is the most common way a social data budget gets ten times larger than it needed to be. Most listening questions are answered by a narrow filtered query run frequently, not by an unfiltered stream sampled and discarded. If your analysis tolerates a sample, take a sample deliberately at ingest rather than paying to fetch everything and throwing most of it away downstream. The cases that genuinely need completeness are compliance archives and anything where a single missed post is a material failure. ### What can no Twitter API give you at any price? Several things a workload plan often assumes. Deleted posts are gone rather than expensive. Private and protected accounts are not readable through a public API by design. Historic engagement counts as they stood at a past moment are not recoverable after the fact, only the current values, which is why any longitudinal analysis has to record its own snapshots as it goes. Recording those honestly as unknown, rather than pricing them, is the difference between a plan that survives and one that has to be rewritten in month two. ### How much does the free signup credit cover per workload? The $0.50 credit buys 625 standard calls, and what that means depends entirely on the workload. It covers roughly 4,762 posts of search-only sentiment analysis, about 552 posts of sentiment with author hydration, 293 lead-scoring prospects, 156 accounts screened for bot signals, and 125 thread expansions. Those five numbers differ by a factor of thirty eight on the same credit, which is the single clearest illustration of why per-workload sizing beats per-call comparison. ### Why do account-reading workloads cost more per subject than post-reading ones? Because posts arrive in pages and accounts do not. A search call returns several matching posts at once, so the cost of the discovery leg amortises across every post it returns. A profile read returns exactly one account, so there is nothing to amortise and the per-subject cost has a floor that volume cannot lower. That single structural difference is why screening 1,000 accounts for bot signals costs about $3.20 while finding 1,000 matching posts costs about $0.105 on the same standard rate. It is a property of the data model rather than of any commercial arrangement, so no negotiation changes it. ### Should a full-history backfill ever run on a schedule? Almost never. Full account history carries its own premium tier because walking an account back to its earliest posts is a different operation from reading a recent page, and a pipeline that re-walks history to discover new posts is paying the most expensive rate in the catalogue to answer a question the cheapest rate answers. Store a high-water mark per account after the first backfill, then read incrementally against it on every subsequent run. Give the backfill and the increment separate page budgets, because a first-sight pull and a routine refresh have completely different acceptable truncation behaviour and a shared constant guarantees one of them is wrong.