Direct HTTP Ingestion (protobuf)
To send telemetry without an official SDK, POST a protobuf-encoded TelemetryBatch directly to the ingest endpoint.
Most integrations should use the official Unity, UE5, or Godot SDK instead.
This page is the low-level path for engines without an SDK or teams with their own delivery pipeline.
Endpoint
Section titled “Endpoint”| Field | Value |
|---|---|
| Method and URL | POST https://ingest.framedash.dev/v1/events |
Content-Type | application/x-protobuf |
X-API-Key | An Ingest key with the events:write scope |
X-SDK-Version | SDK version identifier (e.g. custom-1.0.0) |
Set Content-Length to match the actual body size.
If you gzip the body, add Content-Encoding: gzip.
Keep the request body (after gzip compression, if used) at or below 126,000 bytes.
A larger body is rejected with 413.
A Content-Type other than application/x-protobuf, or a Content-Encoding other than gzip, returns 415.
A missing Content-Length returns 411.
The endpoint returns 202 with { "status": "accepted" } after it accepts the batch for asynchronous processing.
This is not confirmation that every event passed downstream validation or was durably stored; a decodable batch can still contain invalid events that are discarded later.
Ingested events take about 20-30 seconds to appear in builds, queries, and the dashboard.
A short delay does not mean ingestion failed.
When testing a custom sender end to end, give one event a unique marker such as ingest_probe_<unique-id> and replace <unique-id> with a UUID or similar value for each run. After waiting, query that exact marker with a Full key carrying the data:admin scope. A Read-only key’s analytics:read scope can access aggregate analytics APIs, but it cannot run the raw-SQL framedash query used here:
framedash query --api-key-file full.key --project-id <project-id> \ "SELECT count(), max(timestamp) FROM events WHERE event_name = 'ingest_probe_<unique-id>'"Only a count greater than zero, with a timestamp from the current run, confirms durable storage. Use a new marker for every test so an older test event cannot produce a false positive.
proto3 schema
Section titled “proto3 schema”The body is the byte string of an encoded framedash.v1.TelemetryBatch message (proto3).
api_key and sdk_version travel in HTTP headers (X-API-Key, X-SDK-Version), not in the payload.
The full schema definition follows.
syntax = "proto3";
package framedash.v1;
// 3D position vector for spatial analytics (heatmaps, player tracking).message Vector3 { float x = 1; float y = 2; float z = 3;}
// Source of the telemetry event.enum TelemetrySource { TELEMETRY_SOURCE_UNSPECIFIED = 0; TELEMETRY_SOURCE_PLAYER = 1; TELEMETRY_SOURCE_AUTOMATED = 2;}
// Core telemetry event emitted by game SDKs.// Each event maps to a single row in the ClickHouse `framedash.events` table.message GameTelemetryEvent { string event_name = 1; // Unix epoch in microseconds. Stored as DateTime64(6) in ClickHouse. int64 timestamp_us = 2; string session_id = 3; // Explicit player identifier — the only PII the SDK may collect. string player_id = 4; Vector3 position = 5; string map_id = 6; reserved 7; reserved "zone_id"; float fps = 8; float frame_time_ms = 9; int64 memory_used_bytes = 10; float gpu_time_ms = 11; // Free-form string key-value pairs for game-specific context. map<string, string> attributes = 12; // Free-form numeric metrics for game-specific measurements. map<string, double> metrics = 13; // Source of the telemetry event. TelemetrySource source = 14; string build_id = 15; string platform = 16; string engine_version = 17; // Camera orientation in degrees. // camera_yaw: horizontal rotation (0 = North, clockwise, [0, 360)). // camera_pitch: vertical rotation (-90 = straight down, +90 = straight up). // 'optional' enables has-field presence tracking — distinguishes // "not sent by SDK" (absent) from "facing North at level" (0, 0). optional float camera_yaw = 18; optional float camera_pitch = 19; // CPU game thread time in milliseconds (logic, AI, physics). // 0 = not collected (old SDK or unavailable). float game_thread_ms = 20; // CPU render thread time in milliseconds (draw commands, culling). // 0 = not collected (old SDK or unavailable). float render_thread_ms = 21;}
// Batch wrapper sent by SDKs via POST /v1/events.// api_key and sdk_version are transmitted in HTTP headers (X-API-Key, X-SDK-Version),// not in the Protobuf payload.message TelemetryBatch { repeated GameTelemetryEvent events = 1;}Minimal example
Section titled “Minimal example”Compile the .proto with protoc (or buf), build a TelemetryBatch with the generated code, and serialize it to bytes.
Write the serialized bytes to a file and POST it with curl --data-binary.
# 1. Compile the proto (Python shown)protoc --python_out=. telemetry.proto
# 2. In your app code, build a TelemetryBatch and write it to batch.bin
# 3. POST itcurl -X POST https://ingest.framedash.dev/v1/events \ -H "X-API-Key: fd_your_ingest_key_here" \ -H "X-SDK-Version: custom-1.0.0" \ -H "Content-Type: application/x-protobuf" \ --data-binary @batch.binEach event must satisfy the Data Model ingest validation limits.
timestamp_us must fall within the last 30 days and no more than 48 hours in the future, event_name and session_id must be non-empty, string and map fields must stay within their bounds, and numeric metrics must be finite.
The official SDKs clamp these values client-side before flushing.
Next Steps
Section titled “Next Steps”- Events Schema: Columns returned by queries
- API Overview: Authentication and response formats
- Troubleshooting: Confirm events arrive