REST API

Admin API Reference

Create, list, inspect, and delete datasets. You can also manage their Dashboard cards and charts.

Authentication

Create an Admin API key in Settings → Admin. Admin API keys use the TypeID format and start with admin_. The key gives access to every dataset in your account, so keep it secret.

http
1Authorization: Bearer admin_01h45ytscbebc8jsnatf2t1wdy

Admin API keys manage datasets, Dashboard cards, and charts. The dataset API key returned by this API is used with the separate logs API.

Dataset object

id
string
An 11-character ID. Only lowercase letters and digits (a-z, 0-9).
userId
string
The ID of the account that owns the dataset.
title
string
The dataset name.
createdAt
number
Creation time in Unix milliseconds.
updatedAt
number
Last update time in Unix milliseconds.
apiKey
string · conditional
The dataset API key. It is returned when a dataset is created or fetched by ID. It is not included in the list response.
GET/api/admin/v1/datasets

List all datasets owned by the account. Newest datasets come first.

Example

bash
1curl "https://z8log.com/api/admin/v1/datasets" \
2  -H "Authorization: Bearer admin_01h45ytscbebc8jsnatf2t1wdy"
json
1{
2  "datasets": [
3    {
4      "id": "a1b2c3d4e5f",
5      "userId": "user_123",
6      "title": "Production",
7      "createdAt": 1767436200000,
8      "updatedAt": 1767436200000
9    }
10  ]
11}

Status codes

200The request succeeded.
401The Admin API key is missing or invalid.
500The server could not load the datasets.
POST/api/admin/v1/datasets

Create a dataset and return its dataset API key.

Dataset limits are per account: Free 2, Basic 10, Pro 50. Existing datasets stay available after a downgrade, but creating more is blocked while the account is at or above its limit. Metrics use the owner’s searchable history: 3, 7, or 30 days.

JSON body

title
string · optional
A name from 1 to 200 characters. If you omit it, the server picks a name.

Example

bash
1curl -X POST "https://z8log.com/api/admin/v1/datasets" \
2  -H "Authorization: Bearer admin_01h45ytscbebc8jsnatf2t1wdy" \
3  -H "Content-Type: application/json" \
4  -d '{ "title": "Production" }'
json
1{
2  "dataset": {
3    "id": "a1b2c3d4e5f",
4    "userId": "user_123",
5    "title": "Production",
6    "createdAt": 1767436200000,
7    "updatedAt": 1767436200000,
8    "apiKey": "dataset_01h45ytscbebc8jsnatf2t1wdx"
9  }
10}

The response has status 201 Created. Store the returned apiKey in a secure place.

Status codes

201The dataset was created.
400The JSON body or title is invalid.
401The Admin API key is missing or invalid.
409The account has reached its plan’s dataset limit. Delete a dataset or upgrade before creating another.
500The server could not create the dataset.
GET/api/admin/v1/datasets/{datasetId}

Get one dataset and its dataset API key.

Example

bash
1curl "https://z8log.com/api/admin/v1/datasets/a1b2c3d4e5f" \
2  -H "Authorization: Bearer admin_01h45ytscbebc8jsnatf2t1wdy"
json
1{
2  "dataset": {
3    "id": "a1b2c3d4e5f",
4    "userId": "user_123",
5    "title": "Production",
6    "createdAt": 1767436200000,
7    "updatedAt": 1767436200000,
8    "apiKey": "dataset_01h45ytscbebc8jsnatf2t1wdx"
9  }
10}

Status codes

200The request succeeded.
401The Admin API key is missing or invalid.
404The dataset does not exist in this account.
500The server could not load the dataset.

Inspect, preview, then save

With an Admin key, first read GET /api/admin/v1/datasets/{datasetId}. The owned dataset response includes dataset.apiKey. Use that dataset key for POST /api/logs/{datasetId}/query. Start with logs() samples, inspect fields and types, and preview a numeric formula. Use the Admin key for card and Chart PUT calls, then GET the saved result to verify.

The Query API needs no title, color, or slot. It uses the same language validation as saves. Sample checks apply only to generated AI drafts. There is no extra Admin query route.

Use start=now-5m, now-15m, or now-30m and end=now for relative reads. The server resolves all formulas against one clock. Responses include resolved timeframe and aggregate parts. Preserve each result with its formula, parts, and range. Chart points include clipped start/end bounds; use those bounds for details.

Manage a Dashboard

