# Metric substitutions (/docs/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 65 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](/docs/computed-fields.md). ## 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 Instagram needs more than one line because its nulls are media-type dependent. - `post.engagement.saves` is always `null`. Instagram exposes only a per-viewer `viewer_has_saved` boolean, never an aggregate count. This is platform-private. - `post.engagement.shares` is `null` on the base endpoints. A reshare count is available only on `post/stats` and the `*-full` composites (`instagram/profile/reels/full`, `instagram/profile/posts/full`), which pull the mobile `reshare_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 are `null` there on every endpoint, and the views-derived `computed.engagement_rate` and `computed.estimated_reach` are `null` with 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/stories` returns each active story's media, timestamps, and IDs with the engagement leaves `null`. No API from any provider can return story metrics or story reach for an account you do not own. ## 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 | | Reddit | `post.engagement.views`, `.shares`, `.saves` | Reddit exposes no view or share counts, and "saved" is a per-viewer flag upstream, never a count | | Reddit | `author.verified` | Not exposed | | Facebook | `post.engagement.saves` | Not exposed | | Facebook, list surfaces | `author.avatar_url`, `post.media_urls` | The list-item shape is slimmer than the single-object shape | | 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, slim search-user rows | `author.avatar_url`, `author.bio`, `author.followers` | The slim row shape omits them | | 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 into `posts_count`, because a listener figure sitting in a "number of posts" leaf corrupts any cross-platform posts aggregate. - **Google Finance** `previous_close` can 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 populated `brand`. 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-reviews`** and **`app_store/app-reviews`** default `sort_by` to 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 is `newest` for Google Play and `most_recent` for the App Store. An explicit `sort_by` always wins. - **`instagram/search/hashtag`** defaults `type` to `top`. Pass `type=recent` or `type=clips` to change it. ## Next steps - [Computed fields](/docs/computed-fields.md): How engagement_rate, language, category, and reach are derived. - [Unified schema](/docs/schema.md): Every canonical field on every object. - [Schema compatibility](/docs/schema-compatibility.md): What may change in a published schema, and what never does. - [Response schema](/docs/response-schema.md): The envelope, headers, and partial-data warnings.