Bind variant assignments to sessions, then every conversion, funnel step, replay, and log in that session joins the right variant at query time. Sumidata is the analytics side of A/B — your feature-flag service still decides who sees what.
01Mental model
Sumidata is a tracker, not a flag service. You tell it which variant you chose; it joins that to everything else.
An experiment assignment in Sumidata is a tiny row that says: "In session X, experiment Y was running, and the user saw variant Z." The assignment lives in a dedicated table (session_experiments), keyed by (sessionId, experimentId). Reports and the AI Analyst join it to events, conversions, and replays on sessionId — no variant field is needed on downstream events.
EndpointPOST /sdk/session-experiments
Tablesession_experiments
Join keysessionId
Idempotency(sessionId, experimentId) — first wins
Retentiongoverned by your plan's data-retention policy
i
Sumidata does not decide which variant the user sees. Use GrowthBook, Statsig, LaunchDarkly, Optimizely, or your own rollout logic — then report the chosen variant back to Sumidata. This is the same split as a logger vs. a decision engine: one observes, the other decides.
02Sticky by session, not by user
First assignment per (session, experimentId) wins. A later call with a different variant is a no-op.
Sumidata dedups on (sessionId, experimentId) — the first assignment the server sees for a session+experiment pair is the one that sticks. A second call with a different variant for the same experiment in the same session returns { status: "skip" } and writes nothing. This protects analysis from:
Flicker-reassignment when a flag evaluator returns different values on re-mount.
Double-fires when the SDK re-runs on route change.
Race conditions between client-side and server-side assignment.
!
Variant binding is session-scoped, not user-scoped. If the user returns in a new session (after sign-out, or 30 min of inactivity), they may be assigned to a different variant — that's fine for most experiments, but if you need a lifetime-sticky assignment, resolve it from your flag service and pass the same variant into identifyExperiment on every session start.
03From the browser
One push call. The SDK fills in deviceId and sessionId automatically.
Call identifyExperiment as soon as your flag framework has resolved assignments for the current user — ideally before the first product event fires. An experiment registered after the first conversion in a session will still work for anysubsequent events in that session, but the earlier events stay unattributed (the join matches on the stored assignment row; rows don't retroactively inject into past events).
experiments.js
// Called once per session, as soon as your A/B framework resolves assignments
Sumidata.push('identifyExperiment', [[
{ id: 'pricing-v3', variant: 'control' },
{ id: 'onboarding-flow', variant: 'short' },
{ id: 'hero-copy', variant: 'v2' },
]])
i
The call is safe to make repeatedly — the server deduplicates by (sessionId, experimentId), so there is no harm in re-registering on every page navigation. But don't depend on re-registration to change a variant; see section 02.
04From the server
Post the same shape from your backend when assignments are resolved server-side.
Useful when your flag framework runs in SSR, edge workers, or an internal feature-flag service, and you want to register variants without waiting for the client. You need the browser's sessionId and deviceId — get them from the SDK on the client first (see Linking browser session to server), store them on the user row, then call this endpoint.
Register one or more variant assignments for a session. Idempotent. Returns { status: "ok" } when new assignments were inserted, or { status: "skip" } when every pair was already registered.
Request body
Name
Type
Required
Description
projectId
UUID
yes
Your Sumidata project ID
deviceId
UUID
yes
The SDK-issued device identifier
sessionId
UUID
yes
The session to bind the assignments to
experiments
{id, variant}[]
yes
Non-empty array. Each entry needs a non-empty id and variant string
Example request
curl
curl"https://api.sumidata.io/sdk/session-experiments" \
-X POST \
-H"Content-Type: application/json" \
-d'{"projectId":"…","deviceId":"…","sessionId":"…","experiments":[{"id":"pricing-v3","variant":"control"}]}'
{ "status": "ok" } // new assignments inserted
{ "status": "skip" } // every (sessionId, experimentId) was already registered
Errors
400
deviceId required / invalid UUID
Missing or malformed
400
sessionId required / invalid UUID
Missing or malformed
400
experiments must be a non-empty array
Empty or non-array experiments field
400
experiments[i].id / .variant required
Empty strings are rejected per element
05Integration recipes
A one-call recipe for each of the common flag frameworks.
The pattern is always the same: resolve the variant with your flag service, then hand the result to Sumidata. Examples in the browser SDK; swap to the server endpoint if the decision happens on your backend.
Ask the AI Analyst in plain English, or write SQL against session_experiments.
Sumidata intentionally does not expose a read endpoint for assignments — you almost never want the raw rows, you want the analysis built on top. Two ways to get that:
AI Analyst
Plain English. Works out of the box once any assignments are registered.
revenue and conversion rate by variant for pricing-v3, last 14 days
which onboarding-flow variant has the lowest drop-off on step 2?
Experiments dashboard
Under Analytics → Experiments you get a list of every experiment in your window ranked by sales contrast (the spread between your best- and worst-selling variant), a per-variant detail view (devices, sessions, purchases, revenue, and conversion with a 95% confidence interval per variant), and an events-by-variant breakdown showing how each tracked event fires per device across variants. No setup — it populates as soon as assignments are registered.
Replay filtering
In the Sessions and Replays dashboards, the Experiment and Variant selects scope the session list to a single experiment and variant (their options come straight from your data). Each variant row in the experiment detail deep-links here via ?experimentId=&variant=, which pre-fills both selects — great for qualitative review, watching five "treatment" sessions against five "control" back-to-back.
07Limitations
What Sumidata won't do for you — fill these in with your own tooling.
No read endpoint for assignments. There is no GET /sdk/session-experiments. If you need the raw variant for a given session, ask the AI Analyst for the session_experiments rows.
Sample sizes and confidence intervals, not verdicts. The Experiments dashboard shows per-variant conversion with 95% confidence intervals and the sample sizes behind every number. It does not issue significance verdicts — no "winner", no "significant". Whether a difference is real enough to act on stays with you and your decision tooling. Sumidata is the assignment logger, not the decision engine.
No webhooks on new assignments. The registration is synchronous and returns a status, but downstream systems aren't notified.
Session-scoped stickiness. If you need lifetime stickiness, resolve the variant in your flag service on every session and re-register — the first per-session assignment will match what you chose.
Plan-governed retention. Assignments are retained under your plan's data-retention policy, the same as the rest of your analytics data; older data is purged on that schedule.
08AI-agent quick reference
Drop this into an agent prompt when automating experiment setup and analysis.
experiments.yaml
concept: Sumidata records which variant was shown; your flag service decides who sees what.register:
browser: Sumidata.push('identifyExperiment', [[{ id, variant }, …]])server: POST /sdk/session-experiments { projectId, deviceId, sessionId, experiments: [{ id, variant }] }response: { status: "ok" } (inserted) | { status: "skip" } (already registered)stickiness:
dedup_key: (sessionId, experimentId) — first-winschanging_variant: requires a new session (logout / reset / 30 min inactivity)scope: session-bound, not user-boundtiming:
register_before: the first event you want attributed — earlier events stay unattributedretroactive: no — assignment rows don't alter past eventsanalysis:
join: events JOIN session_experiments ON sessionId (+ projectId)no_variant_field_on_events: true — never stamp variant on event payloads; the join handles itdashboard: Analytics → Experiments — list ranked by sales contrast, per-variant detail (conversion + 95% CI), events-by-variantsession_filter: Sessions/Replays accept Experiment + Variant; deep link ?experimentId=&variant=ai_analyst: "revenue by variant for pricing-v3 last 14 days"limits:
- no GET endpoint for raw rows — inspect via the AI Analyst
- CI + sample sizes shown, but no significance verdict — decision stays with you
- no webhooks on new assignments
- retention governed by your plan's data-retention policy