REST API

API Reference

Send structured logs to a dataset, then read and filter them with plain HTTP. No SDK is required.

Base URL and authentication

The hosted API base URL is https://z8log.com. For a self-hosted install, use the URL of that install.

POST requests need the dataset ID in the path and its dataset API key as a Bearer token. Dataset API keys use the TypeID format and start with dataset_. You can find both values on the dataset setup page.

http
1Authorization: Bearer dataset_01h45ytscbebc8jsnatf2t1wdx

Dataset API keys only give access to one dataset. To create and manage datasets, use the Admin API.

Plan limits

Each dataset has 1 Dashboard set and 1 Chart set on Free, 2 of each on Basic, and 5 of each on Pro. Each Dashboard set holds 12 cards; each Chart set holds 4 charts. The two pages remember their selected sets independently. Downgrades preserve sets above the new limit but block their use until an upgrade.

Free accepts 1 log every 5 seconds, Basic 1 per second, and Pro 5 per second, per dataset. Small bursts allow 3, 5, or 25 logs respectively. Each accepted log uses one slot; slots refill at the plan’s rate. All keys and write paths for the same dataset share this allowance. Separate datasets have separate allowances. There is no monthly log quota.

A rate-limited write returns HTTP 429 with code: ingestion_rate_limited and a Retry-After header in seconds. Wait that long before retrying. Failed writes can still use a slot. Do not blindly retry a write after a timeout: it may already have been stored, and another submission can create a duplicate.

Messages allow 1 KiB and serialized data allows 2 KiB, measured in UTF-8 bytes (1 KiB = 1,024 bytes). These limits are the same on every plan.

Searchable history is 3 days on Free, 7 on Basic, and 30 on Pro. Queries, metrics, and pagination use only log timestamps inside the owner’s current history window, up to the server’s current time. Older and future logs stay stored but hidden while outside that window. Upgrading can reveal older logs. Empty intersections return empty results. Numeric queries must still satisfy the existing 30-day duration limit before history is applied.

POST/api/logs/{datasetId}

Write one log entry to a dataset.

Request headers

Authorization
string · required
Use Bearer <datasetApiKey>.
Content-Type
string · recommended
Use application/json.

JSON body

message
string · required
The log message. It must not be empty. Maximum length: 1024 characters.
level
string · optional
One of DEBUG, INFO, WARNING, or ERROR. Case-insensitive. Missing or unknown values become INFO.
flowId
string · optional
An ID used to group related logs. Maximum length: 128 characters.
data
any JSON · optional
Extra structured data. Maximum serialized size: 2048 characters.
timestamp
string · optional
The event time as an ISO 8601 string. The server time is used when this is missing.

Example

bash
1curl --fail-with-body -X POST https://z8log.com/api/logs/your-dataset-id \
2  -H "Authorization: Bearer dataset_01h45ytscbebc8jsnatf2t1wdx" \
3  -H "Content-Type: application/json" \
4  -d '{
5    "message": "User signed up",
6    "level": "INFO",
7    "flowId": "signup_abc123",
8    "data": { "userId": "123", "plan": "pro" },
9    "timestamp": "2026-01-03T10:30:00.000Z"
10  }'
json
1{
2  "ok": true
3}

Status codes

200The request succeeded.
400The JSON body is invalid.
401The dataset API key is missing or invalid.
404The dataset does not exist.
429The dataset’s log rate limit was reached. Wait for Retry-After seconds.
503Plan information is temporarily unavailable. Try again later.
500The server could not complete the request.
POST/api/logs/{datasetId}/query

Read entries, calculate a value, or request a series with one formula.

Use the dataset Bearer key and Content-Type: application/json. See the Formula Language for all functions, types, time rules, and limits. Signed-in formula previews use POST /api/datasets/{datasetId}/query with dataset ownership checks.

