Insights

A channel's view velocity, ranked against channels its own size

GET/data-api/v2/insights/youtube/{handle}/benchmark
Scope
insights:read
Freshness
catalog read
Group
Insights
Platforms
1 of 7

What this endpoint answers

YouTube subscriber counts are rounded at the source and lifetime view counts are not, so views are the only exact series this platform gives us. Differencing them across a fortnight gives views per day, and this ranks that figure inside the percentile ladder for channels in the same subscriber band. It answers the question the activity endpoint deliberately leaves open: whether 1.8 million views a day is a lot for a channel this size.

This is a catalog read: it is served from the CRM Solid database in milliseconds, costs one budget unit, and reports how old the reading is in meta.cache_age_s. It needs the insights:read scope (Insights): Engagement rates, top posts, viral patterns, cohort benchmarks.

Good to know

  • The floor is one million subscribers and it is a measurement, not a pricing decision. Daily-series coverage in the 30 days to 2026-08-31: every one of the 463 channels above 10M and all 4,425 between 1M and 10M had 14 or more observed days; of the 10,421 channels between 300k and 1M only 261 did; below 100k not a single channel had one. The crawler writes 5,000 to 8,000 rows a day against a 35,149 channel catalog and spends them at the head. A percentile from the 300k-1M band would rank a channel against the fifth of its peers that happen to be crawled, so the endpoint refuses instead, with placement.reason = below_coverage_floor.
  • views_per_day is (last observed total - first observed total) / days spanned, not a mean of daily deltas. Two consecutive rows carrying an identical lifetime view count are common and mean the upstream returned a cached figure, not that the channel had a day with no views. Endpoint differencing is immune to that; averaging deltas is not.
  • cohort.quantiles is a 101 point ladder, p0 through p100, and placement.percentile is read off it rather than interpolated between the five headline figures. cohort.measured is the ladder's real n; quote it rather than cohort.channels.
  • The bottom of every ladder reads zero, and that is a real measurement rather than a missing one: 15 of the 463 channels above 10M subscribers (3.2%), 34 of 1,426 in 3M-10M (2.4%) and 81 of 2,999 in 1M-3M (2.7%) added no views at all across the 14 days to 2026-08-31. A channel that stopped is still a channel in the band, so it stays in the population being ranked against.
  • The ladder is rebuilt at most every thirty minutes and ladder_computed_at says when. It moves slowly: the 10M+ median measured 1,772,605 views a day over a 14 day window and 1,731,396 over a 7 day one.
  • views_per_day_per_1k_subscribers divides an exact number by a rounded one and inherits the rounding, which is exactly why the ladder is built on views_per_day instead. Use it to compare a channel with itself, not with another channel.
  • views_per_video is a lifetime average over the whole back catalogue, so it is dominated by whatever the channel published years ago. It is a level, it is not part of the ladder, and it is not a measure of current performance.
  • There is no subscriber growth endpoint for YouTube for the same reason: every subscriber count we hold is a multiple of ten. GET /insights/activity/platforms carries that measurement in full.

Parameters

Every value the call accepts, with the example the spec ships so the request runs as written.

NameInRequiredExampleWhat it does
handlepathrequiredveritasiumYouTube handle without the @, or a UC... channel id.

Call it

Authenticate with a bearer token or the x-api-key header. Keys are server-to-server credentials. Never embed one in front-end code - call the API from your own backend and forward the result.

curl
curl "https://crmsolid.com/data-api/v2/insights/youtube/veritasium/benchmark" \
  -H "Authorization: Bearer psk_live_..."
JavaScript
const res = await fetch("https://crmsolid.com/data-api/v2/insights/youtube/veritasium/benchmark", {
  headers: {
    Authorization: `Bearer ${process.env.CRM_SOLID_DATA_API_KEY}`,
  },
});

if (!res.ok) {
  const { error } = await res.json();
  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);
}

const { data, meta } = await res.json();
Python
import os

import requests

res = requests.get(
    "https://crmsolid.com/data-api/v2/insights/youtube/veritasium/benchmark",
    headers={"Authorization": f"Bearer {os.environ['CRM_SOLID_DATA_API_KEY']}"},
    timeout=30,
)
res.raise_for_status()

payload = res.json()
data, meta = payload["data"], payload["meta"]

Keys look like psk_live_... for production keys, psk_test_... for test keys and are minted in the panel.

What comes back

Success is { data, meta }. Failure is { error: { code, message, request_id } }. The body below is the spec's own example: the values in it are illustrative readings, not live numbers.

