InsightSocial API

Computed fields

How engagement_rate, language, content_category and estimated_reach are calculated, and when each one is null.

Profile, post and comment responses include a computed block next to the platform data, wherever the endpoint supports it. Its shape is identical on every platform, so an Instagram creator's engagement rate can be compared directly with a TikTok creator's.

FieldTypeRangenull when
engagement_ratenumber | null0.0 to 1.0The divisor is missing or zero. On posts, also when likes, comments and shares are all null. On profiles, also when the raw ratio is above 1.0.
languagestring | nullISO 639-1 (en, ko, ja, ...)Latin-script text that is too short, too few words, or reads like a headline. Non-Latin scripts have no length floor. Also when the language is not recognized.
content_categorystring | null14 categories or "other"The text is shorter than 10 characters after trimming. This gate is separate from, and looser than, the one on language.
estimated_reachnumber | nullinteger, 0 or morePosts: views is missing or 0, which includes every post without a view count, such as Instagram photos. Profiles: always null.

Real numbers or honest nulls

A computed field is either a real value or null. It is never 0 standing in for "unknown", because that difference matters as soon as you sort or filter.

When a value had to be pushed back into range, most often an engagement_rate above 1.0, a note explaining it is added to data._warnings.

Where the block appears

  • Author: data.computed on profile responses.
  • Post: data.computed on single-post responses, and on each item of a post list.
  • Comment: data.computed on single-comment responses, and on each item of a comment list. Comments get a smaller block, just { language }, plus labels on the comment lists that are judged (see Labels). The other three fields need signals a comment lacks: there is no view count to divide by, and categories and reach do not make sense for a one-line reply.

Transcript responses carry no computed block. Items on the post search lists also get computed.relevance next to the four fields on this page.

Labels sit alongside

The four fields here come from code working on the row's own numbers and text, so the same input always gives the same output. computed.labels and computed.relevance are different: each is a model's reply to a single yes-or-no style question about the row, such as whether a post is sponsored or whether a result matches your query, given as a probability or a level. They are on by default and add no credits. Labels covers which lists carry which labels, the shapes, and judgments=off.

engagement_rate

A comparable engagement signal between 0.0 and 1.0, rounded to 6 decimal places.

Profiles

Formula
engagement_rate = author.likes_count / author.followers

The result is null when followers is 0 or likes_count is missing. No data and zero engagement are kept apart.

Instagram. Instagram profiles never fill author.likes_count. On Instagram profile endpoints the rate is instead built from up to about 12 recent posts included in the same response:

Formula
engagement_rate = (mean(post_likes) + mean(post_comments)) / followers

Only posts with both likes_count and comments_count are used. With no usable posts the rate is null. No other platform uses this fallback.

Posts (single posts and list items)

Formula
engagement_rate = (likes + comments + shares) / views

A missing or zero views gives null, on purpose. Older tweets and posts on surfaces with no view count get no rate rather than a made-up one. Dividing by 1 instead would surface the raw engagement total (say 26573) as if it were a rate.

The rate is also null when likes, comments and shares are all null. A real 0 in any of them is data, so a post with genuinely no engagement reports 0. Because both fields gate on views on their own, a post can have an estimated_reach and a null engagement rate at the same time.

The same formula on every platform. What varies is which inputs each platform provides:

  • Missing likes, comments or shares count as 0. Instagram and YouTube do not report shares, so their numerator is likes + comments. Nothing is invented, and one missing term does not null the rate. This is the most common reason a hand calculation disagrees: if you add a shares figure on Instagram or YouTube, your number will differ from ours, where shares contributed 0.
  • Missing views stays null, and a null divisor makes the whole rate null.

Rows filled by include

Some list endpoints accept an include parameter that joins each row to its own detail record in the same call, for example /v1/facebook/profile/posts?include=engagement. When a join adds data to a row, that row's engagement_rate and estimated_reach are recalculated from the fuller numbers, so the row matches what a direct call for that item would return.

Only those two fields are recalculated. language and content_category read the caption, which a join does not change.

When your own calculation will not match

If you check engagement_rate by recomputing it from the raw fields, the table below covers every mismatch. In each case our value is correct and the gap comes from an input you do not have.

PlatformMatches (likes+comments+shares)/views?Why
TikTokYes, exactlyViews, likes, comments, shares and saves are all present.
Twitter/XYes, where the rate is not nullMany tweets have no view count (older tweets and a lot of replies), so their rate is null. That means no view count, not zero engagement. Filter to non-null rates before using the field as a feature.
InstagramNoShares and saves are not public, so shares count as 0 and the rate is effectively (likes + comments) / views. Photos have no view count and get null; reels have views and get a rate.
YouTubeNoShares are not returned either, so the rate is (likes + comments) / views.
FacebookPartlyFacebook's like figure appears to count all reactions, not just likes, so the numerator can be higher than a likes-only calculation. Treat the Facebook rate as including all reactions.
YouTube live streamsNo (0 or null)Live and upcoming list rows have a view count but no likes or comments, giving 0, or no view count before the broadcast, giving null. Fetch a finished stream through the single-video endpoint for full engagement.
Reddit, Threads (list items)No (always null)Neither shows a per-post view count on lists or feeds. On Reddit, use score as the engagement signal. A single Threads post fetched by URL does have a view count and gets a rate.

