data_rows
Read rows. WITH id = the single row (by _id). WITHOUT id = one PAGE
of rows (filter/q narrow it, order_by sorts it) with a stable cursor.
Read with empties="sentinel" before re-sending a list: an assumed empty
(written @empty, its reason in comment) then comes back "@empty" — an
ordinary "" stays "" — and, sent back as is, stays one; sent back as ""
it becomes an ordinary empty, refused on a required field.
⚠️ datastore = the table's NUMBER (ns_id, e.g. 174) — the form to use;
the reply carries it back. A name still resolves until 2026-11-08, then
is refused. slot:<name> also works.
⚠️ List mode returns {rows, count, next_cursor, ns_id}. When next_cursor
is not null there are MORE rows: call again with cursor=<next_cursor>
(same datastore/filter/order) — repeat until next_cursor is null. A page
is not the table.
versions picks which VERSIONS of each cell you get. By default, the current
value only; the origin (the FIRST version of the data) is asked for with
versions=["current","origine"]: current is what we established,
origine what the client handed over.
⚠️ Ask for BOTH in ONE call when you compare them — two calls are not atomic,
and an write in between would make you compare the before of one state with
the after of another.
Layers come back FLAT by default (champ.origine beside the bare name);
layers="nested" returns the shape you write — guide datastore-semantics.
The bare name ALWAYS carries the current version; versions only decides
what is added beside it — at any depth: without origine, a list item
carries no item["email.origine"] either. The reply states what it served in
versions_servies, so "I did not ask for it" never looks like "this cell
has none".
The REST face GET …/rows pages by offset with a total, no cursor.
Without order_by the cursor is keyset-stable (rows created meanwhile don't
shift the paging). With order_by it pages by offset instead, since an
arbitrary sort has no stable keyset — so a row inserted mid-walk can shift the
remaining pages. q without order_by ranks the rows (best match first), so
it pages by offset too. Keep the SAME order_by and q across a walk:
passing a cursor from one regime into the other is rejected rather than
silently mispaged.
Use count_only=True to get just the TOTAL number of (optionally filtered)
rows — computed server-side, no rows returned — when you only need the count
(e.g. how many leads match a filter) without pulling the data into context.
Use fields to PROJECT a subset of columns when the full row is heavy and you
only need a few (e.g. name + email + score over a large vivier): each row is
trimmed to those columns (plus _id, always kept so you can still update the
row), drastically shrinking the payload. Bump limit when projecting — narrow
rows let you pull far more per page.
Every column DECLARED in the schema is on each row, null when no value is in
place ("nothing here yet", not "no such column") — including in a projection.
Tool call template
Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.
{
"id": 1,
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"arguments": {
"datastore": "<datastore>"
},
"name": "data_rows"
}
}Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| q | No | free-text search over the row. Every WORD of `q` must be found, in any order, either by meaning (French stemming, accents and case folded: `cahiers` finds "Le Cahier", `corp acme` finds "ACME Corp") or as a literal FRAGMENT (a piece of a SIREN, an URL or a reference pasted as is). Without an explicit sort, best matches come first (rows matched by meaning, then rows matched only by fragment); an explicit sort stays yours. Where to search is `q_scope`. The way to find a row when you don't know WHICH column holds the word. Combines with `filter` (AND). (list mode only) | |
| id | No | `_id` of one row ; omit = list rows. | |
| _org | No | Org (id) de CET APPEL. `oto_list_orgs()`. | |
| limit | No | page size (default 100, list mode only). | |
| _group | No | Équipe (id) de CET APPEL, sous son org parente. | |
| cursor | No | opaque `next_cursor` from a previous call = fetch the NEXT page. | |
| fields | No | list of column names to keep (projection) — the returned rows carry only these plus `_id`; a declared one with no value comes back `null`. Omit = full rows. | |
| filter | No | dict `{column: value}` — exact match. A column may instead take ONE operator: `{"posted_at": {"gte": "2026-06-01"}}`, `{"author": {"contains": "sylvie"}}`, `{"status": {"ne": "processed"}}`, `{"idcc": {"in": ["573", "86"]}}`, `{"email": {"not_empty": true}}`. Ops: eq, ne, contains, in, gt, gte, lt, lte, empty, not_empty. The system columns are filterable too — `_updated_at`/`_created_at` (ops eq/ne/gt/gte/lt/lte; a plain `YYYY-MM-DD` means that WHOLE day, so `{"_updated_at": {"gte": "2026-08-01"}}` = touched since the 1st) and `_id`. Filtering happens in SQL — never pull the whole table to filter it yourself. (list mode only) | |
| layers | No | shape of a cell that carries layers (`origine`/`comment`/`link`). ⚠️ This is a READ: do NOT send the row back. Write only the fields you established — and never `<field>.origine`, which is read here and set by the platform, never by an agent. You WRITE nested (`{"valeur": …, "comment": …}`) and, by default, read back FLAT — this parameter lifts that asymmetry. `flat` (default): `row["email"]` is the value, and each filled layer sits BESIDE it as `row["email.origine"]`. `nested`: `row["email"]` is `{"valeur": …, "origine": …, "comment": …, "link": …}` — `valeur` always, the other keys only when filled — i.e. the shape you WRITE with `data_write`. A cell without layers is the same plain value in both shapes. Any other value is refused. With `nested`, `fields` names columns (a nested cell keeps its layers); `email.origine` as a field name only exists in `flat`. The default WILL switch to `nested`, with dated notice: pass `layers` explicitly if you depend on one shape. | flat |
| _run_id | No | OBLIGATOIRE à chaque appel dès `run_start` ou `data_claim_next` : sans lui, l'écriture sur une ligne réservée est refusée. Hérité seulement si le serveur tient un run actif — n'y compte pas. | |
| empties | No | how an assumed empty (written `@empty`) comes back. `plain` (default): `""`, like any empty cell. `sentinel`: `"@empty"`, the word that writes it — top level, nested (`{"valeur": "@empty", …}`) and inside list elements alike. It is not a value: never copy it into a deliverable. Any other value is refused. | plain |
| filters | No | list of clauses, for what `filter` cannot express — ONE clause may target SEVERAL columns at once. A single notion often lives on numbered columns (`contact1_fonction`, `contact2_fonction`…): ask about all of them in one go by NAMING them. `[{"fields": ["contact1_fonction", "contact2_fonction", "contact3_fonction"], "op": "in", "value": ["DRH", "DAF"]}]` = rows where ANY of those three holds an HR/finance role, whichever rank carries it. `match` picks the sense: `any` (default, one column is enough) or `all` (every listed column) — `all` + `empty` is how you get "rows with NO contact at all", which is NOT the negation of the first. A clause may also name a single column (`{"field": …}`), and a `champ.origine`/`.comment`/`.link` suffix targets that layer. Clauses combine with AND, and with `filter`. (list mode only) | |
| q_scope | No | where `q` searches: `values` = only the values a reader is served (no column names, no layers — `origine`/`comment`/`link` — no JSON quoting); `all` = the whole stored row, names and layers included (finds a row by its `comment`). Default `all`. Any other value is refused. | |
| _project | No | Projet (id) de CET APPEL — jeton PRIMAIRE. `oto_project(op='list')`. | |
| order_by | No | sort column — a user field, or a system one (`_created_at`, `_updated_at`, `_id`). Omit = creation order. Sorting in SQL is how you get "the 10 most recent" or "the top scores" without pulling the table and sorting it yourself. With `q` and no `order_by`, best matches come first. (list mode only) Sorting honors the DECLARED type of the column: a `number` sorts numerically (never "10 < 2"), an `enum` sorts in its declared option order, a `date` chronologically. Values that don't fit the type (junk in a number column, a value outside the enum's options) go to the TAIL in both directions, alphabetically; empty cells go last of all. When that happens the response carries `order_health: {off_type, empty}` — counts over the whole filtered set, absent when everything conforms. | |
| versions | No | ||
| datastore | Yes | target datastore, or `slot:<name>` = the table bound under that slot name by the ACTIVE project (actionable error if unbound). | |
| order_dir | No | `desc` (default) or `asc`. Meaningful with `order_by`; with `q` alone, it orders rows of EQUAL rank by creation. | desc |
| count_only | No | return only `{total}` (filtered row count), no rows. |