// context projectapi

Dataset Query API

The dataset query API reads only the files for the requested dataset.

Use a PAT, not a project API key.

POST /api/datasets/:projectSlug/:datasetId/query
Authorization: Bearer pat_xxx
Content-Type: application/json

There is no raw SQL endpoint. Queries use a builder-style JSON shape that Loguro validates before generating SQL.

For TypeScript projects, @loguro/datasets exposes a typed query DSL that serializes to this JSON shape. See Datasets — TypeScript SDK.

The legacy project-level query endpoint still exists for the default dataset:

POST /api/datasets/:projectSlug/query

Use dataset-specific queries for new code.

Request shape

{
  "range": {
    "from": "2026-06-01T00:00:00Z",
    "to": "2026-06-10T23:59:59Z"
  },
  "select": [
    { "field": "country" },
    { "aggregate": "count", "as": "events" }
  ],
  "where": [
    { "field": "plan", "op": "=", "value": "pro" },
    { "field": "amount", "op": ">", "value": 20 }
  ],
  "groupBy": ["country"],
  "orderBy": [
    { "field": "events", "direction": "desc" }
  ],
  "limit": 100
}

All top-level properties are optional. If omitted:

  • range defaults to the last 7 days.
  • select defaults to count.
  • limit defaults to 100.
  • limit is hard-capped at 1000.

Queryable fields

You can query:

  • declared schema fields for that dataset
  • timestamp

You cannot filter, group, or aggregate on context in v1.

If the dataset exists but has not registered a schema yet, the API returns:

{ "error": "Dataset schema not found" }

Select fields

Select raw fields:

{
  "select": [
    { "field": "timestamp" },
    { "field": "country" },
    { "field": "plan" }
  ],
  "limit": 50
}

When selecting raw fields with groupBy, every non-aggregate selected field must also be in groupBy.

Valid:

{
  "select": [
    { "field": "country" },
    { "aggregate": "count", "as": "events" }
  ],
  "groupBy": ["country"]
}

Invalid:

{
  "select": [
    { "field": "country" },
    { "field": "plan" },
    { "aggregate": "count", "as": "events" }
  ],
  "groupBy": ["country"]
}

plan is selected but not grouped.

Aggregates

Supported aggregates:

AggregateField requiredCompatible fields
countnoall records, or a field if supplied
sumyesnumber
avgyesnumber
minyesnumber, string, boolean, timestamp
maxyesnumber, string, boolean, timestamp
approx_count_distinctyesdeclared fields and timestamp

Examples:

{ "aggregate": "count", "as": "events" }
{ "aggregate": "sum", "field": "amount", "as": "revenue" }
{ "aggregate": "approx_count_distinct", "field": "customer_id", "as": "customers" }

Aliases must be simple identifiers:

^[A-Za-z_][A-Za-z0-9_]{0,63}$

Filters

Supported operators:

OperatorValueNotes
=requiredexact match
!=requirednot equal
>requirednumbers and timestamps are most common
>=requirednumbers and timestamps are most common
<requirednumbers and timestamps are most common
<=requirednumbers and timestamps are most common
innon-empty arrayeach value must match the field type
containsstringstring fields only
startsWithstringstring fields only
existsnonefield is not null

Examples:

{ "field": "plan", "op": "=", "value": "pro" }
{ "field": "amount", "op": ">", "value": 20 }
{ "field": "country", "op": "in", "value": ["RO", "US", "DE"] }
{ "field": "plan", "op": "startsWith", "value": "pro" }
{ "field": "signup_at", "op": ">=", "value": "2026-06-01T00:00:00Z" }

Range

range always filters the standard timestamp field.

{
  "range": {
    "from": "2026-06-01T00:00:00Z",
    "to": "2026-06-10T23:59:59Z"
  }
}

If only from is supplied, the query has a lower bound. If only to is supplied, the query has an upper bound.

If range is omitted, Loguro queries the last 7 days.

Ordering

Order by selected aliases or queryable fields:

{
  "orderBy": [
    { "field": "events", "direction": "desc" }
  ]
}

direction can be asc or desc. Omitted or unknown values default to descending.

Complete examples

Revenue by country:

{
  "range": {
    "from": "2026-06-01T00:00:00Z",
    "to": "2026-06-10T23:59:59Z"
  },
  "select": [
    { "field": "country" },
    { "aggregate": "count", "as": "events" },
    { "aggregate": "sum", "field": "amount", "as": "revenue" }
  ],
  "where": [
    { "field": "plan", "op": "in", "value": ["pro", "team"] }
  ],
  "groupBy": ["country"],
  "orderBy": [{ "field": "revenue", "direction": "desc" }],
  "limit": 50
}

Recent records:

{
  "select": [
    { "field": "timestamp" },
    { "field": "country" },
    { "field": "plan" },
    { "field": "amount" }
  ],
  "orderBy": [{ "field": "timestamp", "direction": "desc" }],
  "limit": 100
}

Unique customers:

{
  "select": [
    { "aggregate": "approx_count_distinct", "field": "customer_id", "as": "customers" }
  ],
  "where": [
    { "field": "customer_id", "op": "exists" }
  ]
}

Empty datasets

If the dataset has a schema but no matching files, the query returns:

{ "rows": [] }

This is not an error.

Errors

Invalid queries return 400:

{ "error": "Unknown field: context.user" }

Common query errors:

  • dataset not found
  • dataset schema not found
  • unknown field
  • invalid operator
  • invalid alias
  • aggregate incompatible with field type
  • value type does not match field type
  • selecting a field that is not in groupBy
  • trying to filter or group by context
// related

See also