A very small share of post rows compute above 1.0; those are capped at 1.0 with a warning.

Out of range: posts cap, profiles null

Posts cap at 1.0. A post ratio above 1.0 (an old tweet with more likes than recorded views, or reshares outnumbering views) still says something, so it is set to 1.0 and a warning is added:

Response
{
  "data": {
    "computed": { "engagement_rate": 1.0 },
    "_warnings": [
      "computed.engagement_rate: value exceeded 1.0 (raw: 1.42); clamped"
    ]
  }
}

Profiles return null. A profile ratio above 1.0 only happens when lifetime likes (TikTok, YouTube and Facebook profiles) are divided by today's follower count. That is not an engagement rate, so the field is null with a warning rather than a fake 1.0:

Response
{
  "data": {
    "computed": { "engagement_rate": null },
    "_warnings": [
      "computed.engagement_rate: author ratio exceeded 1.0 (raw: 86.32); returned null. A lifetime likes/followers ratio is not a real engagement rate"
    ]
  }
}

There is also a lower bound at 0, but positive inputs cannot produce a negative result, so it almost never applies.

language

A two-letter ISO 639-1 code. Regional variants are not used: you get pt, never pt-BR.

ObjectText read
Authorauthor.bio
Postpost.content.text
Commentcomment.text, with a detector tuned for comments (see below)

Missing or non-string text gives null. After that, the rules depend on the script:

  • Non-Latin scripts (Korean, Japanese, Chinese, Arabic, Devanagari, Thai) are recognized by their Unicode ranges at any length. A three-character Korean bio returns ko.
  • Latin scripts use statistical classification, which needs real sentences. URLs, @mentions, #hashtags and list markers are removed first; what is left must be at least 30 characters and 3 words. Short, mostly title-cased text, such as a name or a headline, returns null.

Those limits exist because below them the classifier is wrong too often. No answer is better than a confident wrong one.

How detection works

  1. Script check first. If the text contains characters from these ranges, the code is returned straight away, whatever the length:
    • Hangul gives ko
    • Hiragana or Katakana gives ja
    • CJK ideographs give zh
    • Arabic gives ar
    • Devanagari gives hi
    • Thai gives th
  2. Statistical classification for everything else, on the cleaned text that passed the limits above. If the classifier is not confident, the result is null.

Codes you can receive

Only these codes are returned. Anything else becomes null.

Language codes
ar  bg  ca  cs  da  de  el  en  es  fa  fi  fr  he  hi  hu
id  it  ja  ko  nl  no  pl  pt  ro  ru  sv  th  tr  uk  vi  zh

That is 31 codes.

Comments

Comments are much shorter and messier than bios or captions, and the standard classifier would leave about half of them null. Comments therefore go through their own steps, in this order:

  1. Clean the text. URLs, @mentions, #hashtags and emoji are removed. A comment that was only emoji, a date or a handle returns null.
  2. Script check. The same ranges as above, plus Hebrew (he) and Greek (el), at any length. 좋아요 returns ko.
  3. Noise check. At least 2 words and 4 letters must remain. Anything shorter is a name or an exclamation and returns null.
  4. The platform's own tag. TikTok tags each comment with a language, and Threads sometimes does. If that tag is one of the 31 codes, it is used.
  5. Short-text classification. Everything else goes to a detector built for short strings, and only clear, reliable answers are kept. That is why Great video! gets en as a comment but would be under the 30-character limit in a bio.

The same 31 codes apply. Expect about a quarter of real comments to come back null: emoji reactions, dates and one-word replies carry no language signal.

content_category

One of 14 fixed categories, or "other". It is null when the text is missing or under 10 characters after trimming. That gate differs from the one on language, so a bio can have a category and no language, or the other way round.

CategoryExample keywords
techprogramming, developer, software, ai, saas, blockchain, frontend
foodcooking, recipe, chef, restaurant, baking, vegan
gaminggaming, esports, twitch, fortnite, valorant, fps
fashionfashion, outfit, designer, ootd, streetwear
beautymakeup, skincare, lipstick, serum, moisturizer
fitnessworkout, gym, cardio, yoga, marathon, protein
traveladventure, destination, vacation, backpacking, wanderlust
musicsong, artist, album, concert, producer, spotify
educationlearning, course, tutorial, university, lecture
entertainmentmovie, tv, netflix, celebrity, comedy, series
sportsfootball, basketball, nba, olympics, championship
businessentrepreneur, ceo, marketing, finance, fundraising
newspolitics, economy, election, journalist, parliament
lifestylewellness, mindfulness, productivity, minimalism, diy
otherThe text was long enough to classify, but no category matched two different keywords.