Every Dashboard and Charts endpoint requires a set query parameter, including writes and deletes. Set numbers start at 1. Free allows 1 set per page, Basic 2, and Pro 5. Admin also allows 5 sets per page. A missing or invalid set returns 400; a set above the current plan limit returns 403 with code: set_limit. Downgrades preserve higher sets but block access until the plan allows them again.

Each dataset has independent Dashboard and Chart sets. Every Dashboard set has 12 fixed slots. A card stores its title, tone, formula, and display format. The Dashboard timeframe is not stored in a card.

  1. Call GET /api/admin/v1/datasets/{datasetId}/dashboard?set=1 without a timeframe to inspect all slot configurations.
  2. Choose a slot from 1 to 12. Call PUT /api/admin/v1/datasets/{datasetId}/dashboard/cards/{slot}?set=1 with the complete card configuration. PUT creates or replaces the slot.
  3. Call the Dashboard GET endpoint with start and end to verify the calculated values.
  4. Call DELETE /api/admin/v1/datasets/{datasetId}/dashboard/cards/{slot}?set=1 to clear a slot.

Formulas are written in the formula language. To write formulas for JSON data fields, first inspect logs with the dataset API key returned by the Admin API. Use stable fields such as data.userId and data.event. Z8Log cannot infer what an active user means; the formula must define it from fields present in the logs.

GET/api/admin/v1/datasets/{datasetId}/dashboard?set={set}

Get all 12 Dashboard slots. Add both start and end to calculate card values for a range of up to 30 days.

Query parameters

set
integer · required
Set number within the owner’s plan limit: Free 1, Basic 1–2, Pro 1–5.
start
relative or ISO timestamp · optional
Inclusive range start. It must be used together with end.
end
relative or ISO timestamp · optional
Exclusive range end. It must be after start and used together with start.

Response fields

timeframe
object | null
The normalized start and end values. It is null when the request has no timeframe.
cards
array
Exactly 12 slot objects, ordered from slot 1 to slot 12.
cards[].slot
integer
The slot number from 1 to 12.
cards[].config
object | null
The complete saved card configuration, or null for an empty slot.
cards[].parts
array
Unique aggregate parts: formula, logsFormula, field, raw. Empty for configuration-only reads.
cards[].value
object | null
The calculated value, or null when no timeframe was requested or the slot is empty.

A timeframe can be at most 30 days; end can be at most five minutes ahead of server time.

Example

bash
1curl "https://z8log.com/api/admin/v1/datasets/a1b2c3d4e5f/dashboard?set=1" \
2  -H "Authorization: Bearer admin_01h45ytscbebc8jsnatf2t1wdy"
json
1{
2  "timeframe": null,
3  "cards": [
4    { "slot": 1, "config": {
5      "title": "Errors",
6      "tone": "red",
7      "formula": "count(level = ERROR)",
8      "format": "number"
9    }, "value": null, "parts": [] },
10    { "slot": 2, "config": null, "value": null, "parts": [] }
11  ]
12}

The real response always has 12 slot objects. This example only shows the first two.

Values example

bash
1curl "https://z8log.com/api/admin/v1/datasets/a1b2c3d4e5f/dashboard?set=1&start=2026-09-02T00%3A00%3A00.000Z&end=2026-09-02T10%3A15%3A00.000Z" \
2  -H "Authorization: Bearer admin_01h45ytscbebc8jsnatf2t1wdy"
json
1{
2  "timeframe": {
3    "start": "2026-09-02T00:00:00.000Z",
4    "end": "2026-09-02T10:15:00.000Z"
5  },
6  "cards": [
7    {
8      "slot": 1,
9      "config": {
10        "title": "Error rate",
11        "tone": "red",
12        "formula": "count(level = ERROR) / count() * 100",
13        "format": "percent"
14      },
15      "value": { "raw": 4.2, "formatted": "4.2%" },
16      "parts": [
17        { "formula": "count(level = ERROR)", "logsFormula": "logs(level = ERROR)", "field": null, "raw": 42 },
18        { "formula": "count()", "logsFormula": "logs()", "field": null, "raw": 1000 }
19      ]
20    }
21  ]
22}

The real response also includes the other 11 slots.

Value object

raw
number | null
The numeric result of the formula. It is not rounded here.
formatted
string
Display text. The number format shows integers as they are and other values with up to two decimal places. The percent format uses one decimal place and a percent sign.

When a formula divides by zero, or a stored formula can no longer be read, the value is undefined:

json
1{
2  "raw": null,
3  "formatted": "-"
4}
200The request succeeded.
400The timeframe is invalid or longer than 30 days. No timezone-change detection is used.
401The Admin API key is missing or invalid.
404The dataset does not exist in this account.
500The server could not load the Dashboard.
PUT/api/admin/v1/datasets/{datasetId}/dashboard/cards/{slot}?set={set}

Create or replace one Dashboard card. Slot must be an integer from 1 to 12.

Example

bash
1curl -X PUT "https://z8log.com/api/admin/v1/datasets/a1b2c3d4e5f/dashboard/cards/1?set=1" \
2  -H "Authorization: Bearer admin_01h45ytscbebc8jsnatf2t1wdy" \
3  -H "Content-Type: application/json" \
4  -d '{  "title": "Errors",  "tone": "red",  "formula": "count(level = ERROR)",  "format": "number"}'

Request body

json
1{
2  "title": "Errors",
3  "tone": "red",
4  "formula": "count(level = ERROR)",
5  "format": "number"
6}
title
string · required
Card title from 1 to 80 characters after surrounding whitespace is removed.
tone
string · required
Card color. One of red, orange, yellow, lime, green, teal, cyan, blue, indigo, purple, pink, or gray.
formula
string · required
The calculation rule, written in the formula language. The server stores a normalized version of the formula.
format
string · required
How the value is shown. number shows the value as it is. percent adds one decimal place and a percent sign; the formula itself must multiply by 100.

The card object must contain exactly these four fields. Missing fields and unknown extra fields return 400. An invalid formula returns 400 with an error message that starts with Formula: and names the column of the problem.

Aggregates, filters, operators, and limits are described on the Formula Language page. All formulas use the timeframe supplied to the Dashboard GET request. Do not put a timeframe inside a card configuration.

Distinct count example

json
1{
2  "title": "Active users",
3  "tone": "blue",
4  "formula": "distinct(data.userId, data.event = \"app_used\")",
5  "format": "number"
6}

Active users percentage

json
1{
2  "title": "Active user rate",
3  "tone": "blue",
4  "formula": "distinct(data.userId, data.event = \"app_used\") / distinct(data.userId) * 100",
5  "format": "percent"
6}

This example divides users with an app_used event by all users seen in logs during the requested Dashboard timeframe. Division by zero gives an undefined value.

Response

json
1{
2  "success": true
3}

The server only accepts this formula language. Raw SQL and invalid fields are rejected. Valid data.* paths absent from samples are accepted. Saved formulas must be numeric.

200The card was saved.
400The slot or card configuration is invalid.
401The Admin API key is missing or invalid.
404The dataset does not exist in this account.
500The server could not save the Dashboard card.
DELETE/api/admin/v1/datasets/{datasetId}/dashboard/cards/{slot}?set={set}

Clear one Dashboard slot. Clearing an empty slot also succeeds.

bash
1curl -X DELETE "https://z8log.com/api/admin/v1/datasets/a1b2c3d4e5f/dashboard/cards/1?set=1" \
2  -H "Authorization: Bearer admin_01h45ytscbebc8jsnatf2t1wdy"
200The slot is empty.
400The slot is invalid.
401The Admin API key is missing or invalid.
404The dataset does not exist in this account.
500The server could not clear the Dashboard card.
POST/api/admin/v1/datasets/{datasetId}/dashboard/cards/move?set={set}

Move a Dashboard card to an empty slot, or swap two filled slots.

Example

bash
1curl -X POST "https://z8log.com/api/admin/v1/datasets/a1b2c3d4e5f/dashboard/cards/move?set=1" \
2  -H "Authorization: Bearer admin_01h45ytscbebc8jsnatf2t1wdy" \
3  -H "Content-Type: application/json" \
4  -d '{ "from": 1, "to": 4 }'

Request body

from
integer · required
Source slot from 1 to 12.
to
integer · required
Target slot from 1 to 12. It must differ from the source slot.

A filled source moves to an empty target. Two filled slots are swapped. An empty source makes no change and still succeeds.

json
1{
2  "success": true
3}
200The move or swap was accepted.
400The request body or either slot is invalid.
401The Admin API key is missing or invalid.
404The dataset does not exist in this account.
500The server could not move the Dashboard card.

Manage Charts

