Insights
Complete intelligence dossier for one X account
/data-api/v2/insights/x/{handle}- Scope
- insights:read
- Freshness
- catalog read
- Group
- Insights
- Platforms
- 1 of 7
What this endpoint answers
Everything we hold on one X account in a single call: catalog profile, engagement rates against both followers and impressions, posting-volume profile, permanently kept best posts, the virality patterns measured inside the account's own follower decile, and where its engagement rate lands on the cohort ladder. Sections degrade independently - an account the tweet engine has not reached still returns its profile plus coverage.tweets=false rather than a 404.
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
- Tweet-level coverage is a subset of the X catalog. Scanning cadence is follower-weighted, so the accounts with a reliable sample are the very large ones: the smallest account in the measured cohort sits near 1.7M followers and its tenth percentile near 1.8M. Call GET /insights/x/coverage for the live denominator, and read benchmark.population before quoting any rate.
- An account needs 8 captured original posts before its medians are treated as settled. Below that, coverage.reliable is false and the numbers are an early reading.
- Impressions are present on about 96% of captured original posts, so view_engagement_rate and reach_ratio rest on a slightly smaller sample than the follower-based rates.
- viral_patterns is cut inside the account's own follower decile when the engine assigned one, and falls back to the whole tracked cohort otherwise. peer_group tells you which.
- leaders, coverage and compare are reserved sub-paths under /insights/x and shadow X handles of the same name.
Parameters
Every value the call accepts, with the example the spec ships so the request runs as written.
| Name | In | Required | Example | What it does |
|---|---|---|---|---|
handle | path | required | elonmusk | X handle without the @. 1-15 characters, letters, digits and underscore. |
include | query | optional | engagement,benchmark | Comma-separated sections to return, from: engagement, volume, top_tweets, viral_patterns, benchmark, narrative. Omit for all of them. account and coverage are … |
metric | query | optional | engagement | Which question the viral_patterns section answers. |
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 "https://crmsolid.com/data-api/v2/insights/x/elonmusk" \
-H "Authorization: Bearer psk_live_..."
const res = await fetch("https://crmsolid.com/data-api/v2/insights/x/elonmusk", {
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();
import os
import requests
res = requests.get(
"https://crmsolid.com/data-api/v2/insights/x/elonmusk",
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.
- Account user id
- 44196397
- Account username
- elonmusk
- Account name
- Elon Musk
- Account followers
- 241,382,119
- Account tier
- S
- Account is blue verified
- yes
- Account language
- en
- Account country
- US
- Account profile url
- https://x.com/elonmusk
- Coverage tweets
- yes
- Coverage reliable
- yes
- Coverage originals sampled
- 42
- Coverage tweets sampled
- 187
- Coverage window days
- 30
- Coverage computed at
- 8/19/2026, 2:41:07 AM
- Coverage min reliable sample
- 8
- Coverage note
- Measured over a sample large enough to state as a reading.
- Engagement window days
- 30
- Engagement originals sampled
- 42
- Engagement rates engagement rate
- 0.047
- Engagement rates view engagement rate
- 0.743
- Engagement rates bookmark rate
- 0.022
- Engagement rates reach ratio
- 1.67
- Engagement medians median engagement
- 113,804
- Engagement medians median views
- 15,320,411
- Engagement peer peer group
- d10
- Engagement peer peer group label
- Accounts of similar size (decile 10 of 10)
- Engagement peer peer percentile
- 94
{
"data": {
"account": {
"user_id": "44196397",
"username": "elonmusk",
"name": "Elon Musk",
"followers": 241382119,
"tier": "S",
"is_blue_verified": true,
"language": "en",
"country": "US",
"profile_url": "https://x.com/elonmusk"
},
"coverage": {
"tweets": true,
"reliable": true,
"originals_sampled": 42,
"tweets_sampled": 187,
"window_days": 30,
"computed_at": "2026-08-19T02:41:07.000Z",
"min_reliable_sample": 8,
"note": "Measured over a sample large enough to state as a reading."
},
"engagement": {
"window_days": 30,
"originals_sampled": 42,
"rates": {
"engagement_rate": 0.0471,
"view_engagement_rate": 0.7425,
"bookmark_rate": 0.0219,
"reach_ratio": 1.67
},
"medians": {
"median_engagement": 113804,
"median_views": 15320411
},
"peer": {
"peer_group": "d10",
"peer_group_label": "Accounts of similar size (decile 10 of 10)",
"peer_percentile": 94,
"band": "Top 10% for its size"
}
},
"benchmark": {
"engagement_rate": 0.0471,
"percentile": 75,
"band": "Top 25%",
"population": {
"accounts": 3162,
"min_followers": 1687752,
"median_followers": 2840444
}
}
},
"meta": {
"request_id": "req_9f2c41a8b3d5",
"generated_at": "2026-08-23T09:14:02.317Z",
"took_ms": 42
}
}
The meta block
request_idstringrequiredUnique id for this request. Quote it in a support ticket.
generated_atstringrequiredServer time the response was produced.
took_msintegerrequiredMilliseconds spent server-side.
pagePageoptionalsourcestringoptionalWhich backend served the payload, for endpoints with more than one.
cache_age_sintegeroptionalAge of the underlying data in seconds. 0 for live reads.
When it fails
GET /insights/x/{handle} documents 8 failure statuses. Branch on error.code, which is stable and enumerated; message is prose and may change.
- 401Unauthorized
unauthorized | invalid_keyNo key was presented, or the key is unknown, revoked or expired.
- 402PaymentRequired
payment_required | subscription_inactiveThe 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.
- 403Forbidden
forbidden_scope | forbidden_ipThe 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.
- 404NotFound
not_foundThe addressed resource does not exist.
- 422InvalidRequest
invalid_requestA parameter is malformed, out of range or mutually exclusive with another. `details` names the offending fields.
- 429RateLimited
rate_limited | quota_exceededEither 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.
- 500InternalError
internal_errorSomething failed on our side. Internals are never leaked; quote the request id.
- 503Unavailable
upstream_timeout | upstream_error | not_configuredThe 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 X. Rate limits come from the tier on your key.
| Tier | Requests a minute | Requests a day | Live reads a minute |
|---|---|---|---|
| free | 30 | 1,000 | 5 |
| standard | 120 | 25,000 | 20 |
| pro | 600 | 250,000 | 60 |
| unlimited | 6,000 | 10,000,000 | 600 |
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-x-dossier