Matching rules

Two keywords to win

A category needs at least two different keywords to be chosen. One stray word ("team", "protein", "producer") gives "other" instead of a wrong label. This is the usual reason an obviously on-topic bio comes back as "other".

  • Short keywords of 3 characters or fewer, such as "ai", "tv" and "dj", must match a whole word. They match "building with ai" but not "hair" or "said".
  • Those short words mean other things in other languages ("ai" in Portuguese and French), so they only count when computed.language is en or null. content_category is therefore calculated after language and can change with it.
  • Longer or multi-word keywords match on word boundaries. "machine learning" matches in "I love machine learning!" without spilling into neighboring words.
  • If several categories reach two keywords, the highest score wins. Ties go to the category listed first in this order: tech, food, gaming, fashion, beauty, fitness, travel, music, education, entertainment, sports, business, news, lifestyle.

Treat content_category as a quick first pass, not a taxonomy: it is keyword-based, fast, repeatable and noisy. On post lists, the niche label places a caption in a published niche taxonomy and returns null when the caption is too thin; see Labels. For anything more, run your own model over the bio and text fields.

estimated_reach

A rough upper estimate of how many distinct accounts a post reaches. Always a whole number of 0 or more, or null.

Profiles: always null

On profiles, estimated_reach is always null. A follower count and an engagement rate are not enough to estimate reach, and a profile has no view signal the way a post does. An older formula (followers * engagement_rate * 0.1) could return less than a single post's likes on big accounts, so it was removed. The field stays in the block so the shape does not change.

Posts

Formula
estimated_reach = round(views * 1.2)

Views already track reach closely, so a small multiplier gives an estimate of unique accounts that allows for some repeat viewing.

The field is null whenever views is missing or 0. Views are the only condition, so whether you get a value depends on whether the platform shows a view count for that kind of media:

  • Instagram: reels and videos have a play count and get an estimate. Photo posts and carousels with only photos have no view count, so both estimated_reach and engagement_rate are always null, while likes and comments still fill in. A carousel with a video in it does get an estimate.
  • Twitter/X: tweets without a view count (older tweets, many replies) return null.
  • Reddit and Threads list items: no per-post views, so always null.

Limits

This is an estimate, not a measurement. It works for:

  • Ranking posts or creators by rough reach when the platform does not publish reach.
  • Sizing the order of magnitude of a creator's impact for outreach.

It does not work for:

  • Forecasting paid-media returns.
  • Comparing reach between platforms that count views very differently, such as TikTok's looping views against YouTube's longer view threshold.

Example response

The values below are illustrative.

Response
{
  "success": true,
  "platform": "tiktok",
  "endpoint": "/v1/tiktok/profile",
  "data": {
    "author": {
      "username": "khaby.lame",
      "followers": 160000000,
      "likes_count": 2500000000,
      "bio": "If u wanna laugh u r in the right place"
    },
    "computed": {
      "engagement_rate": null,
      "language": "en",
      "content_category": "other",
      "estimated_reach": null
    },
    "_warnings": [
      "computed.engagement_rate: author ratio exceeded 1.0 (raw: 15.625); returned null. A lifetime likes/followers ratio is not a real engagement rate"
    ]
  },
  "credits_used": 20,
  "credits_remaining": 9480,
  "request_id": "req_1a2b3c4d5e6f",
  "cached": false,
  "idempotent_replay": false,
  "charge_reason": "miss",
  "free_call": false
}

What this shows:

  • TikTok reports lifetime likes against current followers, so the ratio is far above 1.0. The profile rule returns null with a warning instead of a fake 1.0.
  • estimated_reach is null because this is a profile.
  • content_category is "other" because the bio does not hit any category's keywords twice.

When to be careful

  • The raw value was out of range. Look for clamped (posts) or returned null (profiles) in _warnings, then decide whether to use our value or your own calculation.
  • The post has no views. Common on Instagram photos, older tweets, some Reddit endpoints and some Facebook surfaces. On Instagram this depends on the media type: a reel gets a rate and a reach estimate, a photo from the same account an hour later gets null for both.
  • language is null on a Latin-script bio. After removing links, handles and hashtags, the text was probably under 30 characters or 3 words, or read like a title. Check author.bio yourself. This never happens to Korean, Japanese, Chinese, Arabic, Devanagari or Thai text.

On this page