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 want | Your assistant asks | Tool |
|---|---|---|
| A number your organization agreed on, such as revenue | the approved metric | metric_query |
| Anything else about the rows: a list, a count, a total by month | the dataset itself | dataset_query |
| A question you ask again and again | the saved view, by name | view_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,000andyesmean 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
| Field | What it tells you |
|---|---|
dataset | The dataset the answer is about. For a metric, the one it’s counted from. |
has_data | Whether anything has been loaded yet. When it’s false, the version and date fields below are left out. |
data_version | Which load the answer was read from. Each load makes a new one. |
schema_version | Which approved structure the data was read with. It only moves when a person approves a change. |
as_of | When the data is true of the world. Your assistant should say this date with the number. |
freshness | fresh (up to date), stale (a refresh on its schedule is late; the data is still answered) or unknown (the dataset has no schedule). |
filters | Every 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_from | Every 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:
| Field | What it tells you |
|---|---|
key | The columns that identify each row of this dataset, in order. Empty when nothing does. |
row_ids | One 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_note | When 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
| Field | What it tells you |
|---|---|
metric | The metric’s short name. |
metric_version | The approved version that answered. |
metric_version_in_force | When an earlier version was asked, the version in force now. |
sentence | The sentence that was approved, saying exactly what the metric counts. |
Saved view answers
| Field | What it tells you |
|---|---|
view | The view’s name. |
parameters | The values the view was asked with. |
period | When a period was given, which one, with its first and last day. |
metrics | Each 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. |
answers | One full metric answer per metric, exactly as asking that metric directly would give it. |
not_answered | Any metric that was retired and so left out, with why. |
read_from | Every dataset the answers read, once each. |
Other fields you’ll see
| Field | What it tells you |
|---|---|
rows and fields | The 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_returned | How many rows matched every filter, how many groups there were, and how many rows or groups came back. |
message | The 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_version | When an earlier load was asked, the newest one. |
loaded_at, expected_cadence | When the data was loaded, and how often new data is expected. |
freshness_note | On a list, a preview or a row’s history, a sentence to pass on when the data is out of date. |
include | For 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_from | For a dataset built from other datasets: the data version of each it was built from, and whether it’s behind. |
truncated, note | Whether there were more groups than returned, and a note when rows were cut to keep the answer small. |
next_step | While 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.