Ingestion
Event format, batching, schema evolution, and error handling.
Everything you need to send events from your application to TrailStack.
Endpoint
POST https://api.trailstack.io/v1/Spaces/AppendRecords| Parameter | Required | Default | Description |
|---|---|---|---|
spaceName | Yes | — | Target Space name |
fireAndForget | No | true | Return immediately or wait for confirmation |
trackIpAddress | No | false | Capture client IP as the ip field |
trackCountry | No | false | Resolve country code from IP as the country field |
Content-Type: text/plain
Authorization: Bearer <your-inject-token>
Event format
Each event is a JSON object. The event field is required — it determines the view name. {"event": "page_view"} creates a view called evt_page_view.
{
"event": "page_view",
"properties": {
"path": "/pricing",
"duration_ms": 3200,
"authenticated": true
}
}| Field | Required | Type | Description |
|---|---|---|---|
event | Yes | string | Event name. Creates a view called evt_<name> in ClickHouse. |
properties or attrs | No | object | Event attributes. Each key becomes a typed column. |
references or refs | No | object | Reference-type attributes (foreign keys to entities). |
time | No | number or string | Unix seconds or ISO-8601. Defaults to server insertion time. |
Field naming rules: lowercase letters, numbers, and underscores. Must start with a letter. Pattern: ^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$
Batching
Send multiple events in one request using newline-delimited JSON (NDJSON). Each line is one event:
{"event": "page_view", "properties": {"path": "/pricing", "duration_ms": 3200}}
{"event": "page_view", "properties": {"path": "/docs", "duration_ms": 1500}}
{"event": "signup", "properties": {"plan": "free", "campaign": "google"}}All lines in a request go to the same Space. Each line is parsed independently — a malformed line does not block valid lines.
Response
200 OK
{"appendedLines": 3, "appendedBytes": 245}When fireAndForget=true (default), counters return 0 because the server enqueues the payload and responds immediately. Set fireAndForget=false to get actual counts.
402 Payment Required — storage limit reached. The response includes your current usage and a link to upgrade. Your data stays readable and queryable.
404 Not Found — Space does not exist. Check the spaceName parameter.
400 Bad Request — invalid request format.
fireAndForget
true(default): the server writes your payload to a queue and returns immediately. Fastest option. Use this in production when you do not need confirmation per request.false: the server waits until the payload is appended and returns actual line and byte counts. Use this when testing or when you need delivery confirmation.
Schema evolution
TrailStack detects types from your JSON and creates ClickHouse columns automatically. No migrations, no schema definitions.
Detected types:
| JSON value | ClickHouse type |
|---|---|
"hello" | String |
42 or 3.14 | Float64 |
true / false | Bool |
null | Ignored (no column created) |
New fields: when you send an event with a field TrailStack has not seen before, it adds the column automatically. Your next query sees the new column.
Mixed types: if you send the same field with different types across events (for example, "count": 42 in one event and "count": "hello" in another), both values are stored in separate typed columns. There is no data loss and no coercion.
Removing fields: if you stop sending a field, the column stays. Existing data is not affected. The column contains null for events that do not include it.
Unsupported values: objects and arrays inside properties are not supported. The entire event becomes a garbage row — the raw payload is preserved with an error message in the all_errors view.
What creates garbage rows:
- Malformed JSON
- Unknown top-level fields (allowed:
event,properties/attrs,references/refs,time,nonce,deleted) - Objects or arrays inside
properties - Invalid field names (must match
^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$) - Duplicate field names in the same event
Auto-detected fields
When trackIpAddress and trackCountry are enabled (both default to false), TrailStack adds two fields to every event:
| Field | Value |
|---|---|
ip | Client IP address |
country | Two-letter country code resolved from the IP |
Set trackIpAddress=true and trackCountry=true in the query string to enable.