The HypercadeAnalytics SDK is the recommended path for Unity and Unreal games. If you’re on something the SDK doesn’t cover — Godot, a custom engine, a web companion app, a dedicated server, a backfill script — you can post directly to the ingest endpoint over plain HTTPS. This page is the wire contract.
You can, but here’s what you lose by not using the SDK
The SDK does more than format JSON. If you call the HTTP endpoint yourself, you are now responsible for everything in this list — and most of it is non-trivial to get right:
- Disk-journal transport. The SDK appends every event to a per-game NDJSON journal first, then uploads in batches. If the player crashes, loses network, force-quits, or the build hangs mid-flush, events survive on disk and replay on next launch. Direct HTTP callers lose any event that fails to POST.
- Batching. The SDK groups pending events by table and posts each table in one call (up to 500 rows / 4 MB). Per-event POSTs are fine for low-volume server-side use, but very wasteful for game-client scale.
- Auto-flush triggers. The SDK flushes on focus loss, application pause, quit, and at startup if a previous session left an orphan journal. You’ll need an equivalent shutdown / pause hook in your runtime.
- 503 retry with backoff. nyxData returns 503 the first time a table is created or its schema is patched. The SDK retries up to 3 times automatically. If you don’t, you’ll silently drop the first event into every new table and the first event that introduces a new column.
- Machine identity (
h_id). The SDK generates a UUID v4 once per device and persists it to a shared filesystem location so all Hypercade-instrumented apps on the same machine emit the sameh_id. Without it, every install (and every fresh process if you regenerate per-run) looks like a different player. - Environment enrichment. The SDK auto-attaches
env_os,env_gpu,env_ram(GB, pow-2 bucketed),env_vram(GB, pow-2 bucketed), andenv_deck. Missing this means you lose the per-hardware cohort views in BigQuery / Looker. - Session lifecycle events. The SDK emits
session_starton initialize andsession_end(withduration_ms) on focus-loss / pause / quit, into thesessionstable. Without it, thesessionstable will be empty and the standard session-duration dashboards won’t work.
For one-off backfills or low-volume internal callers, none of this matters. For anything player-facing, plan to reimplement the parts you need before going to production.
Endpoint
POST https://data.programmoria.com/v1/log/batch
Content-Type: application/json
A legacy single-row endpoint at POST /v1/log/ exists for back-compat and accepts { "token", "table", "data": {...} } — the batch endpoint replaces it for all new integrations.
Request body
{
"token": "<your-project-token>",
"table": "<bigquery-table-name>",
"rows": [
{ "h_id": "...", "app": "...", "env_os": "...", "marker": "boss_01_down" },
{ "h_id": "...", "app": "...", "env_os": "...", "level": 3, "score": 12500 }
]
}Per-request limits: up to 500 rows, up to 4 MB body. Split across multiple requests if you exceed either.
Field rules
Each row is a flat object — no nested objects, no arrays, no null. Three value types are accepted:
| JSON type | BigQuery type |
|---|---|
boolean | BOOL |
number (int or float) | NUMERIC (rounded to 9 decimal places) |
string | STRING |
The first event sent to a given table fixes its schema. The second event for the same table can introduce new columns (you’ll get a 503, retry, and they’ll be added) but you can’t change the type of an existing column — once level is NUMERIC, it can’t become STRING later. Decide your column types before going to volume.
Managed fields you should send
These are not literally required by nyxData — it’ll happily insert rows without them — but every Hypercade dashboard expects them:
| Field | Type | Notes |
|---|---|---|
h_id | string | UUID v4 identifying the device. Generate once, persist, reuse. |
app | string | Your game/app slug (the same value for all rows you send). |
env_os | string | Windows 11, macOS 14.2, Ubuntu 22.04, iOS 17.2, etc. |
env_gpu | string | GPU model string. |
env_ram | number | System RAM in GB, bucketed to a power of 2 (e.g. 4, 8, 16, 32). |
env_vram | number | Video RAM in GB, bucketed to a power of 2. |
env_deck | boolean | true only on Steam Deck. |
For the marker family of events the convention is to put the marker tag in a marker field on the row.
Managed fields nyxData adds
insertedAt (TIMESTAMP, set to UTC now on the server) is appended to every row. Tables are partitioned by month on this field. You don’t send this; nyxData does.
Response codes
| Status | Meaning | What to do |
|---|---|---|
200 | Row(s) accepted | Done. |
400 | Missing/empty token, table, or rows; or the row contained a nested object/array | Fix the request. |
401 | Token not recognized | Check the token. Tokens are issued by Hypercade and pin you to one BigQuery project. |
503 | Table just got created, or schema was just patched to add new fields | Retry the same request. Up to 3 attempts is the SDK’s default; the second attempt almost always succeeds. |
500 | BigQuery API error | Surface the error; do not retry tightly. |
Identity (h_id) — what to do
The simplest correct implementation, in pseudocode:
path = <appdata>/HypercadeFoundation/hc_identity.dat
if path exists and is non-empty:
h_id = read first 36 chars of path
else:
h_id = generate UUID v4
write h_id to path (use create-new semantics so concurrent first-launches
on the same machine converge on one writer)
Use %LOCALAPPDATA% on Windows, ~/Library/Application Support/Hypercade/ on macOS, ~/.config/hypercade/ on Linux. Putting it under HypercadeFoundation/ (not under your app’s own folder) is what lets all Hypercade-instrumented apps on the same machine share an identity.
503 retry — what to do
attempt = 0
while attempt < 3:
response = POST /v1/log/batch
if response.status == 200: success
if response.status == 503: sleep 0.5s; attempt += 1; continue
else: fail (don't retry)
fail (out of attempts)
The first event sent to a new table will always 503 once (table creation). Likewise the first event that introduces a new field on an existing table.
Examples
curl:
curl -X POST https://data.programmoria.com/v1/log/batch \
-H "Content-Type: application/json" \
-d '{
"token": "<your-project-token>",
"table": "progression",
"rows": [
{
"h_id": "550e8400-e29b-41d4-a716-446655440000",
"app": "my_game",
"env_os": "Windows 11",
"env_gpu": "NVIDIA GeForce RTX 4090",
"env_ram": 32,
"env_vram": 24,
"env_deck": false,
"marker": "boss_01_down"
}
]
}'JavaScript / Node fetch:
async function logBatch(table, rows) {
for (let attempt = 0; attempt < 3; attempt++) {
const res = await fetch("https://data.programmoria.com/v1/log/batch", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token: TOKEN, table, rows }),
});
if (res.status === 200) return;
if (res.status === 503) { await new Promise(r => setTimeout(r, 500)); continue; }
throw new Error(`nyxData ${res.status}: ${await res.text()}`);
}
throw new Error("nyxData: out of retries");
}Getting a token
Tokens are issued by Hypercade — they pin you to a specific BigQuery project, so we have to provision the destination dataset before handing the token out. Email [email protected] with your studio name and a short description of what you’ll be sending and we’ll set you up.