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.