Channel id
UCHnyfMqiRRG1u-2MsSQLbXA
Handle
veritasium
Title
Veritasium
Status
active
Subscribers
21,200,000
Subscribers precision
rounded
Hidden subscribers
no
Lifetime views
4,504,635,300
Videos
515
Views per video
8,746,864.7
Published at
7/21/2010, 7:18:02 AM
Window requested days
14
Window from
2026-08-17
Window to
2026-08-31
Window days observed
15
Window days spanned
15
Window coverage pct
100
Window longest gap days
0
Velocity measured
yes
Velocity views per day
1,868,892.7
Velocity views per day per 1k subscribers
88.15
Velocity first day
2026-08-17
Velocity first value
4,478,470,802
Velocity latest day
2026-08-31
Velocity latest value
4,504,635,300
Cohort band
10m_plus
Cohort label
10M+ subscribers
Cohort min subscribers
10,000,000
200 GET /insights/youtube/{handle}/benchmark
{
  "data": {
    "channel_id": "UCHnyfMqiRRG1u-2MsSQLbXA",
    "handle": "veritasium",
    "title": "Veritasium",
    "status": "active",
    "subscribers": 21200000,
    "subscribers_precision": "rounded",
    "hidden_subscribers": false,
    "lifetime_views": 4504635300,
    "videos": 515,
    "views_per_video": 8746864.7,
    "published_at": "2010-07-21T07:18:02.000Z",
    "window": {
      "requested_days": 14,
      "from": "2026-08-17",
      "to": "2026-08-31",
      "days_observed": 15,
      "days_spanned": 15,
      "coverage_pct": 100,
      "longest_gap_days": 0
    },
    "velocity": {
      "measured": true,
      "reason": null,
      "views_per_day": 1868892.7,
      "views_per_day_per_1k_subscribers": 88.15,
      "first": {
        "day": "2026-08-17",
        "value": 4478470802
      },
      "latest": {
        "day": "2026-08-31",
        "value": 4504635300
      }
    },
    "cohort": {
      "band": "10m_plus",
      "label": "10M+ subscribers",
      "min_subscribers": 10000000,
      "max_subscribers": null,
      "channels": 463,
      "measured": 463,
      "coverage_pct": 100,
      "percentiles": {
        "p10": 80274.7,
        "p25": 547474,
        "p50": 1772605.1,
        "p75": 4170286.6,
        "p90": 10412050.8
      },
      "quantiles": [
        0,
        0,
        0,
        0
      ]
    },
    "placement": {
      "benchmarked": true,
      "reason": null,
      "percentile": 51,
      "vs_median": 1.054,
      "ladder_computed_at": "2026-08-31T14:20:11.402Z"
    }
  },
  "meta": {
    "request_id": "req_9f2c41a8b3d5",
    "generated_at": "2026-08-23T09:14:02.317Z",
    "took_ms": 42
  }
}

The meta block

  • request_idstringrequired

    Unique id for this request. Quote it in a support ticket.

  • generated_atstringrequired

    Server time the response was produced.

  • took_msintegerrequired

    Milliseconds spent server-side.

  • pagePageoptional
  • sourcestringoptional

    Which backend served the payload, for endpoints with more than one.

  • cache_age_sintegeroptional

    Age of the underlying data in seconds. 0 for live reads.

When it fails

GET /insights/youtube/{handle}/benchmark documents 8 failure statuses. Branch on error.code, which is stable and enumerated; message is prose and may change.

  • 401Unauthorizedunauthorized | invalid_key

    No key was presented, or the key is unknown, revoked or expired.

  • 402PaymentRequiredpayment_required | subscription_inactive

    The key is valid but the plan behind it cannot serve the call: the included requests are spent and overage is switched off, capped or unfunded (payment_required), or the billing period lapsed and was not renewed (subscription_inactive). Retrying does not help; paying does. The X-Plan-* headers on this response say how far past the line you are.

    Carries X-Plan, X-Plan-Limit, X-Plan-Overage, X-Plan-Period-End, X-Plan-Remaining.

  • 403Forbiddenforbidden_scope | forbidden_ip

    The key is valid but not allowed to make this call: it lacks the scope, or the request came from an address outside the key's allowlist.

  • 404NotFoundnot_found

    The addressed resource does not exist.

  • 422InvalidRequestinvalid_request

    A parameter is malformed, out of range or mutually exclusive with another. `details` names the offending fields.

  • 429RateLimitedrate_limited | quota_exceeded

    Either the burst ceiling for the current minute or the daily quota is spent. Distinguish with the code: rate_limited clears within the minute, quota_exceeded does not clear until 00:00 UTC.

    Carries Retry-After, X-Quota-Limit, X-Quota-Remaining, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Request-Id.

  • 500InternalErrorinternal_error

    Something failed on our side. Internals are never leaked; quote the request id.

  • 503Unavailableupstream_timeout | upstream_error | not_configured

    The request could not be served right now. BRANCH ON error.code, not on the status: 'upstream_timeout' means a source was too slow (this is what a catalog query hitting its 15-second statement timeout returns, so it is reachable from any endpoint that reads the corpus, not only the live-scrape ones) and the same call is worth retrying with backoff - narrowing it with a smaller limit, a filtered scope or a less popular account makes it far less likely; 'upstream_error' means a source was unreachable, so back off further; 'not_configured' means the capability has no backing service in this deployment, and retrying will never help.

Every response carries X-Request-Id and meta.request_id. Quote it in support requests.

Coverage and limits

This endpoint covers YouTube. Rate limits come from the tier on your key.

YouTube
TierRequests a minuteRequests a dayLive reads a minute
free301,0005
standard12025,00020
pro600250,00060
unlimited6,00010,000,000600

This call only draws on the ordinary per-minute and per-day columns. Every response reports where you stand in X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset and the X-Plan headers.

Start calling it

A key takes a minute to mint in the panel, no card. The reference covers authentication, the envelope, scopes, rate limits and every error code in one page.

Reference path: /data-api/reference/insights-youtube-benchmark

We value your privacy

We use cookies to improve our site, analyze traffic, and personalize ads. You can accept all, reject non-essential, or customize your choices. Read our Cookie Policy.