@volter/twin-mixpanel
v0.1.37
Published
Local Mixpanel twin — the vendor's 104-operation surface compiled from its own 14 first-party OpenAPI documents, served with honest loud gaps; the ingestion plane (track, $identify, $create_alias, profile $set) modeled over kernel state and verified again
Readme
@volter/twin-mixpanel
Legacy connector helpers: this package still has callable helpers using the retired v1
syncPullAPI. Those paths require migration before use on the current kernel; older helper descriptions below do not establish current compatibility. Check the generated index for protocol standing and use the shared model for current state semantics.
Local Mixpanel twin — the vendor's 104-operation surface, compiled from Mixpanel's own
first-party OpenAPI set (all 14 documents: annotations, data-pipelines, experiments, export,
feature-flags, feature-flags-management, gdpr, identity, ingestion, lexicon-schemas, platform,
query, service-accounts, warehouse-connectors) by the assembly line's S2 station
(scripts/spec-compile.ts), plus 3 SDK-only operations the shipped mixpanel-browser@2
drives that no published spec file declares — recorded per the SDK-beats-spec precedent.
This pack is an assembly-line article (var/line/mixpanel, scripts/line.ts): the surface
table, router data, and all-todo manifest inventory are generated; semantics are hand-authored
per demanded operation and flipped done only with a real verify().
Coverage
Honest and low by construction — 13 done of 123. The ingestion plane is modeled and
verified against the real, unmodified mixpanel Node SDK; on the ingestion host every other
spec-declared operation answers a loud vendor-shaped 404 naming the gap — never a fake
success. See mixpanel-capabilities.ts for the full Done/todo ledger, spec-sources.json for
grounding provenance, and var/line/mixpanel/RULINGS.json for the 126 divergence rulings behind
it.
Two denominator limits are filed as todos rather than hidden. scripts/spec-compile.ts drops the
base path in each document's servers url, so the 83 ops served at mixpanel.com/api/app,
mixpanel.com/api/query and data.mixpanel.com/api/2.0 are registered at their bare paths
keys — a real client's path reaches a generic 404 rather than the loud gap
(mixpanel.api.loud_gap_on_based_planes). The same script de-dupes ops by method+path across
spec files, which dropped POST /import "Import Events" in favour of identity.json's same-path
"Merge Identities" (mixpanel.api.import_events).
- Done:
POST /track(track an event, all three real transports with UTF-8 payload bytes, mixed-batch per-record dispatch, spec-required-field validation,?verbose=1mode),$identify(create identity) and$create_aliaswith their projectable links,POST /engage$set(create-or-merge a profile, including the merge-over-prior-state and re-set-a-previously-held-value cases), fail-loudly conformance including an unmodeled fragment sibling and the fragment-less/importcollision, and the connector's push half with the in-body project token. - Planned (tier-ranked todos): the rest of the
/engageand/groupsoperator families ($set_once,$add,$union,$append,$remove,$unset,$delete, batch),/importand identity merge, the Query API (insights/funnels/retention/segmentation/JQL/cohorts), raw export, lookup tables, feature flags, annotations, lexicon schemas, GDPR, service accounts, warehouse connectors and data pipelines; the UI mirror (see below); connector pull. - Out of scope: nothing yet — every gap above is build-or-carve-out.
The UI mirror is OWED and unbuilt — do not cite this pack as no-mirror precedent
ui-scope.json rules needsUi: true. Mixpanel is two-halved: instrumentation is written in
code, but the vendor's core job — funnels, retention, insights boards, cohorts — is done in a
browser, and 18 of its 104 spec operations are Query API reads that exist to feed those screens.
That is the same answer as posthog, the identical product category, which mirrors. v1 models
the ingestion half only; mixpanel.ui.event_stream, mixpanel.ui.insights and
mixpanel.ui.profiles are live todos with todo journeys in ui-scope.json, not omissions.
Wire notes (why this pack looks unusual)
- Fragment paths are not wire paths. Mixpanel declares 18 ops at anchors like
/track#create-identityand/engage#profile-set. A URL fragment is never transmitted: the operation is discriminated by body content onPOST /track,/engageand/groups. A fourth path,/import, carries two operations with no fragment at all (import-eventsandidentity-merge) — unmodeled, and it answers the loud gap. The pack-owned request loop does the body dispatch; the generatedmatchOphandles everything else. - A
/trackbatch is a list of independent events.mixpanel-browser@2batches by default (batch_requests: true, up to 50) and itsidentify()istrack('$identify', …)on the same batcher, so an ordinary flush mixes ops. The discriminator therefore runs per record. - The base64 carries UTF-8 bytes. Both SDKs utf8-encode before base64ing, so the payload is
decoded through
TextDecoder, notatobalone. - Three transports, not one. The spec says POST +
application/json.[email protected]sends GET with the payload base64'd into?data=;mixpanel-browser@2sends POST withapplication/x-www-form-urlencodeddata=…to trailing-slash routes, in base64 or raw JSON depending on the host. All are accepted. - The answer is a bare integer.
1or0astext/plain, or{status, error}under?verbose=1. This is load-bearing: the Node SDK byte-compares the body to'1'. - Auth is in the body. The project token rides in
properties.token(events) or$token(profiles); there is noAuthorizationheader on the ingestion plane.
Hosts
api.mixpanel.com, api-eu.mixpanel.com, api-in.mixpanel.com (ingestion — both the spec
servers enum and [email protected]'s DEFAULT_CONFIG.host), api-js.mixpanel.com (the
mixpanel-browser@2 default), mixpanel.com (the query/app plane) and
data{,-eu,-in}.mixpanel.com (the export/data-pipeline plane, servers of export.json and
data-pipelines.json) are claimed by the injector. cdn.mxpnl.com is a real Mixpanel property
but is deliberately not claimed — it serves the SDK's JS bundle, not API operations; nor is
platform-{us,eu,in}.mixpanel.com, whose spec document declares zero paths.
The store doors
Mixpanel's ingestion plane — the whole of what this pack models — is write-only: /track,
/engage and /groups answer a bare 1 and nothing on that plane reads an event back. What was
ingested is therefore read through the twin's own named projections, GET /twin/store/{events,
profiles,identities,aliases} — the read-only doors the programming model gives a twin whose
vendor has no listing endpoint (the mailgun precedent). They are deliberately out of the
capability manifest: counting scaffolding the vendor does not have would pad the denominator.
GET /twin names them under stores.
Rate budget
Mixpanel publishes no single scalar request limit (ingestion is byte-metered per minute; the
Query/Export APIs are limited per hour), so mixpanel-budget.ts claims no extra permissiveness
over the kernel fallback: 60 weighted units / 60s at default weight 2. The vendor's real cost
structure appears only in the relative weights — ingestion 2, /import 3, Query/Export 6.
Run
world-mixpanel serve --port 47392 # or: bun packages/twin/mixpanel/src/cli.ts serve
world-mixpanel conformance
world-mixpanel ops # print all 104 routed operationsPoint the unmodified SDK at it with its own documented config — no patching:
const mixpanel = require('mixpanel').init('YOUR_TOKEN', { host: '127.0.0.1:47392', protocol: 'http' });
mixpanel.track('Order Completed', { distinct_id: 'u_1', amount: 42 });