Metric substitutions
Where a platform's upstream cannot supply a canonical field, SocialCrawl returns an honest null rather than a fabricated value or a substituted metric. This page lists every structural null by platform.
SocialCrawl maps 68 platforms onto one canonical schema, and not every platform exposes every canonical field. When an upstream cannot supply a field, the response returns an honest null.
A canonical field never carries a fabricated value, and never carries a
different metric wearing that field's name. Where a platform exposes a
closely-related metric worth having, it goes under an ext.* namespace so the
canonical leaf keeps meaning exactly what it says.
This page is the source of truth for those cases. If a field is listed here as a structural null for a platform, its absence is by upstream design, not a bug. For how the derived computed block is calculated, see Computed fields.
The one substitution
GitHub is the single exception to the rule above. post.engagement.likes carries reactions on issues, pull requests, and comments, or stars on repos, because GitHub has no like primitive. Treat those values as GitHub's approval signal, not Instagram-style likes.
Every other close-but-different metric lives under ext.*. Spotify's author.ext.monthly_listeners is the reference case.
Instagram needs more than one line because its nulls are media-type dependent.
post.engagement.savesisnullon every surface exceptpost/statswithinclude=saves, which reads the save and repost counts for one post. On lists no source carries them at list speed, so they staynullthere.post.engagement.likesand.commentsare Instagram's own counts. A post that is also shared to Facebook carries its Facebook likes and comments in some upstream reads; those are subtracted, so the number matches what instagram.com shows on the post. Accounts that hide their like count returnnullrather than the placeholder Instagram sends.- When that subtraction happens, the response says so rather than leaving you to notice.
post.ext.facebook_likesandpost.ext.facebook_commentscarry the Facebook share that was excluded, and a note indata._warningsnames the rows it affected. Add the two together if you want the combined figure. Both keys are absent on a post with no Facebook share, and each is absent on its own when nothing was excluded from that count. post.engagement.sharesisnullon the base endpoints. A reshare count is available only onpost/statsand the*-fullcomposites (instagram/profile/reels/full,instagram/profile/posts/full), which pull the mobilereshare_count. Read shares from those surfaces.post.engagement.views, and the shares count above, exist only on video media: reels, videos, and carousels containing a video. Photo posts and photo-only carousels have no public view or share count, so both fields arenullthere on every endpoint, and the views-derivedcomputed.engagement_rateandcomputed.estimated_reacharenullwith them. Likes and comments populate on every media type.- Stories carry no engagement metrics at all. Story views, replies, likes, and reach are owner-only analytics that Instagram exposes to no third party.
/v1/instagram/storiesreturns each active story's media, timestamps, and IDs with the engagement leavesnull. No API from any provider can return story metrics or story reach for an account you do not own.
Pinterest splits its counts between the list surfaces and the pin itself.
- Search rows carry no counts at all:
post.engagement.saves,.likes(reactions),.commentsand.sharesarenullon a plainsearchcall. Addinclude=engagementand every row is filled from the per-pin lookup in the same call, at 1 extra credit per row filled./v1/pinterest/pinreturns the same counts for a single pin at 1 credit. post.engagement.viewsisnullon search rows. Pinterest does not publish a view count there.- Board rows carry saves, comments and shares, but no date:
post.published_atisnullon a plainboardcall, because the board payload has no date field.include=engagementfills it from the pin, along with a missing reaction count. post.content.textis the pin's title, or its description when the title is empty. Many pins have neither, sonullthere means the pin genuinely carries no text. Pinterest's machine-generated image caption is never mapped into it.
Threads
Threads splits its numbers between the single-object surfaces and the lists, and the lists fill on request.
post.engagement.viewsisnullonuser/postsandsearch, because Threads publishes a view count on a single post's page only.GET /v1/threads/postreturns it for one post at 1 credit, andinclude=engagementfills it on each row of either list in the same call, at 1 extra credit per row filled. Onsearchthat covers the first 20 rows.- A view count also does not exist yet on a brand-new post, on any surface: Threads publishes one once it has it. A row like that is free on
search, where the join charges only for a landed view count or display name, and it still receives its pinned flag. Measured 12/09/2026 on a page of very fresh posts: 11 of 19 carried a view count, and a direct single-post call on the other 8 had none either. post.flags.pinnedisnullonsearchrows, where the upstream omits the field rather than reporting false. The sameinclude=engagementfills it.author.followersandauthor.bioarenullonsearch/users, because the account-search index carries identity only.include=profilefills both, plus the private flag and the bio link, at 1 extra credit per account filled.post.author.display_nameisnullon theuser/postswindow and filled byinclude=engagement, or by asking for more than 15 posts, which moves the call to a source that carries it.- Every join bills only for rows that come back filled, refunds the rest, serves a recently fetched row from cache for nothing, and reports what it did in
data.hydration. See Threads.
LinkedIn needs its own section because two author leaves are weaker on a people-list row than on the member's own profile, and both are recoverable on the same call.
author.followerson asearch/people,company/people, orpost/reactionsrow is LinkedIn's rounded display bucket rather than a census: 39,000 for a member whose real count is 39,278, or 2,000 for one on 2,344. Those rows setauthor.ext.followers_approximatetotrue, so you can always tell a published bucket from an exact figure. Many rows publish no figure at all, andauthor.followersisnullthere rather than guessed at.author.followingisnullon every plain people-list row.- Neither is a permanent null. Send
include=profileand each row is joined to that member's own profile lookup inside the one call:author.followersbecomes the exact follower count,author.followingis filled with the member's connection count (on LinkedIn a member's following number is the number of connections), andauthor.location,author.joined_at, and the country, website, cover image and profile flags onauthor.extland where the row lacks them. Every row the lookup fills also setsauthor.ext.followers_approximatetofalse, including rows that arrived with no count at all, so the flag answers "is this figure exact" on every row rather than only on the ones that came in rounded. A row the lookup could not fill keeps the flag it arrived with. The list stays 10 credits and the join adds 4 credits for each row it fills, up to 50. Rows already in cache are free, rows it cannot fill are refunded, andlimit(1 to 10) caps the rows and the extra credits together. See LinkedIn.
Facebook publishes several per-post numbers on the post's own permalink and not on the list that names it, so the list rows arrive null there until you ask for the join.
post.engagement.sharesisnullon everyprofile/postsrow, andpost.engagement.viewsandpost.content.duration_secondsarenullon every reel in that feed, because Facebook carries all three on the individual post only. Sendinclude=engagementand each row is filled from its own post record in the same call, at 1 extra credit per row filled. A post Facebook publishes no share count for staysnullrather than being given a0, and that row is free.post.engagement.savesisnulleverywhere. Facebook publishes no save count on any surface.- A
profile/photosrow is the image, its caption and its permalink:post.author.display_name,post.author.avatar_url,post.engagement.likes,.comments,.sharesandpost.published_atare allnullon a plain call.include=detailsfills them from that photo's own post, at 1 extra credit per photo filled. A photo posted on its own carries the post's counts, while a photo inside a multi-photo post carries its own, which are usually far lower. Photos have no view count, sopost.engagement.viewsstaysnulleither way. - An event has no views and no likes, so on
profile/eventsandeventsthe RSVP counts ride those two leaves:post.engagement.viewscarries the interested count andpost.engagement.likesthe going count, with the same numbers under their own names onpost.ext.event.interested_countandgoing_count. The substitution is the same for values the row arrives with and valuesinclude=detailsfills. post.ext.event.end_timestampandpost.ext.event.ticket_urlare a genuine platform null on both event lists, with or without the join. Facebook published neither on any event measured, so the join does not promise them.- Every Facebook join bills only for rows that come back filled, refunds the rest, serves a recently fetched row from cache for nothing, and reports what it did in
data.hydration. See Fill the rows, or fetch them?.
Structural nulls by platform
These canonical fields are null across a platform's surfaces because the upstream never exposes them. They will not populate no matter how you call the endpoint.
| Platform | Fields that stay null | Why |
|---|---|---|
| YouTube | post.engagement.shares, post.engagement.saves | Both are owner-only analytics upstream |
post.engagement.views, .shares, .saves | Reddit exposes no view or share counts, and "saved" is a per-viewer flag upstream, never a count | |
author.verified | Not exposed | |
post.engagement.saves | Not exposed | |
| Facebook, list surfaces | author.avatar_url, post.media_urls | Slimmer rows; a photo's author fills with include=details |
| Naver Search corpora | post.engagement.views, .likes, .comments | The corpora are search indexes, not social feeds, so there are no engagement signals to return |
| Naver Search corpora | author.avatar_url, post.media_urls, post.published_at | These vary by corpus and are null on the corpora that do not carry them, for example news and image differ from blog |
| Spotify artists | author.posts_count | Spotify artists have no posts, see the note below |
| Spotify artists | author.following, author.likes_count, author.verified | Not exposed upstream |
| Spotify, non-artist rows | post.engagement.* on album, track, and podcast rows | Those rows carry no engagement block |
| Google Finance | quote.price.previous_close | null when the upstream quote omits it, a structural null rather than a fixed value |
| Google Shopping | product.brand | Google Shopping returns no clean brand field |
| Rumble, search rows | post.engagement.*, author.avatar_url, post.published_at | SERP rows carry none of them |
| Threads | author.following, author.posts_count, author.likes_count, author.joined_at | Threads publishes none of the four on a public profile |
| Threads | comment.parent_id | Nothing upstream says which reply a nested reply answers |
| GitHub | author.avatar_url on several archetypes | Not exposed |
| TikTok | post.parent_id on root comments | By design, only replies carry a parent |
| Google Business | author.username on business/updates rows | Not carried on those rows |
Three of those rows have a workaround worth knowing:
- Spotify surfaces the headline reach metric separately as
author.ext.monthly_listeners. It is not folded intoposts_count, because a listener figure sitting in a "number of posts" leaf corrupts any cross-platform posts aggregate. - Google Finance
previous_closecan be derived client-side from the current price and the change when you need it. - Google Shopping brand can often be lifted from
product.specifications[]. Only Amazon products carry a populatedbrand.
One field is a default rather than a null: the Twitter/X single-tweet shape carries no post.pinned flag, so it defaults to false.
Changed defaults
Two endpoints apply a SocialCrawl-chosen default when you omit a sort or type parameter, so results are predictable:
google_play/app-reviewsandapp_store/app-reviewsdefaultsort_byto newest-first. Google Play's own upstream default is "most relevant", and it is overridden so that an unspecified sort returns the newest reviews. The per-store value isnewestfor Google Play andmost_recentfor the App Store. An explicitsort_byalways wins.instagram/search/hashtagdefaultstypetotop. Passtype=recentortype=clipsto change it.
