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 same h_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), and env_deck. Missing this means you lose the per-hardware cohort views in BigQuery / Looker.
  • Session lifecycle events. The SDK emits session_start on initialize and session_end (with duration_ms) on focus-loss / pause / quit, into the sessions table. Without it, the sessions table 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 typeBigQuery type
booleanBOOL
number (int or float)NUMERIC (rounded to 9 decimal places)
stringSTRING

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:

FieldTypeNotes
h_idstringUUID v4 identifying the device. Generate once, persist, reuse.
appstringYour game/app slug (the same value for all rows you send).
env_osstringWindows 11, macOS 14.2, Ubuntu 22.04, iOS 17.2, etc.
env_gpustringGPU model string.
env_ramnumberSystem RAM in GB, bucketed to a power of 2 (e.g. 4, 8, 16, 32).
env_vramnumberVideo RAM in GB, bucketed to a power of 2.
env_deckbooleantrue 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

StatusMeaningWhat to do
200Row(s) acceptedDone.
400Missing/empty token, table, or rows; or the row contained a nested object/arrayFix the request.
401Token not recognizedCheck the token. Tokens are issued by Hypercade and pin you to one BigQuery project.
503Table just got created, or schema was just patched to add new fieldsRetry the same request. Up to 3 attempts is the SDK’s default; the second attempt almost always succeeds.
500BigQuery API errorSurface 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.

See also