Skip to content

Commit 244d6bd

Browse files
AzymAzym
authored andcommitted
docs(sdk): engagementRate semantics; account rate is always null
Mirrors the server fixes in bulk-publish 5029cac and 4149e6d. - Account-level engagementRate is documented and typed as always null: no handler computes one (the getAccountAnalytics contract has no such field), so it was the column default 0 on all 125 production rows and looked like a measured 0%. Both SDKs already allowed null, so nothing breaks. - The two engagementRate fields on the engagement response are different numbers — post-level is a mean over the post's measurable channels, platformMetrics[] is one channel's own rate. Documented at both, since rendering the former beside the latter's counters is what produced a TikTok panel reading 0/0/0/0 and 24.55%. - profileViews is Facebook-only, websiteClicks is Google-Business-only; a 0 elsewhere means not reported. node SDK 1.7.2, python SDK 0.8.2 (pyproject + __version__ in step).
1 parent 593b451 commit 244d6bd

7 files changed

Lines changed: 28 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Changelog
22

3+
## 2026-07-29 — analytics accuracy: engagementRate semantics (node SDK 1.7.2, python SDK 0.8.2)
4+
5+
### Fixed
6+
7+
- **`avgEngagementRate` on `GET /api/analytics/engagement` was averaged over only the posts with non-zero engagement.** The server stores `engagement_rate` as engagements ÷ impressions and writes 0 when impressions is 0, so filtering on `rate > 0` dropped every post that genuinely got no engagement — 66% of measurable rows in production — and reported a mean over only the posts that performed. It is now averaged over every row with `impressions > 0`, which is exactly when the stored rate is meaningful. Expect this number to go DOWN, and to be correct.
8+
- **`engagementRate` on `GET /api/analytics/account` is now `null`, never `0`.** No platform handler computes an account-level engagement rate — the internal `getAccountAnalytics` contract has no such field — so the column was its default 0 on every row and was indistinguishable from a measured 0%. Both SDKs already typed it as nullable, so this is not a breaking change. For a real rate use `platformMetrics[].engagementRate` from the engagement endpoint.
9+
10+
### Documented
11+
12+
- **`platformMetrics[].engagementRate` vs the post-level `engagementRate`** on the engagement response are different numbers: the post-level value is the mean over the post's channels that were measurable, the per-channel value is that one channel's own rate. Rendering the post-level average beside a single channel's counters shows one network's percentage next to another network's zeros.
13+
- **`profileViews` is Facebook-only** (`page_views_total`) and **`websiteClicks` is Google-Business-only**. A 0 on any other platform means "not reported", not "measured zero".
14+
315
## 2026-07-29 — RFC 9207 issuer validation on the MCP OAuth server (MCP 1.11.0)
416

517
### Added

node/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bulkpublish",
3-
"version": "1.7.1",
3+
"version": "1.7.2",
44
"description": "Social media automation API for AI agents, LLMs, and developers. Publish to 11 platforms programmatically.",
55
"author": "BulkPublish",
66
"license": "MIT",

node/src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,8 +819,16 @@ export interface AccountMetricsDataPoint {
819819
following: number | null;
820820
impressions: number | null;
821821
reach: number | null;
822+
/** Facebook only (`page_views_total`); null/0 elsewhere — not measured. */
822823
profileViews: number | null;
824+
/** Google Business only; null/0 elsewhere — not measured. */
823825
websiteClicks: number | null;
826+
/**
827+
* ALWAYS null. No platform handler computes an account-level engagement rate,
828+
* so the server returns null rather than a 0 that would look like a measured
829+
* 0%. For a real rate use `platformMetrics[].engagementRate` from
830+
* `analytics.engagement()`.
831+
*/
824832
engagementRate: number | null;
825833
}
826834

openapi.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3436,7 +3436,7 @@
34363436
],
34373437
"responses": {
34383438
"200": {
3439-
"description": "Account metrics with channel list",
3439+
"description": "Account metrics with channel list.\n\n`engagementRate` on each row is ALWAYS `null`: no platform handler computes an account-level engagement rate (the `getAccountAnalytics` contract has no such field), so the stored column is its default 0 for every row. It is returned as null rather than 0 so it cannot be mistaken for a measured 0%. For a real rate, use the per-post `engagementRate` inside `platformMetrics[]` on `GET /api/analytics/engagement`.\n\n`websiteClicks` is populated by Google Business only; `profileViews` by Facebook only (`page_views_total`). Both are 0 for every other platform because no other handler reports them — not because the value was measured as zero.",
34403440
"content": {
34413441
"application/json": {
34423442
"schema": {

python/bulkpublish/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
ValidationError,
1010
)
1111

12-
__version__ = "0.8.1"
12+
__version__ = "0.8.2"
1313
__all__ = [
1414
"BulkPublish",
1515
"AsyncBulkPublish",

python/bulkpublish/types.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,10 @@ class AccountMetrics(TypedDict, total=False):
313313
followers: int
314314
following: int
315315
totalPosts: int
316-
engagementRate: float
316+
#: Always ``None`` — no platform handler computes an account-level rate, so
317+
#: the server returns null rather than a 0 that would look measured. Use the
318+
#: per-post ``engagementRate`` from ``analytics.engagement()`` instead.
319+
engagementRate: Optional[float]
317320

318321

319322
# ---------------------------------------------------------------------------

python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "bulkpublish"
7-
version = "0.8.1"
7+
version = "0.8.2"
88
description = "Social media automation API for AI agents, LLMs, and developers. Publish to 11 platforms programmatically."
99
readme = "README.md"
1010
license = "MIT"

0 commit comments

Comments
 (0)