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.
1Authorization: Bearer dataset_01h45ytscbebc8jsnatf2t1wdxDataset 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.
/api/logs/{datasetId}Write one log entry to a dataset.
Request headers
Bearer <datasetApiKey>.application/json.JSON body
DEBUG, INFO, WARNING, or ERROR. Case-insensitive. Missing or unknown values become INFO.Example
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 }'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./api/logs/{datasetId}/queryRead 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.
Unknown fields and options for another result type return 400. Time is captured once on the server. Ranges include start and exclude end.
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}'1{
2 "formula": "logs()",
3 "start": "now-5m",
4 "end": "now",
5 "limit": 25
6}1{
2 "formula": "logs(level = ERROR and data.service = \"api\")",
3 "start": "now-15m",
4 "end": "now",
5 "limit": 25
6}1{
2 "formula": "count(level = ERROR) / count() * 100",
3 "start": "now-30m",
4 "end": "now",
5 "format": "percent"
6}1{
2 "formula": "avg(data.durationMs, data.service = \"api\")",
3 "start": "now-5m",
4 "end": "now",
5 "mode": "series"
6}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}1{
2 "formula": "logs()"
3}Every result has kind, formula, and timeframe with resolved UTC start and end (null for an unbounded entry range).
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.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.
1{
2 "error": "Invalid API key"
3}