Numbers and encoding
This page describes how Open Play carries numbers, money, time, and identifiers over JSON. The most common mistake is converting 64-bit integers to JavaScript number and losing precision; read this page to avoid that mistake.
Short rules
| Field type | Core REST | Plugin and realtime |
|---|---|---|
64-bit integer (int64) | JSON number ("amount": 250) | Decimal text ("amount": "250") |
| 32-bit integer | JSON number | JSON number |
| Floating point | Nowhere | Nowhere |
| Money | Minor-unit integer (amount, balance) | Minor unit, ...Minor fields, int64 |
| Percentage | Basis point (10000 = 100%) | Basis point |
| Time | RFC 3339 text (created_at) | Unix milliseconds integer (...Ms) |
| Identifier | UUID text | UUID text |
| Enum | snake_case text | snake_case (plugin), PascalCase (realtime frame types) |
64-bit integers
JavaScript’s JSON.parse converts numbers to double; integers above 2^53 − 1 (9,007,199,254,740,991) are silently corrupted. Open Play therefore uses two profiles:
- Core REST sends
int64fields as JSON numbers (version,balance,amount). In practice the values stay in the safe range, but the guarantee is a parser that reads withbigint. The TypeScript SDK reads withlossless-jsonand returnsbigint. - Plugin and realtime (the
plugin-v1-decimal-int64encoding profile) send and expectint64fields as decimal text:{"expectedVersion":"3"},{"sessionEpoch":"7"}. This way every client language carries them losslessly.
Canonical decimal text
In plugins a signed int64 must be written in canonical form; the server rejects any other form (400 invalid_argument):
| Valid | Invalid | Why |
|---|---|---|
"0", "42", "-7" | "+1" | No plus sign |
"9223372036854775807" | "01", "007" | No leading zero |
"-9223372036854775808" | "-0" | No negative zero |
" 5", "5 " | No whitespace | |
"1.5", "1e3" | No fraction or exponent | |
"9223372036854775808" | The int64 bound was exceeded | |
5 (JSON number) | A plugin int64 field wants text |
The counters in realtime frames (sessionEpoch, inputSeq, revision, sequence, expectedVersion, streamEpoch, barrierSequence) are in the same text form and cannot be negative. protocolVersion, schemaVersion, and other small counters are JSON numbers.
In the plugin schema document, the bounds of an int64 field are also written as text ("minimum": "0", "maximum": "9223372036854775807"); this is normal.
In client languages
| Language | Mapping |
|---|---|
| TypeScript SDK | int64 → bigint. Do not convert to a number; use value.toString() for display. Do not read JSON yourself with JSON.parse |
| Unity (C#) | int64 → long; in plugin outputs, raw JSON JsonValue stays as decimal text |
| Raw JSON (another language) | Read plugin fields as string and convert to int64 |
If you parse JSON yourself in TypeScript, use a library such as lossless-json; otherwise large values are corrupted.
Money and scaled values
- Money values are minor-unit integers (for example cents): how many main units
250is depends on your game’s display decision (the currency’s “minor digits” setting in the panel determines the display); the API carries only the integer. Balance and amount fields come asamount,balance, and in plugins as...Minor. - Floating point (
float/double) is used nowhere: not in requests, not in responses, and not in computation. On your side too, do not usefloatfor money; work with integers and convert to decimals only for display. - Scaled values in content groups are an
integer + scalepair:4350and scale2→43.50. In the TypeScript SDK,formatScaled(value: bigint, scale)formats this losslessly (0 to 18digits). Do not do your ownfloatdivision.
Percentages: basis points
Percentage and ratio fields are basis point integers: 10000 = 100%, 2500 = 25%, 1 = 0.01%. Example: the group reward share share_bp must be between 0 and 10000; outside that gets 400.
Time
| Where | Format | Example |
|---|---|---|
Core REST responses (created_at, as_of) | RFC 3339 | 2026-11-03T10:15:30+00:00 |
Plugin and realtime (createdAtMs, sent_at_ms) | Unix epoch milliseconds, integer | 1793700930000 |
Chat query before_ms | Unix epoch milliseconds | 1793700930000 |
t in the webhook signature | Unix epoch seconds | 1793700930 |
Deprecation header | @<unix seconds> (RFC 9745) | @1793491200 |
Plugin deprecation (sinceUnixS) | Unix seconds | 1793577600 |
Interpret server times as UTC. The client clock is not used in server decisions: time and randomness are generated on the server and written to the event.
Identifiers and keys
- UUID: lowercase, hyphenated text (
0192a3b4-0000-7000-8000-000000000001). Most resource IDs are time-ordered UUID v7; you can use them for ordering, but do not infer meaning from the format. keyfields (definition keys, leaderboard, quest): readable identifiers given by the game developer; use them exactly as created, do not change the case or whitespace.- Plugin IDs: lowercase and hyphens (
package-ride). Message types: dot-separated, plugin-prefixed (package-ride.delivery-trip.active.input). - Cursors: opaque text; do not parse their contents (pagination).
- API keys:
crx_live_<prefix>_<secret>; the prefix is only for identification, the secret part is shown only once (security).
Enumeration (enum) values
All enumeration values come from a single definition on the server, and an unknown value is rejected (not silently ignored):
- Core REST fields are
snake_casetext ("type": "set_flag","channel": "in_game","status": "pending"). - Realtime frame
typeandkindvalues arePascalCase(Authenticate,PlayerSelf); the field names arecamelCase. - Chat socket frame
typevalues arecamelCase, and the field names aresnake_case(channel_id). - Webhook event types are
snake_case(webhook events). - Values listed as
enumin a plugin schema document (for example"image/png") are used exactly.
If an unknown enum value arrives in your client (it may have been added in a new version), fall back to a safe default instead of crashing; the SDKs reject an unknown value in some places and skip it in others, so follow version upgrades from the changelog.
Text
- All text is UTF-8 JSON text.
- Fields have length limits (for example
Idempotency-Key200 characters,minLength/maxLengthon plugin string fields); a limit violation gets400. - Plain-text error messages (core REST) can be in English or Turkish and change between versions; do not use them for machine decisions (errors).