Each Chart set has four chart slots. A chart stores its title, tone, formula, display format, and chart type. The timeframe and adaptive bucket size are not stored in the chart.

  1. Call GET /api/admin/v1/datasets/{datasetId}/charts?set=1 without a timeframe to inspect all four slot configurations.
  2. Choose a slot from 1 to 4. Call PUT /api/admin/v1/datasets/{datasetId}/charts/{slot}?set=1 with the complete chart configuration. PUT creates or replaces the slot.
  3. Call the Charts GET endpoint with start and end to calculate the formula once for every adaptive time bucket.
  4. Use the move endpoint to move or swap charts. Use DELETE to clear a slot.

Charts use the same formula language as Dashboard cards. The formula is evaluated separately for every time bucket.

GET/api/admin/v1/datasets/{datasetId}/charts?set={set}

Get all four chart slots. Add both start and end to calculate a time series for each configured chart.

Query parameters

set
integer · required
Set number within the owner’s plan limit: Free 1, Basic 1–2, Pro 1–5.
start
relative or ISO timestamp · optional
Inclusive range start. It must be used together with end.
end
relative or ISO timestamp · optional
Exclusive range end. It must be after start and used together with start.

Response fields

timeframe
object | null
The expanded query start and end, restricted to the plan's available history and server time. It is null when the request has no timeframe.
bucketMs
integer | null
The adaptive time bucket size in milliseconds. It is null when the request has no timeframe.
charts
array
Exactly four slot objects, ordered from slot 1 to slot 4.
charts[].slot
integer
The slot number from 1 to 4.
charts[].config
object | null
The complete saved chart configuration, or null for an empty slot.
charts[].points
array
Calculated time points in ascending order. It is empty without a timeframe or configuration.
charts[].points[].start, end
UTC timestamps
Full clock-aligned interval bounds, inclusive start and exclusive end. Every interval spans bucketMs.
charts[].points[].parts
array
Unique aggregate parts: formula, logsFormula, field, raw.
charts[].points[].at
ISO 8601 string
The start of the time bucket.
charts[].points[].value
object
The formula result for this bucket, with raw and formatted fields.

A requested timeframe can be at most 30 days; end can be at most five minutes ahead of server time. The server first restricts the request to available history, chooses a clean interval targeting 30 intervals, then expands start backward and end forward to full clock-aligned intervals without changing the interval size. Already aligned bounds stay unchanged. Queries include logs in the expanded range, subject to the plan's history limits and server time. The current interval displays its full bounds and fills as logs arrive. Alignment can produce 31 columns. Within the plan's available history, the fixed ranges use 15m / 30s, 1h / 2m, 6h / 15m, 24h / 1h, 3d / 3h, 7d / 6h, and 30d / 1d.

Configuration example

bash
1curl "https://z8log.com/api/admin/v1/datasets/a1b2c3d4e5f/charts?set=1" \
2  -H "Authorization: Bearer admin_01h45ytscbebc8jsnatf2t1wdy"
json
1{
2  "timeframe": null,
3  "bucketMs": null,
4  "charts": [
5    { "slot": 1, "config": {
6      "title": "Errors over time",
7      "tone": "red",
8      "formula": "count(level = ERROR)",
9      "format": "number",
10      "visualization": "line"
11    }, "points": [] },
12    { "slot": 2, "config": null, "points": [] }
13  ]
14}

The real response always has four slot objects. This example only shows the first two.

Time-series example

bash
1curl "https://z8log.com/api/admin/v1/datasets/a1b2c3d4e5f/charts?set=1&start=2026-09-02T00%3A00%3A00.000Z&end=2026-09-02T10%3A15%3A00.000Z" \
2  -H "Authorization: Bearer admin_01h45ytscbebc8jsnatf2t1wdy"
json
1{
2  "timeframe": {
3    "start": "2026-09-02T00:00:00.000Z",
4    "end": "2026-09-02T10:30:00.000Z"
5  },
6  "bucketMs": 1800000,
7  "charts": [
8    {
9      "slot": 1,
10      "config": {
11        "title": "Errors over time",
12        "tone": "red",
13        "formula": "count(level = ERROR)",
14        "format": "number",
15        "visualization": "line"
16      },
17      "points": [
18        { "at": "2026-09-02T00:00:00.000Z", "start": "2026-09-02T00:00:00.000Z", "end": "2026-09-02T00:30:00.000Z", "value": { "raw": 3, "formatted": "3" }, "parts": [{ "formula": "count(level = ERROR)", "logsFormula": "logs(level = ERROR)", "field": null, "raw": 3 }] },
19        { "at": "2026-09-02T00:30:00.000Z", "start": "2026-09-02T00:30:00.000Z", "end": "2026-09-02T01:00:00.000Z", "value": { "raw": 5, "formatted": "5" }, "parts": [{ "formula": "count(level = ERROR)", "logsFormula": "logs(level = ERROR)", "field": null, "raw": 5 }] }
20      ]
21    }
22  ]
23}

