Quickstart
This walk-through goes from a few pasted rows to a saved view that any assistant can answer by name. It uses a small online shop’s orders. Each step shows what you say, the tool your assistant calls, and what Ontyx answers. The answers are real ones, shortened where marked.
You need an owner or admin account and a connected assistant (Connect your assistant). Your assistant chooses the tools itself; you never have to name them.
1. Describe a dataset
Here’s our orders export. Keep it in Ontyx. Each order number is unique, we send the whole export every day, and status is only ever open, shipped or cancelled.
order_id,ordered_on,customer,region,status,amount A-1001,2026-08-04,Birch Cafe,west,shipped,120.00 A-1002,2026-08-19,Harbor Books,east,shipped,85.50 A-1003,2026-09-02,Birch Cafe,west,shipped,240.00 A-1004,2026-09-15,Lumen Studio,west,cancelled,60.00 A-1005,2026-09-28,Harbor Books,east,shipped,310.25 A-1006,2026-10-03,Lumen Studio,west,open,99.99
Your assistant looks at the rows and proposes a structure with dataset_define:
{
"name": "orders",
"description": "Every order from the online shop, one row per order.",
"columns": [
{ "name": "order_id", "type": "string", "nullable": false, "description": "The shop's order number." },
{ "name": "ordered_on", "type": "date", "nullable": false, "description": "The day the order was placed." },
{ "name": "customer", "type": "string" },
{ "name": "region", "type": "string" },
{ "name": "status", "type": "string" },
{ "name": "amount", "type": "decimal", "scale": 2, "description": "Order total in US dollars." }
],
"key": ["order_id"],
"default_load_mode": "upsert",
"expected_cadence": "daily",
"keep_raw": false,
"checks": [{ "kind": "accepted_values", "column": "status", "values": ["open", "shipped", "cancelled"] }]
}Ontyx reads the proposal back in plain words, and your assistant shows it to you:
About: Every order from the online shop, one row per order.
Columns (6):
• order_id — text, identifies each row: The shop's order number.
• ordered_on — date, always filled in: The day the order was placed.
• customer — text
• region — text
• status — text
• amount — exact amount with 2 decimal places: Order total in US dollars.
Each row is identified by: order_id.
Checked on every load: "status" only holds "open", "shipped" or "cancelled" (blanks allowed).
New data: each load updates rows that already exist and adds new ones.
Expected to refresh: every day.
Keeps a copy of each original record: no.Nothing can be loaded yet: the structure is waiting for a person to approve it.
2. Approve it
Yes, that’s right.
Your assistant calls dataset_approve_schema_change with { "dataset": "orders", "schema_version": 1, "confirmed_by_person": true }. It’s only allowed to do that after you say yes in the conversation, and only an owner or admin can approve. Ontyx answers:
Structure version 1 of "orders" is now in force. The dataset has no data yet.You could also approve it on the dashboard’s Approvals page.
3. Load the rows
Your assistant sends the rows you pasted with dataset_load_rows. Every row is checked against the approved structure before anything is written. Ontyx answers (shortened):
{
"dataset": "orders",
"data_version": 1,
"mode": "upsert",
"rows_loaded": 6,
"rows_added": 6,
"total_rows": 6,
"as_of": "2026-10-30T18:00:00.000Z",
"message": "Loaded 6 rows into \"orders\" (each load updates rows that already exist and adds new ones); it now has 6 rows. Checked: Each of the 6 rows of \"orders\" has its own \"order_id\"; this load's rows were matched against the ones already there. All 6 rows in this load have a value in \"ordered_on\". \"status\" is \"open\", \"shipped\" or \"cancelled\" on all 6 rows in this load. Up to date: the data is as of 1 day ago (2026-10-30), and new data is expected every day."
}Each load makes a new data version. If one row had broken a rule, say a status of refunded, nothing would have been loaded and the answer would say why:
Nothing was loaded: Column "status" only allows "open", "shipped" or "cancelled", but 1 row has a value that isn't on that list: "refunded" (1 row). …For a file too big to paste, your assistant gives you an upload link instead (dataset_begin_upload). For data a script produces, use the Push API.
4. Ask a question
What was revenue by month, leaving out cancelled orders?
Your assistant asks dataset_query in parts, never as code:
{
"dataset": "orders",
"where": [{ "column": "status", "op": "ne", "value": "cancelled" }],
"group_by": [{ "column": "ordered_on", "by": "month" }],
"aggregates": [{ "fn": "count" }, { "fn": "sum", "column": "amount", "as": "revenue" }]
}Ontyx answers (shortened):
{
"dataset": "orders",
"data_version": 1,
"schema_version": 1,
"as_of": "2026-10-30T18:00:00.000Z",
"freshness": "fresh",
"filters": [{ "column": "status", "op": "ne", "value": "cancelled", "means": "status isn't \"cancelled\"" }],
"rows": [
{ "ordered_on_month": "2026-08", "count": 2, "revenue": "205.50" },
{ "ordered_on_month": "2026-09", "count": 2, "revenue": "550.25" },
{ "ordered_on_month": "2026-10", "count": 1, "revenue": "99.99" }
],
"message": "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)."
}And your assistant tells you something like: Revenue was $205.50 in August (2 orders), $550.25 in September (2) and $99.99 so far in October (1), leaving out cancelled orders. The orders data is as of October 30, 2026.
5. Save it as a metric
That’s our revenue number. Save it so we always count it this way, by month, and let us split it by region or customer.
Your assistant proposes a metric with metric_propose:
{
"name": "revenue",
"label": "Revenue",
"description": "Money from orders that weren't cancelled.",
"dataset": "orders",
"measure": { "fn": "sum", "column": "amount" },
"filters": [{ "column": "status", "op": "ne", "value": "cancelled" }],
"time": { "column": "ordered_on", "grains": ["month", "quarter", "year"], "default": "month" },
"dimensions": ["region", "customer"]
}Ontyx reads it back as one sentence, written by Ontyx rather than by your assistant, so what you approve is exactly what gets counted:
Revenue: the total amount in "orders", where status isn't "cancelled", by month, quarter or year of ordered_on, split by region or customer.Yes.
Your assistant calls metric_approve and the metric is in force at version 1. From now on, any assistant asks it with metric_query and gets the same number.
6. Save a view
Save a report of revenue by region that we can run for any month.
Your assistant saves a view with view_save:
{
"name": "monthly_revenue",
"description": "Revenue for one month, by region.",
"metrics": [{ "metric": "revenue", "by": ["region"] }],
"time": { "grain": "month" },
"parameters": [{ "name": "month", "kind": "period", "description": "The month to report on." }]
}Ontyx answers with what the view asks:
The view "monthly_revenue" asks 1 metric by month, for the month given as "month": Revenue (metric "revenue"), split by region.7. Ask for it from a fresh conversation
Open a new conversation, in the same assistant or a different one, as anyone in the organization (a reader too):
Run monthly revenue for September 2026.
The assistant calls view_query with { "view": "monthly_revenue", "parameters": { "month": "2026-09" } }. Ontyx answers each metric exactly as metric_query would (shortened):
{
"view": "monthly_revenue",
"parameters": { "month": "2026-09" },
"metrics": [{ "metric": "revenue", "metric_version": 1, "saved_with_metric_version": 1, "answered": true }],
"answers": [
{
"metric": "revenue",
"metric_version": 1,
"dataset": "orders",
"data_version": 1,
"as_of": "2026-10-30T18:00:00.000Z",
"freshness": "fresh",
"rows": [
{ "ordered_on_month": "2026-09", "region": "east", "revenue": "310.25" },
{ "ordered_on_month": "2026-09", "region": "west", "revenue": "240.00" }
]
}
],
"message": "The view \"monthly_revenue\", for month 2026-09, answered 1 metric: Revenue (metric \"revenue\", version 1). Each answer gives its rows, every filter applied and the date its data is as of."
}September 2026 revenue was $310.25 in the east and $240.00 in the west (metric “revenue”, version 1), from the orders data as of October 30, 2026. Ask the same thing tomorrow, or from ChatGPT instead of Claude, and the answer is the same until new data arrives.
Where to go next
- Send new data every day. The next load updates the orders that changed and adds new ones. A script can do it with the Push API.
- Follow a record. “How did order A-1006 change?” answers with each version of that row and who loaded it. See Lineage and history.
- Understand every field of an answer. See Asking questions.
- Look at it all in a browser. The Dashboard shows your datasets, what’s waiting for approval, your metrics and views.