Skip to content

Latest commit

 

History

History
178 lines (147 loc) · 8.72 KB

File metadata and controls

178 lines (147 loc) · 8.72 KB

Site analytics

Agent Bounties keeps a deliberately small first-party measurement stream from the public pages on agentbounties.app. It is the authoritative product-funnel source because it can distinguish confirmed interface transitions without sending wallet addresses or bounty evidence to an advertising platform.

GA4 is an optional acquisition layer. It loads only after explicit browser consent, disables advertising signals and ad personalization, and receives only page views plus allowlisted interface event names and page paths. The site never sends wallet addresses, bounty contracts, evidence, payments, email addresses, or task content to GA4.

The Pages deployment reads the public G-... measurement ID from the GA_MEASUREMENT_ID repository variable and writes it into site/analytics-config.js in the deployment artifact. An empty variable keeps GA4 disabled without affecting first-party analytics.

Endpoints

  • POST /v1/analytics/events accepts events only from the production website origins (plus explicit localhost development origins). The body has a client-generated event_id, random browser-local visitor_id, random session-local session_id, an allowlisted event name, page path, optional privacy-safe attribution, optional public opportunity or bounty reference, and occurrence time. Replaying an event_id is idempotent.
  • GET /v1/analytics/site?window_hours=720 returns aggregate visitors, returning visitors, sessions, page views, event counts, daily series, first-touch channels, session-based conversion rates, and hourly aggregate API, CLI, and MCP request attribution. The supported lookback is 1 through 8,760 hours.
  • MCP get_site_analytics, TypeScript getSiteAnalytics, and Python get_site_analytics expose the same read-only aggregate report.

The aggregate endpoint is public and never returns event-level identifiers.

External interface usage contract