formula
string · required
logs(filter) returns entries. Numeric formulas return a value or series. The response includes the normalized formula.
start, end
string | null
ISO timestamps with timezone, now, or now-15m. Entries allow omitted or null bounds and default to All time. Numeric queries require both bounds, at most 30 days, and end at most five minutes in the future.
mode
value | series
Numeric only. Default value. Series uses automatic buckets.
format
number | percent
Numeric only. Default number. Percent does not multiply by 100.
limit
integer 1–1,000
Entries only. Default 100 per page; Logs UI uses 100. No total page-chain cap. Aggregates scan the full accepted range.
before, after
positive safe integer
Entries only. pos < before AND pos > after. Entries always use pos DESC.

Unknown fields and options for another result type return 400. Time is captured once on the server. Ranges include start and exclude end.

bash
1curl --fail-with-body -X POST "https://z8log.com/api/logs/your-dataset-id/query" -H "Authorization: Bearer $Z8LOG_API_KEY" -H "Content-Type: application/json" --data '{"formula":"logs(level = ERROR and data.service = \"api\")","start":"now-15m","end":"now","limit":25}'
json
1{
2  "formula": "logs()",
3  "start": "now-5m",
4  "end": "now",
5  "limit": 25
6}
json
1{
2  "formula": "logs(level = ERROR and data.service = \"api\")",
3  "start": "now-15m",
4  "end": "now",
5  "limit": 25
6}
json
1{
2  "formula": "count(level = ERROR) / count() * 100",
3  "start": "now-30m",
4  "end": "now",
5  "format": "percent"
6}
json
1{
2  "formula": "avg(data.durationMs, data.service = \"api\")",
3  "start": "now-5m",
4  "end": "now",
5  "mode": "series"
6}
json
1{
2  "formula": "logs(data.durationMs >= 1000 and data.durationMs < 5000)",
3  "start": "2026-09-09T10:00:00Z",
4  "end": "2026-09-09T10:15:00Z",
5  "limit": 25
6}
json
1{
2  "formula": "logs()"
3}

Every result has kind, formula, and timeframe with resolved UTC start and end (null for an unbounded entry range).

kind: logs
entries, next
Entries contain pos, timestamp, level, flowId, message, and data. next is null or a whole query object. Submit next unchanged to this endpoint with the same credentials.
kind: value
value, parts
value has raw and formatted. Each unique aggregate part has formula, logsFormula, field, and raw.
kind: series
bucketMs, points
Each point has at, start, end, value, and parts. Bounds are clipped to the range. Empty buckets are included.
json
1{
2  "kind": "logs",
3  "formula": "logs(level = ERROR)",
4  "timeframe": {
5    "start": "2026-09-09T10:00:00.000Z",
6    "end": "2026-09-09T10:15:00.000Z"
7  },
8  "entries": [
9    {
10      "pos": 100,
11      "timestamp": "2026-09-09T10:02:00.000Z",
12      "level": "ERROR",
13      "flowId": "request-123",
14      "message": "Request failed",
15      "data": {
16        "service": "api",
17        "durationMs": 1250
18      }
19    }
20  ],
21  "next": {
22    "formula": "logs(level = ERROR)",
23    "start": "2026-09-09T10:00:00.000Z",
24    "end": "2026-09-09T10:15:00.000Z",
25    "limit": 1,
26    "before": 100
27  }
28}

Continuation keeps resolved bounds, limit, and after. before is the last returned position and excludes later inserts. A new refresh starts with the original relative request. A separate detail query can include late inserts with older timestamps.

Start with sample entries. Read actual field names, types, and meanings before choosing filters. Narrow with typed equality or numeric thresholds, then calculate totals and trends. Unknown valid data paths are accepted; samples are not a complete schema.

For position polling, keep after unchanged until all next pages finish. Merge by pos. Retry a failed cycle with the same after. Start each new cycle without the old before. Wait until each cycle ends before starting the next.

200The request succeeded.
400A formula, range, or query option is invalid.
401The dataset API key is missing or invalid.
404The dataset does not exist.
503Plan information is temporarily unavailable. Try again later.
500The server could not complete the request.
json
1{
2  "error": "Numeric comparison needs a data.* field and a number",
3  "code": "invalid_formula",
4  "position": 21
5}

Formula errors include a zero-based position. Malformed JSON, invalid ranges, and invalid options return 400. Database failures return a safe error message.

Errors

Errors use a JSON object with one error field.

json
1{
2  "error": "Invalid API key"
3}