Analytics on your reader

Embed
Preview of the card
<a href="https://forumulate.com/guides/analytics"><img src="https://forumulate.com/guides/_api/public/orgs/046a9bc0-d530-43ae-9bae-af7945008cda/spaces/36aa4df0-1e95-4cef-8ae9-795169441345/pages/716077e5-f1f5-4f8b-b0d0-4d2aad914d56/social-card.svg?v=3e7ae33eec8b" alt="Analytics on your reader" width="600" height="315"></a>

Measure your published spaces with the analytics tool you already use. Ten vendors are built in. Each one loads only after a reader agrees to analytics, and only on spaces where you set it up.

Vendor

What you enter

Hosted elsewhere?

Google Analytics

ga4: measurement ID, G-XXXXXXX

—

Tag Manager

gtm: container ID, GTM-XXXXXX

—

Segment

segment: source write key

—

Plausible

plausible: the site domain you registered there

plausibleHost (default https://plausible.io)

Fathom

fathom: site ID, ABCDEFGH

—

Umami

umami: website ID (a UUID)

umamiHost (default https://cloud.umami.is)

Matomo

matomo: numeric site ID

matomoHost: required, Matomo has no default

Simple Analytics

simpleAnalytics: the hostname you registered there

—

PostHog

posthog: project API key, phc_…

posthogHost (default https://us.i.posthog.com; EU is https://eu.i.posthog.com)

Intercom

intercom: app ID (support widget, not analytics)

—

A host is an https:// address. It can include a path, for example https://example.com/matomo, but no query string. Leave it empty to use the vendor's cloud.

Set it once for the whole organization

Settings → Analytics in the console holds your organization's defaults. Every space uses them. Over the API:

GET PUT /orgs/{orgId}/analytics-tags
{ "plausible": "docs.example.com", "posthog": "phc_…", "posthogHost": "https://eu.i.posthog.com" }

Override it per space

The space's Settings fold has the same fields, plus a Use the organization's analytics switch:

GET PUT /orgs/{orgId}/spaces/{spaceId}/analytics-tags
{ "ga4": "G-ABC123XYZ9", "inheritOrg": true }

The space and the organization combine one vendor at a time:

  1. A vendor the space sets uses the space's value.

  2. A vendor the space leaves empty uses the organization's value.

  3. A vendor set in neither place does not load.

So you can run Plausible on every space and add GA4 to one of them. Set "inheritOrg": false to ignore the organization's defaults on a space. That space then loads only the vendors it sets itself. customHead is always per space, because arbitrary HTML is never inherited.

Both routes merge. A key you leave out keeps its stored value, and "" clears it.

What is checked

Every ID and host must match the vendor's format. These values are written into scripts on your reader, so anything else is refused with a 422 that names the expected format. A malformed value stored before a check existed is ignored, not rendered.

A Matomo site ID needs a Matomo host. On a space that ignores the organization's defaults, sending one without the other is a 422. A space that uses the defaults can take the host from the organization.

  • All vendors except Intercom are in the analytics consent category. Nothing loads until a reader agrees, on any host. This applies to the privacy-focused vendors too, for consistency with your consent banner.

  • Your reader's security policy allows a vendor's addresses only when that vendor is set up for the space, either directly or through the organization. A space with no analytics does not allow any analytics addresses. For PostHog this covers the loader host (-assets) and the ingest host. For Simple Analytics it covers the script host and the queue host.

  • Page changes inside the reader count as page views. Plausible, Fathom, Umami, Simple Analytics and PostHog track these themselves. For Matomo, Forumulate records each navigation for you.

See Theming: the rest of it for customHead and the consent category of each tag.

Reports on your community

The tags above measure your readers from outside. Forumulate also reports on what happens inside your organization: who joined, who stayed, what was written and what people searched for and did not find. Both reports are read-only, scoped to one organization, and need the org.view_audit capability, the same one as the audit log, because they are operator numbers rather than member-facing ones.

GET /orgs/{orgId}/growth/reports?days=30&weeks=12&months=6

days (default 30, up to 3650), weeks (default 12, up to 520) and months (default 6, up to 120) set how far back each series runs. The response has:

  • signups: new members per week.

  • activeUsers: distinct active users, as daily, weekly and monthly series (DAU, WAU, MAU).

  • retention: weekly join cohorts and how much of each stayed active.

  • content, flags, likes, solved: topics and posts, moderation flags, reactions and topics with an accepted answer, per week.

  • trustLevels: how many users sit at each trust level, 0 to 4.

The wider batch is the one to reach for when you are deciding what to write:

GET /orgs/{orgId}/growth/reports/extra?days=30&weeks=12

It returns per-day series for signups, topics, posts, active users, flags, likes, bookmarks and searches that found nothing; pageViews; timeToFirstResponse (average hours and a weekly series); topicsWithNoReplies; moderatorActivity; and the topSearchTerms, noResultSearchTerms and referrers lists. pageViews and referrers carry an available flag, so a missing source reads as "not recorded" rather than as zero.

Your own API activity

If you build against the API with a personal access token, Forumulate keeps a log of those calls so you can see what your integration is doing:

GET /orgs/{orgId}/api-activity?limit=100

Any member with org.read can call it, and it only ever returns the caller's own calls. limit defaults to 100 and is capped at 500, newest first. Each call has method, route, status, durationMs and at. The summary beside them gives total, errors, errorRate and avgMs, which is the quick way to spot an integration that has started failing. To walk further back than one page, send the API version header and follow the page object, or pass ?page=2; a call that names no version keeps the plain { calls, summary } shape.

In the API

Every operation below is also reachable from the other surfaces without a dedicated command. forumulate api <Operation> calls it from the CLI, the JavaScript SDK has a typed method for each one (the last column names it; the Python and Go clients are generated from the same spec), and an agent reaches the organization-scoped ones through the MCP call_api tool. Where a CLI command or MCP tool exists too, the last column names it. Operation names link to the API reference.

Api Activity

Operation

Request

What it does

Shortcuts

ApiActivity_mine

GET /orgs/{orgId}/api-activity

Get the API activity in an organization

client.analytics.apiActivity.mine()

Growth Reports

Operation

Request

What it does

Shortcuts

GrowthReports_reports_

GET /orgs/{orgId}/growth/reports

List reports in an organization (growth)

client.analytics.growthReports.reports()

GrowthReports_extraReports

GET /orgs/{orgId}/growth/reports/extra

Get the extra in an organization

client.analytics.growthReports.extraReports()

Org Analytics

Operation

Request

What it does

Shortcuts

OrgAnalytics_get

GET /orgs/{orgId}/analytics-tags

List analytics tags in an organization

client.analytics.tags.get()

OrgAnalytics_set

PUT /orgs/{orgId}/analytics-tags

Replace analytics tags in an organization

client.analytics.tags.set()

OrgAnalytics_events

GET /orgs/{orgId}/analytics/events

List events in an organization (analytics)

client.analytics.tags.events()<br>client.analytics.orgEvents()

OrgAnalytics_referrers

GET /orgs/{orgId}/analytics/referrers

List referrers in an organization

client.analytics.tags.referrers()

Space Settings analytics tags

Operation

Request

What it does

Shortcuts

SpaceSettings_getAnalytics

GET /orgs/{orgId}/spaces/{spaceId}/analytics-tags

List analytics tags in a space

client.spaces.settings.getAnalytics()

SpaceSettings_setAnalytics

PUT /orgs/{orgId}/spaces/{spaceId}/analytics-tags

Replace analytics tags in a space

client.spaces.settings.setAnalytics()

The Forumulate teamUpdated