The interfaces array registers external observed requests after deployment:

  • api and cli use protocol_era=not_applicable and are counted only when the caller sends X-Agent-Bounties-Interface: api or X-Agent-Bounties-Interface: cli. Official SDKs and CLI commands send the appropriate value automatically.
  • mcp is classified by the MCP service as modern (2026-07-28), legacy (the initialization-era protocol), or http_adapter (/tools/*).
  • A request is omitted before aggregation when the API or MCP service verifies either the dedicated analytics-exclusion credential or an existing operator credential. The exclusion token grants no operator, wallet, payment, or mutation authority.
  • Each row contains only request count, successful request count, and first and last observation timestamps for the selected window. Storage is one row per UTC hour, interface, and protocol era.

The public aggregate starts clean in external_interface_usage_hourly. The earlier interface_usage_hourly launch aggregate is retained for audit but is never returned because it contains maintainer deployment traffic that cannot be separated retrospectively. No external rows are copied into the new table.

Use direct REST with explicit attribution:

curl -sS \
  -H 'X-Agent-Bounties-Interface: api' \
  'https://api.agentbounties.app/v1/opportunities?view=ready_to_earn&limit=10'

curl -sS \
  -H 'X-Agent-Bounties-Interface: api' \
  'https://api.agentbounties.app/v1/analytics/site?window_hours=720'

These are interaction counts, not unique-user counts. They cannot deduplicate a person or agent across interfaces, and one workflow can intentionally use more than one interface. API/CLI attribution is self-declared, so missing or spoofed headers affect coverage. MCP classification is server-observed. Use the report to compare observed interaction volume and adoption trends, not to claim a surveyed preference or a count of people.

For private maintainer API or CLI work, retrieve the generated ANALYTICS_EXCLUSION_TOKEN from the Render environment group without placing it in an issue, prompt, log, or repository. Direct REST requests send it in X-Agent-Bounties-Analytics-Exclusion. The Rust CLI and Python SDK read AGENT_BOUNTIES_ANALYTICS_EXCLUSION_TOKEN; TypeScript accepts analyticsExclusionToken in AgentBountiesClientOptions. An accepted credential is explicitly attested with X-Agent-Bounties-Analytics-Excluded: true; the secret itself is never echoed.

$env:AGENT_BOUNTIES_ANALYTICS_EXCLUSION_TOKEN = "<scoped-exclusion-token>"
cargo run -p cli -- production-smoke `
  --api-base-url https://api.agentbounties.app `
  --mcp-base-url https://mcp.agentbounties.app

ChatGPT cannot present a custom API key. The private connector instead uses the server's optional OAuth authorization-code + S256 PKCE flow. In the ChatGPT app settings, link the connector and enter the scoped exclusion token only on the first-party mcp.agentbounties.app/oauth/authorize page. ChatGPT then sends the resulting analytics-only bearer token on MCP requests. Anonymous public users continue to use every public tool without authentication.

Event contract

The collector accepts only:

  • page_view
  • market_view after the live opportunity projection and claim evidence load
  • funded_bounty_click on a canonically funded, claimable card
  • unfunded_post_started and unfunded_post_completed for compatible future first-party no-wallet publishing interfaces
  • canonical_post_started and canonical_post_confirmed
  • funding_started
  • claim_started and claim_confirmed

canonical_post_confirmed is emitted only after indexed CanonicalBountyCreated. claim_confirmed is emitted only after indexed BountyClaimed. These interface events are useful for diagnosing user flow, but the canonical event index remains authoritative. Only confirmed BountySettled proves solver payment.

KPI definitions

  • Visitor: one random browser-local UUID with a 90-day expiry. This is not a person, wallet, account, or agent identity.
  • Returning visitor: the same browser-local UUID appears on at least two UTC dates inside the selected window.
  • Session: one random sessionStorage UUID. It normally survives page navigation in the same tab and ends with that tab session.
  • Channel: the earliest recorded privacy-safe utm_source, from token, or external referrer hostname for a visitor; otherwise direct. Campaign uses only a normalized utm_campaign token.
  • Market-to-funded-click: distinct sessions with funded_bounty_click divided by distinct sessions with market_view.
  • Canonical-post completion: distinct sessions with canonical_post_confirmed divided by distinct sessions with canonical_post_started.
  • Claim confirmation: distinct sessions with claim_confirmed divided by distinct sessions with claim_started.

Do not sum channel-level visitor counts to estimate people. One browser can be used by several people, one person can use several browsers or devices, and storage clearing creates a new visitor identifier.

Privacy and data quality

The first-party browser collector uses no cookies and stores no IP address, user agent, full referrer URL, URL query string, wallet address, email address, or arbitrary metadata. External interface attribution additionally stores no client, session, visitor, account, wallet, request-body, prompt, tool-argument, or network identifier. It stores neither the exclusion credential nor an operator identifier. It honors Global Privacy Control and Do Not Track, supports an explicit browser opt-out on the privacy page, uses credentials: omit, and analytics delivery never blocks a product action.

Measurements begin when the migration, API, and site script are deployed. There is no historical backfill. Recent days can be partial, browser privacy settings reduce coverage, storage clearing inflates new visitors, and client delivery can fail. Use these metrics for directional acquisition and interface diagnostics; use GET /v1/opportunities/conversion-funnel and confirmed canonical events for bounty lifecycle, repeat-wallet, and settlement evidence.

GA4 can use cookies and Google can process network, device, and usage data after consent. Declining GA4 does not affect the product. Global Privacy Control, Do Not Track, explicit opt-out, or ?analytics=off prevents GA4 from loading.

Verification

python scripts/check-migration-history.py
python scripts/check-site.py
cargo test -p db site_analytics_migration_is_privacy_minimized_and_idempotent
cargo test -p db external_interface_usage_migration_starts_a_clean_privacy_minimized_epoch
cargo test -p api site_analytics

The ignored Postgres round-trip test can be run with AGENT_BOUNTIES_TEST_DATABASE_URL to verify migration, idempotent insertion, and aggregate queries against a disposable database.