Ludex ingestion — HTTP contract reference
This document is the canonical reference for the Ludex telemetry ingestion HTTP API: routes, headers, JSON models, status codes, and behavior.
Principle: Direct HTTP is the supported integration path. Optional SDKs are thin clients over this same API.
Base URL and versioning
All ingest routes live under the /v1 prefix.
| Environment | Example base |
|---|---|
| Production | https://ingest.ludexstudio.com (or the URL Ludex provides) |
Full URLs below are written as {base}/v1/....
Authentication
Every ingest request must include:
Authorization: Bearer <api_key>
The server validates the Bearer token and derives organization_id, project_id, and environment_id from the credential.
Required alignment: The JSON body must include:
project_id— must exactly match the credential'sproject_id.environment— must exactly match the credential'senvironment_id.
Mismatch yields 403 with project_mismatch or environment_mismatch (see error-catalog.md).
Scopes on the credential:
| Endpoint | Required scope |
|---|---|
POST /v1/ingest/event |
ingest:events |
POST /v1/ingest/batch |
ingest:batch |
Missing scope → 403 insufficient_scope.
Content type
Use JSON bodies:
Content-Type: application/json
Optional headers
| Header | Purpose |
|---|---|
Idempotency-Key |
Opaque string. When present, a 202 success response for the same credential + key is cached and replayed within a server-side TTL (default 300 seconds). The server only inspects the header when sent as Idempotency-Key or idempotency-key (these spellings only). Do not assume other casings are honored. |
X-Correlation-Id |
Correlation id attached to downstream processing and traceability. |
X-Request-Id |
If provided, used in request-scoped logging; otherwise the server generates one. |
CORS: Production ingest is expected to be server-to-server or from controlled clients. Do not rely on browser CORS in production.
Data models (JSON)
SdkInfo (optional on each event)
| Field | Type | Required |
|---|---|---|
name |
string | yes, if sdk is present |
version |
string | yes, if sdk is present |
SdkEvent (single event payload / batch item)
| Field | Type | Required | Notes |
|---|---|---|---|
event_name |
string | yes | Logical event type name. |
timestamp |
string | yes | ISO-8601 timestamp (UTC recommended). Accepts Z suffix or offset. If timezone is omitted, UTC is assumed. |
player_id |
string or null | no | Optional end-user identifier. |
session_id |
string or null | no | |
platform |
string or null | no | |
build_version |
string or null | no | |
game_version |
string or null | no | |
event_id |
string or null | no | Client-supplied stable id (UUID recommended). Used for deduplication when provided. If omitted, server generates a UUID. |
properties |
object | no | Arbitrary JSON object; default {}. Values are JSON-serializable types. |
sdk |
SdkInfo or null |
no | Identifies the sending SDK. |
POST /v1/ingest/event — request body
| Field | Type | Required |
|---|---|---|
project_id |
string | yes |
environment |
string | yes — must match credential environment_id |
event |
SdkEvent |
yes |
POST /v1/ingest/event — success 202
| Field | Type | Notes |
|---|---|---|
status |
string | Always "accepted" |
request_id |
string | UUID for this ingest attempt |
event_id |
string | Echoes client event_id or server-generated UUID |
duplicate |
boolean | Default false. true when the same event_id was already ingested (deduplicated; still 202) |
POST /v1/ingest/batch — request body
| Field | Type | Required |
|---|---|---|
project_id |
string | yes |
environment |
string | yes |
events |
array of SdkEvent |
yes — max 5000 events per request |
POST /v1/ingest/batch — success 202 (full accept)
| Field | Type | Notes |
|---|---|---|
status |
string | "accepted" |
request_id |
string | UUID |
accepted_count |
integer | Number of events accepted into the pipeline |
rejected_count |
integer | 0 |
POST /v1/ingest/batch — success 202 (partial)
| Field | Type | Notes |
|---|---|---|
status |
string | "partial" |
request_id |
string | UUID |
accepted_count |
integer | |
rejected_count |
integer | Matches errors.length |
errors |
array | See BatchErrorDetail |
BatchErrorDetail
| Field | Type | Notes |
|---|---|---|
index |
integer | Zero-based index into request events |
code |
string | Machine-readable error code |
message |
string | Human-readable message |
Batch deduplication: If an event is a duplicate (same event_id already ingested), it counts toward accepted_count and does not add to errors.
Endpoints
POST {base}/v1/ingest/event
- Auth: Bearer required.
- Scope:
ingest:events - Rate limit (default): 1000 requests per minute per client IP.
- Success: 202 — body as above.
- Failure: 400 / 401 / 403 / 413 / 422 / 429 / 500 — see error-catalog.md.
POST {base}/v1/ingest/batch
- Auth: Bearer required.
- Scope:
ingest:batch - Rate limit (default): 100 requests per minute per client IP.
- Success: 202 — either full
acceptedorpartialas above. - Failure: 400 / 401 / 403 / 413 / 422 / 429; per-item failures generally appear as 202 partial with
errors.
GET {base}/v1/health
- Auth: none
- Success: 200 —
{"status":"ok","message":"Healthy"}(use for liveness checks).
Idempotency semantics
When Idempotency-Key is sent:
- The cache key is scoped to your credential and the header value (trimmed; empty after trim disables caching).
- If a cached 202 body exists for that key, it is returned without re-processing the request.
- Provides best-effort duplicate suppression for retries.
Client guidance
- Always send
project_idandenvironmentmatching the API key. - Generate a stable UUID per logical event for
event_idwhen you need deduplication across retries. - Use
Idempotency-Keyon retries after timeouts for stable 202 replay. - Prefer batch for volume; respect max events and rate limits.
- Parse 202 partial batch responses and log or fix rows by
errors[].index.
Related docs
- quickstart.md — first event in minutes
- error-catalog.md — status codes, codes, retry policy
- OpenAPI:
{base}/docs,{base}/redocwhen the service is running