Add privacy-safe product analytics
Establish an idempotent analytics pipeline and internal decision dashboard while keeping event metadata allowlisted and account deletion enforceable.
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
# Product analytics
|
||||
|
||||
OSGKeyboard analytics records product behavior metadata only. It is separate
|
||||
from billing, provider execution and immutable credit ledgers.
|
||||
|
||||
## Privacy boundary
|
||||
|
||||
Never send or persist:
|
||||
|
||||
- audio or audio-derived content;
|
||||
- keyboard input, prompts, context, transcripts or model output;
|
||||
- Apple subjects, email addresses, names, tokens, API keys or provider
|
||||
credentials;
|
||||
- arbitrary property names or free-form text.
|
||||
|
||||
The service stores a SHA-256 digest of the random installation identifier, not
|
||||
the identifier supplied by the client. Event DTO string representations are
|
||||
redacted. Product analytics rows linked to an account are deleted with that
|
||||
account. Anonymous installations that never link to an account are retained for
|
||||
90 days and are eligible for scheduled deletion in a later operational job.
|
||||
|
||||
## Ingestion API
|
||||
|
||||
`POST /v1/analytics/events` accepts one batch of 1 to 50 events.
|
||||
|
||||
- Authentication is optional so first-open and pre-login events can be
|
||||
measured. Invalid bearer credentials are rejected.
|
||||
- `installationId` must be a client-generated UUID stored in the containing app
|
||||
and shared with the keyboard extension through the App Group.
|
||||
- When a valid account session is present, the installation is linked to that
|
||||
account. An installation cannot later be linked to a different account.
|
||||
- Every `clientEventId` is a client-generated UUID. The pair
|
||||
`(installation, clientEventId)` is unique.
|
||||
- Replaying an identical event is accepted and reported as replayed.
|
||||
- Reusing an event ID with different values returns `409 conflict`.
|
||||
- The whole batch is validated before persistence and committed atomically.
|
||||
- `occurredAt` must be no more than 35 days old and no more than five minutes in
|
||||
the future. UTC ISO-8601 timestamps are required.
|
||||
|
||||
The successful response reports `accepted` and `replayed` event counts. It does
|
||||
not return account or installation identifiers.
|
||||
|
||||
## Event catalog
|
||||
|
||||
### Lifecycle events
|
||||
|
||||
`FIRST_OPEN`
|
||||
|
||||
- Required: `surface=APP`, `acquisitionChannel`.
|
||||
- Optional: `appVersion`, `osVersion`.
|
||||
- Must be emitted once per installation. Server idempotency protects retries.
|
||||
|
||||
`SESSION_STARTED`
|
||||
|
||||
- Required: `surface` (`APP` or `KEYBOARD`).
|
||||
- Optional: `appVersion`, `osVersion`.
|
||||
|
||||
`KEYBOARD_ACTIVATED`
|
||||
|
||||
- Required: `surface=KEYBOARD`.
|
||||
- Emit when the custom keyboard becomes active, not for every keystroke.
|
||||
|
||||
### AI events
|
||||
|
||||
`AI_FEATURE_STARTED`
|
||||
|
||||
- Required: `feature`, `executionMode`, `surface`.
|
||||
- Optional: `durationBucket` is not allowed.
|
||||
|
||||
`AI_FEATURE_SUCCEEDED`
|
||||
|
||||
- Required: `feature`, `executionMode`, `surface`, `durationBucket`.
|
||||
- This is the client-side core value event.
|
||||
- Managed totals remain server-authoritative; the event provides feature and
|
||||
surface detail.
|
||||
|
||||
`AI_FEATURE_FAILED`
|
||||
|
||||
- Required: `feature`, `executionMode`, `surface`, `failureCategory`.
|
||||
- Optional: `durationBucket`.
|
||||
- Never include provider response bodies or user content.
|
||||
|
||||
### Purchase events
|
||||
|
||||
`PURCHASE_VIEWED`
|
||||
|
||||
- Required: `surface=APP`.
|
||||
|
||||
`PURCHASE_STARTED`
|
||||
|
||||
- Required: `surface=APP`.
|
||||
|
||||
`PURCHASE_CANCELLED`
|
||||
|
||||
- Required: `surface=APP`, `failureCategory=CANCELLED`.
|
||||
|
||||
Successful purchases are derived from verified StoreKit transactions and must
|
||||
not be duplicated as client success events.
|
||||
|
||||
### Referral events
|
||||
|
||||
`REFERRAL_SHARED`
|
||||
|
||||
- Required: `surface=APP`.
|
||||
|
||||
`INVITE_OPENED`
|
||||
|
||||
- Required: `surface=INVITE_WEB`, `acquisitionChannel=REFERRAL`.
|
||||
- The app may emit this event after handling an invitation Universal Link.
|
||||
- The first-party web route separately increments a UTC daily aggregate after
|
||||
validating the referral code. It stores no IP address, user agent, cookie,
|
||||
installation ID, referral code, or other request metadata.
|
||||
- No third-party analytics script is permitted.
|
||||
|
||||
Referral binding, value qualification and reward are derived from server data.
|
||||
|
||||
## Allowed dimensions
|
||||
|
||||
`surface`
|
||||
|
||||
- `APP`
|
||||
- `KEYBOARD`
|
||||
- `INVITE_WEB`
|
||||
|
||||
`acquisitionChannel`
|
||||
|
||||
- `APP_STORE_ORGANIC`
|
||||
- `REFERRAL`
|
||||
- `SOCIAL_CONTENT`
|
||||
- `UNKNOWN`
|
||||
|
||||
`feature`
|
||||
|
||||
- `TRANSCRIPTION`
|
||||
- `POLISH`
|
||||
- `AI_ASSISTANT`
|
||||
- `AGENT`
|
||||
- `HOTWORD`
|
||||
- `OTHER`
|
||||
|
||||
`executionMode`
|
||||
|
||||
- `MANAGED`
|
||||
- `LOCAL`
|
||||
- `BYOK`
|
||||
|
||||
`failureCategory`
|
||||
|
||||
- `NETWORK`
|
||||
- `PROVIDER`
|
||||
- `TIMEOUT`
|
||||
- `CANCELLED`
|
||||
- `INSUFFICIENT_CREDITS`
|
||||
- `VALIDATION`
|
||||
- `UNKNOWN`
|
||||
|
||||
`durationBucket`
|
||||
|
||||
- `LT_1S`
|
||||
- `S1_TO_3`
|
||||
- `S3_TO_10`
|
||||
- `S10_TO_30`
|
||||
- `GTE_30S`
|
||||
|
||||
`appVersion` and `osVersion` are optional ASCII release identifiers of at most
|
||||
32 characters. They may not contain spaces, user-generated values or device
|
||||
names.
|
||||
|
||||
## Client delivery guidance
|
||||
|
||||
- Persist pending events in an append-only local queue.
|
||||
- Retry with exponential backoff after network failures and HTTP 5xx.
|
||||
- Drop events rejected with HTTP 400/422 after recording a local diagnostic
|
||||
counter; do not retry malformed events indefinitely.
|
||||
- Reuse the same `clientEventId` for every retry.
|
||||
- Send at most 50 events per batch and remove events only after a successful
|
||||
response.
|
||||
- Do not block the AI interaction or purchase flow on analytics delivery.
|
||||
|
||||
## Reporting
|
||||
|
||||
The admin analytics endpoints combine:
|
||||
|
||||
- accepted product events for acquisition, local/BYOK usage and client funnel
|
||||
detail;
|
||||
- settled credit usage for managed AI counts and credit consumption;
|
||||
- accounts, StoreKit and referrals for registration, monetization and referral
|
||||
outcomes.
|
||||
|
||||
Metric formulas are defined in
|
||||
[`ANALYTICS_METRICS_DICTIONARY.md`](ANALYTICS_METRICS_DICTIONARY.md).
|
||||
Reference in New Issue
Block a user