Measurement
fidra proves whether it adds revenue with a treatment/control holdout. To close the loop we need exactly one thing from you: a daily NGR aggregate per pseudonymous user.
What we need — one row per userRef per day
{
"schemaVersion": 1,
"userRef": "u-9f3a", // YOUR pseudonymous id — the join key to the assignment
"day": "2026-07-01", // UTC calendar day
"ngrTotal": 42.5, // all-surface NGR for this user this day (may be negative)
"currency": "USDT", // REQUIRED (v2): your single reporting currency
"bySurface": { "feed": 20, "slots": 22.5 }, // the cannibalization view; absent surface = 0
"bonusCost": 5, // optional, ≥ 0
"providerCost": 3, // optional, ≥ 0
"assignment": "treatment", // the user's holdout arm — required
"experimentId": "exp-2026q3", // optional
"strata": "vip" // optional — stratified randomization label
}
- Why
assignmentrides the row: operator-embed events relay to your app, not to fidra — this ingest is the only operator→fidra channel, and you already know the arm (you randomized). - Report a row for every assigned user, even
ngrTotal: 0— including users who never played. That is what makes the ITT denominator complete. tenantis not sent — the server stamps it from your API key; you cannot report as another operator.- Surfaces are a frozen enum:
feed,other_casino,slots,live,sportsbook. Adding one is a version bump. currency(v2, required): an uppercase ticker — fiat ISO code or crypto/stablecoin symbol (USD,GEL,USDT,BTC). Every money field on the row is denominated in it. A batch mixing currencies is rejected with400.- NGR may be negative (a user who net-won on the day). Costs are ≥ 0.
What never crosses
No player name, email, IP, device id, wallet balance, bet, or raw transaction. The ingest runs an allowlist validator — any field outside the schema is rejected with 400, so a future integration cannot accidentally leak PII.
Endpoint
POST https://staging.fidra.tech/api/outcomes
header: x-fidra-outcomes-key: <your per-tenant key>
body: one row, or an array of rows (a day's batch, ≤ 500)
→ 200 { ok: true, accepted: N } · 400 invalid (with reason) · 401 bad key · 429 rate limited
Idempotent per (userRef, day): re-reporting a day overwrites it — a corrected backfill is safe. Swap the host for production at go-live.
Currency normalization — the rule of one
One tenant reports in one currency, always. Multi-currency wagering (multi-crypto or dual-rail wallets) is normalized by you before reporting — you already do this for your own P&L. The integration contract records: the reporting currency (most crypto operators: USDT/USD), the FX source, and the rate-fixing moment — at bet time, not end-of-day, so market moves can't masquerade as NGR movement. fidra never converts; if rows arrive in more than one currency the dashboard refuses the readout instead of summing mixed units.
How the number is computed — intention-to-treat
The readout joins your rows to the holdout assignments by userRef, sums each user's window NGR, and compares arms counting every assigned user — non-openers enter treatment at their real, often-lower NGR. That is what keeps the estimate honest. The estimator, its 95% CI, the surface decomposition and the underpowered guard are unit-tested in @fidra/measurement; method detail ships in docs/METHODOLOGY.md.
Versioning
schemaVersion on every row is the outcome contract version (independent of the event schema version). Current: 2 — adds the required currency field. v1 rows are still accepted against the v1 allowlist (which predates currency), so in-flight reports never silently fail; new integrations report v2.
docs/integration/MEASUREMENT.md · docs/integration/EVENTS.md
fidra is a B2B software supplier — no gambling services operated, no player funds held.
g.jgerenaia@tomsons.co