The real response includes every bucket and all four slots. A missing count bucket returns zero. Division by zero returns an undefined value with raw: null and formatted: "-".

200The request succeeded.
400The timeframe is invalid or longer than 30 days. No timezone-change detection is used.
401The Admin API key is missing or invalid.
404The dataset does not exist in this account.
500The server could not load the charts.
PUT/api/admin/v1/datasets/{datasetId}/charts/{slot}?set={set}

Create or replace one chart. Slot must be an integer from 1 to 4.

Example

bash
1curl -X PUT "https://z8log.com/api/admin/v1/datasets/a1b2c3d4e5f/charts/1?set=1" \
2  -H "Authorization: Bearer admin_01h45ytscbebc8jsnatf2t1wdy" \
3  -H "Content-Type: application/json" \
4  -d '{  "title": "Errors over time",  "tone": "red",  "formula": "count(level = ERROR)",  "format": "number",  "visualization": "line"}'

Request body

json
1{
2  "title": "Errors over time",
3  "tone": "red",
4  "formula": "count(level = ERROR)",
5  "format": "number",
6  "visualization": "line"
7}
title
string · required
Chart title from 1 to 80 characters after surrounding whitespace is removed.
tone
string · required
Chart color. One of red, orange, yellow, lime, green, teal, cyan, blue, indigo, purple, pink, or gray.
formula
string · required
The calculation rule, written in the formula language. The server stores a normalized version of the formula.
format
string · required
number or percent. Percent adds one decimal place and a percent sign; the formula itself must multiply by 100.
visualization
string · required
bar or line.

The chart object must contain exactly these five fields. Missing fields and unknown extra fields return 400. Do not put a timeframe or bucket size inside a chart configuration.

Response

json
1{
2  "success": true
3}
200The chart was saved.
400The slot or chart configuration is invalid.
401The Admin API key is missing or invalid.
404The dataset does not exist in this account.
500The server could not save the chart.
DELETE/api/admin/v1/datasets/{datasetId}/charts/{slot}?set={set}

Clear one chart slot. Clearing an empty slot also succeeds.

bash
1curl -X DELETE "https://z8log.com/api/admin/v1/datasets/a1b2c3d4e5f/charts/1?set=1" \
2  -H "Authorization: Bearer admin_01h45ytscbebc8jsnatf2t1wdy"
200The slot is empty.
400The slot is invalid.
401The Admin API key is missing or invalid.
404The dataset does not exist in this account.
500The server could not clear the chart.
POST/api/admin/v1/datasets/{datasetId}/charts/move?set={set}

Move a chart to an empty slot, or swap two filled slots.

Example

bash
1curl -X POST "https://z8log.com/api/admin/v1/datasets/a1b2c3d4e5f/charts/move?set=1" \
2  -H "Authorization: Bearer admin_01h45ytscbebc8jsnatf2t1wdy" \
3  -H "Content-Type: application/json" \
4  -d '{ "from": 1, "to": 4 }'

Request body

from
integer · required
Source slot from 1 to 4.
to
integer · required
Target slot from 1 to 4. It must differ from the source slot.

A filled source moves to an empty target. Two filled slots are swapped. An empty source makes no change and still succeeds.

json
1{
2  "success": true
3}
200The move or swap was accepted.
400The request body or either slot is invalid.
401The Admin API key is missing or invalid.
404The dataset does not exist in this account.
500The server could not move the chart.
DELETE/api/admin/v1/datasets/{datasetId}

Permanently delete a dataset and all logs stored in it.

This action cannot be undone.

Example

bash
1curl -X DELETE "https://z8log.com/api/admin/v1/datasets/a1b2c3d4e5f" \
2  -H "Authorization: Bearer admin_01h45ytscbebc8jsnatf2t1wdy"
json
1{
2  "success": true
3}

Status codes

200The dataset was deleted.
401The Admin API key is missing or invalid.
404The dataset does not exist in this account.
500The server could not delete the dataset.

Errors

Errors use a JSON object with one error field.

json
1{
2  "error": "Not authorized"
3}