Ingest API
| Endpoint | Caller | Key | Body | Success |
|---|---|---|---|---|
POST /v1/e | Browser script and packages | Site key, in the body as k | One event, JSON sent as text/plain | Always 204 |
POST /v1/batch | Servers, the agent | Ingest key, in a header | {"events": [...], "sent_at": "..."} | 202 + per-event result |
POST /v1/logs | Vector, Fluent Bit, ad-hoc shippers | Ingest key, in a header | Raw log lines, one per line | 202 + per-line result |
The two key kinds are explained in site key and ingest key. The ingest key is sent as Authorization: Bearer ksi_.... The browser script manages its own requests; most people never call /v1/e directly.
POST /v1/e
The browser beacon, sent with navigator.sendBeacon so there is no CORS preflight. Maximum body 16 KiB. The body is an event plus the site key:
{
"k": "kst_xxxx",
"uuid": "0192f3b0-7c1a-7000-8000-5d1e8f3a9b22",
"event": "$pageview",
"request": { "host": "example.com", "path": "/pricing/?utm_source=hn", "referrer": "https://news.ycombinator.com/" },
"properties": { "$screen_width": 1440 }
}The answer is always 204 with an empty body, including for an unknown key, a disallowed origin, an invalid event or a rate limit: a beacon cannot act on an error, and a uniform answer does not reveal which keys exist. If the site has allowed origins, a request whose Origin is not on the list is dropped. Ingest takes the user agent from the request and the visitor’s IP from the connection, behind Cloudflare from its headers.
POST /v1/batch
Events as JSON, up to 1,000 per request, gzip accepted. Fields, a worked curl and retries are in Server events and the HTTP API. The answer lists accepted and every rejection with its index, field and error.
POST /v1/logs
Raw access-log lines, one per \n, parsed with the same parsers as the agent. Give the format in ?format= or the X-Knockstat-Format header (values in Log formats). Each parsed line becomes a $request event whose id comes from the line, so a resent request does not double-count. At most 5,000 lines per request; index in the answer is the zero-based line number, and an unparseable line is rejected with field: "line".
curl -sS "https://app.knockstat.com/v1/logs?format=combined_host" \
-H "Authorization: Bearer $KNOCKSTAT_INGEST_KEY" \
-H "Content-Type: text/plain" \
--data-binary @access.logWith ?envelope=vector or ?envelope=fluentbit (or the X-Knockstat-Envelope header) the body is the shipper’s JSON records, one per line or as an array, and ingest reads the line from message (Vector) or log, else message (Fluent Bit). The envelope is never guessed. See Vector and Fluent Bit.
Limits
| Limit | Value | Over the limit |
|---|---|---|
| Events per batch | 1 to 1,000 | Batch rejected (400) |
| Body size | 1 MiB compressed, 8 MiB once decompressed | 413 |
Log lines per /v1/logs request | 5,000 | 400 |
| Event name | 1 to 200 characters, no control characters | Event rejected |
| Properties per event | 64 | Event rejected |
| Property key | 1 to 128 characters | Event rejected |
| Property value | string (at most 1,024 characters), finite number, boolean or null; no objects or arrays | Event rejected |
distinct_id | 200 characters | Event rejected |
timestamp | At most 10 minutes ahead of ingest's clock, after sent_at correction | Event rejected |
request.path, referrer, user_agent, host | 4,096, 2,048, 1,024 and 253 characters | Truncated, not rejected |
Characters are Unicode scalar values, not bytes.
Status codes
| Code | Meaning | What to do |
|---|---|---|
202 | Accepted. The body lists accepted and rejected. | Drop the accepted events from your buffer. |
204 | Beacon received, whatever the outcome (/v1/e only). | Nothing. |
400 | Body is not valid JSON, the batch is empty or too large, or the format is unknown. | Fix the request. Do not retry it unchanged. |
401 | Missing, unknown or revoked ingest key. | Stop and check the key. Do not retry. |
413 | Body over the size limit. | Split the batch and resend. |
415 | Unsupported Content-Encoding. | Send gzip or no encoding. |
429 | Over the key's rate limit (rate_limited), or the organization is past its plan's monthly quota (quota_exceeded). Retry-After gives seconds. | Keep the events, wait at least Retry-After, resend. |
503 | Ingest cannot store events right now. Retry-After is given. | Back off with jitter, keep events buffered, resend. |
Errors other than beacons share one body shape, where error is one of invalid_body, batch_too_large, unknown_format, unauthorized, payload_too_large, unsupported_encoding, rate_limited, quota_exceeded or unavailable:
{ "error": "rate_limited", "message": "ingest key ksi_7c0e… is over 500 requests per second" }Rate limits
Limits apply per key, as token buckets counted in events: a batch of 500 events costs 500 tokens, a beacon costs 1, and a /v1/logs call costs one per line. These are the defaults; your plan can change them.
| Key | Sustained | Burst |
|---|---|---|
Site key (kst_) | 100 events/s | 1,000 |
Ingest key (ksi_) | 5,000 events/s | 50,000 |
A beacon over its limit is dropped silently. A batch or logs call over its limit gets 429 with rate_limited and a Retry-After in whole seconds. Nothing in a refused call is stored, so resend the same batch.
Monthly quota
A plan may cap events per calendar month (UTC), with a grace above the cap. Past the cap events are still stored. Past the grace, a batch or logs call gets 429 with quota_exceeded and nothing in it is stored; beacons are dropped silently. The count behind the quota trails by up to five minutes. Your plan and this month’s events are shown under Settings → Plan and billing.
Versioning
The /v1 prefix covers this contract. New optional fields, log formats and response fields are added within v1; a changed meaning, a removed field or a tighter limit moves to /v2, and /v1 keeps running until deployed SDKs and agents have moved.
Read API
On a self-hosted install the same API also accepts Authorization: Bearer <key>, with keys the operator sets in the gateway’s config file ([keys] in gateway.toml, mapping a key to a tenant):
curl -s -H "Authorization: Bearer $KNOCKSTAT_KEY" \
"https://your-knockstat.example.com/v1/stats?from=$(date -d '7 days ago' +%s)&to=$(date +%s)&interval=hour"Without a session or key every endpoint answers 401 {"error": "not signed in"}.
Common parameters
| Parameter | Meaning |
|---|---|
from, to | The window [from, to): epoch seconds, epoch milliseconds or RFC 3339. Default: the 24 hours before to, which defaults to now. |
interval | Chart buckets: minute, hour (default) or day. A long window widens the bucket so there are at most about 240. Times are UTC. |
host | One site, by its host name as /v1/sites lists it. Default: every site. |
device | desktop, mobile or tablet. |
path, country, browser, os | Narrow to one page, country code, browser or OS. |
channel, referring_domain, utm_source, utm_medium, utm_campaign, utm_term, utm_content | Narrow to the sessions whose first page view had that value. |
limit | Rows in a list, where an endpoint has one. At most 1000. |
Endpoints
| Endpoint | Answers |
|---|---|
GET /v1/stats | Summary (requests, humans, bots, bytes, visitors, page views) with changes against the window before, and a time series. |
GET /v1/breakdowns | Top 50 pages, referrers, countries, devices, browsers, OSes, versions and outbound links, plus status shares. |
GET /v1/sessions | Sessions, bounces and duration against the window before; entry and exit pages, referrers, channels and UTMs. |
GET /v1/live | Visitors and page views in the last five minutes. |
GET /v1/hours?tz=Area/City | Visitors and page views by weekday and hour in that IANA time zone (default UTC). |
GET /v1/errors | Pages people got 4xx and 5xx answers on. |
GET /v1/events | Custom event names, most frequent first. |
GET /v1/events/detail?name= | One event over time, its property keys, and the top values of a property. |
GET /v1/sites | Sites seen in the window, busiest first. |
POST /v1/reports/{insights,funnels,flows,retention} | The product reports, with a JSON body. These are what the dashboard’s report builders send. |
GET /v1/usage?from=&to= | Stored events per UTC day for your organization, by site, source and event class. |
What each figure counts (visitors, page views, sessions, channels) matches the dashboard: bots and automated traffic are left out of people’s figures and counted on their own.
Errors, caching and CSV
- Errors are JSON with an
errormessage:400for a bad parameter or body (reports add thefieldthey refuse),401without a session or key,422for a report too large to run (with ahint),502when the store fails,501for a report the store does not have. - Answers are cached for 20 seconds per gateway replica, keyed by the exact request, and a window that ends less than 5 seconds ago is not cached. Round
todown so repeated calls share an answer. - The API answers JSON only. The dashboard’s CSV buttons on its lists and report tables export CSV.
Stuck? Email [email protected].