DocsAsking questions

Asking questions

You ask in your own words, and your assistant turns the question into one of three kinds of request. Questions are always asked in parts (which rows, grouped how, totalled how), never as code, and every answer comes back with the facts an assistant needs to say where the number came from.

You wantYour assistant asksTool
A number your organization agreed on, such as revenuethe approved metricmetric_query
Anything else about the rows: a list, a count, a total by monththe dataset itselfdataset_query
A question you ask again and againthe saved view, by nameview_query

Your assistant is told to check for a metric or a view before working a number out itself, and to answer with the numbers exactly as returned, what was filtered and the date the data is as of.

When your organization keeps many datasets, your assistant first finds the right one by saying what you’re after in a few words, such as “ammo prices by caliber”. Ontyx answers with the closest few datasets, each with its columns, how many rows it has, and the metrics and views that already read it (dataset_list with looking_for).

Asking a metric

A metric already knows what it counts and its own filters. When you ask it, you choose:

  • The period to count by: one of the metric’s periods (day, week, month, quarter or year), or one number for the whole period. Without one, the metric’s default is used.
  • What to split it by: up to 4 of the things the metric can be split by.
  • Extra filters: on those same things, or on its date column.
  • A date range: from and to, both included. A date on its own includes that whole day.
  • An earlier version of the metric, or an earlier data version of its dataset, to see what it said then.

Anything the metric wasn’t set up for is refused with what it does allow, so an assistant can’t quietly count something else under the metric’s name.

Asking the rows of a dataset

A question of one dataset is made of parts, each optional:

  • Filters, all of which must hold: equals, is not (blanks count as “not”), more or less than, one of a list (up to 100 values), between two values (both ends included), contains some text (ignoring capitals), is blank, isn’t blank. Values are read as the column’s kind, the same way a load reads them, so 2026-01-31, $1,000 and yes mean what you’d expect.
  • Groups: by a column, or by the day, week (starting Monday), month, quarter or year a date falls in. A date and time falls in the period of its column’s time zone.
  • Totals: count of rows, count of different values, total, average, smallest, largest. Groups without totals are counted.
  • Columns to show, sort order and how many rows (100 unless you ask for more, up to 1,000).
  • An earlier data version, to ask what the data said then.

A misspelled column is answered with a suggestion (“There’s no column called “aplieddate”. Did you mean “applieddate”?”), and every problem in a question is reported at once. A question that takes longer than 20 seconds is stopped, and the answer says to ask a narrower one.

Across two datasets

When a dataset points at another through a declared relationship, a question can bring in the other dataset’s columns: each order with its customer’s city, each reply with the thread it answers. The other dataset’s columns are named after it (customers.city) everywhere in the question and the answer.

Every row of the dataset asked is kept. A row whose matching row isn’t there, or that has a blank where it should point, gets blanks in the other dataset’s columns, and the answer says how many: “3 of 4 rows point at a row of “meetings”; 1 row points at a row that isn’t there, so its “meetings” columns are blank.” It goes one step deep, through relationships the dataset itself declares.

The answer

Every answer that returns data carries the same fields besides the data itself, so any assistant can say which number it gave, from what, and how current it is.

What every answer carries

FieldWhat it tells you
datasetThe dataset the answer is about. For a metric, the one it’s counted from.
has_dataWhether anything has been loaded yet. When it’s false, the version and date fields below are left out.
data_versionWhich load the answer was read from. Each load makes a new one.
schema_versionWhich approved structure the data was read with. It only moves when a person approves a change.
as_ofWhen the data is true of the world. Your assistant should say this date with the number.
freshnessfresh (up to date), stale (a refresh on its schedule is late; the data is still answered) or unknown (the dataset has no schedule).
filtersEvery filter that was applied, each with means, the filter in words (status isn't "cancelled"). For a metric, from says whether it’s the metric’s own filter (metric), one in the question (question) or the date range (period). Empty when there were none.
read_fromEvery dataset the answer read, each with its data version, structure version, as-of date and freshness. When a question brought in another dataset, both are listed, the one asked as queried and the other as included.

Answers made of rows

A list of rows also says how to point back at each one:

FieldWhat it tells you
keyThe columns that identify each row of this dataset, in order. Empty when nothing does.
row_idsOne identifier per row shown, in the same order: each row’s values in those columns, such as { "order_id": "A-1001" }. Use one to follow that row’s history.
row_ids_noteWhen nothing identifies the rows, a sentence saying so, in place of identifiers.

Grouped and totalled answers are made of groups, not rows of the dataset, so they don’t carry row identifiers.

Metric answers

FieldWhat it tells you
metricThe metric’s short name.
metric_versionThe approved version that answered.
metric_version_in_forceWhen an earlier version was asked, the version in force now.
sentenceThe sentence that was approved, saying exactly what the metric counts.

Saved view answers

FieldWhat it tells you
viewThe view’s name.
parametersThe values the view was asked with.
periodWhen a period was given, which one, with its first and last day.
metricsEach metric the view asks, with the version that answered and, when the metric has moved on since the view was saved, saved_with_metric_version.
answersOne full metric answer per metric, exactly as asking that metric directly would give it.
not_answeredAny metric that was retired and so left out, with why.
read_fromEvery dataset the answers read, once each.

Other fields you’ll see

FieldWhat it tells you
rows and fieldsThe data, and the name and kind of each field in it (from: group or from: total for parts of a grouped answer).
rows_matching, groups, rows_returnedHow many rows matched every filter, how many groups there were, and how many rows or groups came back.
messageThe whole answer in a sentence or two, such as “5 rows of 6 rows in “orders” match where status isn’t “cancelled”, in 3 groups. The data is as of 2026-10-30T18:00:00.000Z (data version 1).”
latest_data_versionWhen an earlier load was asked, the newest one.
loaded_at, expected_cadenceWhen the data was loaded, and how often new data is expected.
freshness_noteOn a list, a preview or a row’s history, a sentence to pass on when the data is out of date.
includeFor a question across two datasets: how many rows matched a row of the other dataset, how many didn’t, and how many had a blank.
built_fromFor a dataset built from other datasets: the data version of each it was built from, and whether it’s behind.
truncated, noteWhether there were more groups than returned, and a note when rows were cut to keep the answer small.
next_stepWhile your organization is getting started, the natural next thing to do.

Exact amounts come back as text ("205.50") so no digit is lost, and dates and times come back in UTC.

When a question can’t be answered

Refusals are written for your assistant to pass on in plain words: the column that doesn’t exist and the nearest match, the value that doesn’t fit, the split a metric doesn’t take. Nothing is answered from a guess. The codes are listed in the tools reference.