Read a list
data.view-source.query@1
Reads the rows of one table, a page at a time, with the fields, filters, search and sort order you ask for.
Call it
Send POST /invocations with the target this operation's catalogue entry gives: replace YOUR_FEATURE_ID with its featureId, and YOUR_TABLE_REF with a table's resourceRef from List data tables. This one reads the first two quote requests whose email is at example.com, by name.
# A new operationId for each new request; the same one to retry it.
curl https://api.growlocal.site/api/v1/profiles/YOUR_BUSINESS_ID/invocations \
-H "Authorization: Bearer gl_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"target": {
"featureId": "YOUR_FEATURE_ID",
"export": "queryRows",
"contractRef": "data.view-source.query@1"
},
"operationId": "read-quote-requests-1",
"input": {
"resourceRef": "YOUR_TABLE_REF",
"query": {
"columns": [
"f_name",
"f_email"
],
"filters": [
{
"field": "f_email",
"op": "contains",
"value": "@example.com"
}
],
"sort": [
{
"field": "f_name",
"direction": "asc"
}
],
"pageSize": 2
}
}
}'
// A new operationId for each new request; the same one to retry it.
const response = await fetch(
"https://api.growlocal.site/api/v1/profiles/YOUR_BUSINESS_ID/invocations",
{
method: "POST",
headers: {
Authorization: "Bearer gl_YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
"target": {
"featureId": "YOUR_FEATURE_ID",
"export": "queryRows",
"contractRef": "data.view-source.query@1"
},
"operationId": "read-quote-requests-1",
"input": {
"resourceRef": "YOUR_TABLE_REF",
"query": {
"columns": [
"f_name",
"f_email"
],
"filters": [
{
"field": "f_email",
"op": "contains",
"value": "@example.com"
}
],
"sort": [
{
"field": "f_name",
"direction": "asc"
}
],
"pageSize": 2
}
}
}),
},
);
const receipt = await response.json();
console.log(response.status, receipt);
import requests
# A new operationId for each new request; the same one to retry it.
response = requests.post(
"https://api.growlocal.site/api/v1/profiles/YOUR_BUSINESS_ID/invocations",
headers={"Authorization": "Bearer gl_YOUR_API_KEY"},
json={
"target": {
"featureId": "YOUR_FEATURE_ID",
"export": "queryRows",
"contractRef": "data.view-source.query@1",
},
"operationId": "read-quote-requests-1",
"input": {
"resourceRef": "YOUR_TABLE_REF",
"query": {
"columns": [
"f_name",
"f_email",
],
"filters": [
{
"field": "f_email",
"op": "contains",
"value": "@example.com",
},
],
"sort": [
{
"field": "f_name",
"direction": "asc",
},
],
"pageSize": 2,
},
},
},
timeout=30,
)
print(response.status_code, response.json())
Parameters
What goes in input:
| Name | Type | Required | Description |
|---|---|---|---|
resourceRef |
string | Yes | The table to read: its resourceRef from List data tables. |
query |
object | Yes | What to read, and how much of it. |
query.metadata |
object | No | Narrows the rows to those that came in a certain way or at a certain time. These are facts GrowLocal records about each row, never what a visitor typed. |
query.metadata.origin |
object | No | Only rows that came in through one form on one site. |
query.metadata.origin.siteId |
string (uuid) | Yes | The site the rows came in through. |
query.metadata.origin.featureId |
string | Yes | The form they came in through, as a row's metadata.origin.featureId names it. |
query.metadata.createdAt |
object | No | Only rows that came in during a period. |
query.metadata.createdAt.gte |
string (date-time) | No | From this moment on, inclusive. |
query.metadata.createdAt.lt |
string (date-time) | No | Before this moment. |
query.columns |
array of string | No | The fields each row carries, by id, at most 256. Leave it out for every field this key can read. |
query.filters |
array of object | No | Up to 16 filters, all of which a row must match. Which operators a field takes is its query.filters in List data tables. |
query.filters[].field |
string | Yes | The field to compare, by id. |
query.filters[].op |
one of eq, in, not-in, contains, gt, gte, lt, lte, empty, present |
Yes | How to compare: eq equals value; in and not-in compare with values (not-in also matches an empty field); contains finds value as text; gt, gte, lt and lte compare numbers or dates; empty and present need no value. |
query.filters[].value |
string, number, boolean, object or array | Sometimes | What eq, contains, gt, gte, lt and lte compare with. Never null: match a missing value with empty. |
query.filters[].values |
array of string, number, boolean, object or array | Sometimes | What in and not-in compare with, at most 64 values. |
query.search |
object | No | Finds rows by text, ignoring case. |
query.search.text |
string | Yes | The text to find, 1 to 200 characters. |
query.search.columns |
array of string | No | The fields to search, by id. Leave it out to search every field this key can read that allows it. |
query.sort |
array of object | No | Up to 3 fields to sort by, in order. Rows with no value sort last either way. |
query.sort[].field |
string | Yes | The field to sort by, by id. |
query.sort[].direction |
one of asc, desc |
Yes | asc or desc. |
query.tieBreak |
one of oldest-first, newest-first |
No | The order of rows that sort the same, and of every row when there is no sort: by when they were added, oldest-first unless you ask for newest-first. |
query.pageSize |
integer | Yes | How many rows a page holds, 1 to 200. |
query.cursor |
string | No | The nextCursor of the page before, to read the next one (Pagination). Leave it out for the first page. |
query.archived |
boolean | No | true reads the table's archived rows instead of its current ones. |
query.trashed |
boolean | No | true reads the rows in Trash instead. Not together with archived. |
recordRef |
object | No | Narrows the page to one row: its recordRef as a page of this table gives it. |
recordRef.businessProfileId |
string | Yes | Your business's id. |
recordRef.featureId |
string | Yes | The featureId of the row's recordRef. |
recordRef.collectionId |
string | Yes | The collectionId of the row's recordRef. |
recordRef.id |
string | Yes | The row's id. |
Response
A call that worked answers 200 with an applied receipt; its output holds these fields, among others. GET /catalog/data.view-source.query@1 answers the complete shape as JSON Schema.
| Name | Type | Description |
|---|---|---|
rows |
array of object | The page's rows, at most pageSize. |
rows[].metadata |
object | Facts GrowLocal records about the row. |
rows[].metadata.createdAt |
string (date-time) | When the row was added. |
rows[].metadata.origin |
object or null | The form and site the row came in through, or null when it was added another way. |
rows[].recordRef |
object | Names the row: send it as row to Change items. |
rows[].revision |
integer | The row's revision. Changing it needs this as expectedRevision. |
rows[].values |
object | The row's values, keyed by field id. |
rows[].fields |
array of string | The fields this row carries, by id. |
rows[].allowedTransitions |
array of string | For a table with a status, the statuses this row can move to next. |
rows[].trash |
object | On a trashed: true page: when the row was deleted and when it will be removed for good. |
nextCursor |
null or string | Send it as query.cursor for the next page; null when this is the last. |
{
"status": "applied",
"operationId": "read-quote-requests-1",
"output": {
"contract": "data.view-page/1",
"schemaRef": "…",
"rows": [
{
"key": "0b6f3c2a-5d1e-4f7a-9c8b-2e4d6f8a1c3e",
"recordRef": {
"businessProfileId": "YOUR_BUSINESS_ID",
"featureId": "juniper-cakes.data",
"collectionId": "juniper-cakes.quote-requests",
"id": "0b6f3c2a-5d1e-4f7a-9c8b-2e4d6f8a1c3e"
},
"revision": 1,
"metadata": {
"createdAt": "2026-11-06T01:57:31.090547+00:00",
"origin": null
},
"values": {
"f_name": "Jordan Example",
"f_email": "jordan@example.com"
},
"fields": [
"f_email",
"f_name"
],
"allowedTransitions": [
"quoted"
]
},
{
"key": "7c1e9a4b-3f2d-4e6a-8b5c-9d0e1f2a3b4c",
"recordRef": {
"businessProfileId": "YOUR_BUSINESS_ID",
"featureId": "juniper-cakes.data",
"collectionId": "juniper-cakes.quote-requests",
"id": "7c1e9a4b-3f2d-4e6a-8b5c-9d0e1f2a3b4c"
},
"revision": 1,
"metadata": {
"createdAt": "2026-10-07T20:44:46.675444+00:00",
"origin": {
"siteId": null,
"featureId": "juniper-cakes.quote-requests-form"
}
},
"values": {
"f_name": "Omar Reyes",
"f_email": "omar@example.com"
},
"fields": [
"f_email",
"f_name"
],
"allowedTransitions": [
"quoted"
]
}
],
"nextCursor": "eyJkIjoiNGYxNzA3NzA0YzU2NjNjZDgxNWI0ZTdlMmIyOGUyZmYiLCJrIjpbIk9tYXIgUmV5ZXMiXX0"
}
}
Errors
It reads and changes nothing. Besides the answers every request can get (Errors), its receipt can say:
| Status | HTTP | What it means |
|---|---|---|
rejected |
400, or 403 when it's a matter of access | Not done. diagnostics says why: each has a code, a path into what you sent and a message. |
The codes in diagnostics you're most likely to see:
| Code | Status | What it means |
|---|---|---|
DATA.RESOURCE_NOT_FOUND |
rejected |
No such table here for this key. |
DATA.CURSOR_INVALID |
rejected |
The cursor isn't one, or belongs to another query (Pagination). |
CONTRACT.INPUT_INVALID |
rejected |
The query doesn't fit: message names the field, for example a pageSize over 200. |