---
title: Querying and the query builder
description: Narrow a list with filter and filter-op, order it with order-by, and return only the fields you need with load-only and expand. Build the request below, in REST and GraphQL.
canonical: https://developers.coevera.com/concepts/querying/
section: concepts
verification: behaviour
lastVerified: 2026-10-15
publisher: Coevera
---

# Querying and the query builder

**Answer:** REST lists take filter[field]=value with filter-op[field]=operator (eq by default), order-by (a leading - sorts descending), load-only and expand. A raw comma separates OR values; %2C is a literal comma. A repeated parameter is refused with HTTP 400, code 117. GraphQL takes the same conditions as a filter object on getByCriteria.

Every list request, `GET /entities/{Entity}`, takes the same parameters to filter, sort and trim
the records; each entity's page in the [REST reference](/reference/rest/), such as
[list accounts](/reference/rest/accounts/#list), lists them with its fields. Build a query with
the builder, or read on for the rules it follows.

## Build a query

**Query builder** (interactive on the web page): pick an entity (Accounts, Contacts, Leads, Opportunities, Products, Quotes, Projects, Tasks, Appointments, Notes, Clients), add filters, a sort, a page size and the fields to return, and it writes the REST request and the same query in GraphQL. It makes no API call. Its default query for Accounts:

```text
https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Accounts?filter[name]=north&filter-op[name]=icontains&filter[city]=Seattle&filter[modified]=2026-10-01&filter-op[modified]=gte&order-by=-modified&load-only=id,name,city,modified&first=30
```

The same query in GraphQL, sent as POST to `https://{region}.api.coevera.com/api/v100/app/space/{space_id}/graphql/public`:

```graphql
query {
  entities {
    account {
      getByCriteria(
        first: 30
        filter: {
          AND: [
            { name: { icontains: ["north"] } }
            { city: { eq: ["Seattle"] } }
            { modified: { gte: "2026-10-01" } }
          ]
        }
        orderBy: [{ modified: Desc }]
      ) {
        totalCount
        pageInfo { hasNextPage endCursor }
        edges { node { id name city modified } }
      }
    }
  }
}
```

Filterable fields of Accounts in the builder (REST name, type; GraphQL name where GraphQL can filter it): `account_class` option, `accountClass`; `account_type_id` ID, `accountTypeId`; `address` text, `address`; `city` text, `city`; `comments` text, `comments`; `country` text, `country`; `created` date-time, `created`; `currency_exchange_rates_list_id` ID, `currencyExchangeRatesListId`; `customer_type_id` ID, `customerTypeId`; `email1` text, `email1`; `email2` text, `email2`; `email3` text, `email3`; `email4` text, `email4`; `email5` text, `email5`; `health_status` number, `healthStatus`; `home_page` text, `homePage`; `id` ID, `id`; `industry_id` ID, `industryId`; `is_archived` true or false, `isArchived`; `is_deleted` true or false, `isDeleted`; `last_ai_account_hierarchy_applied` date-time, `lastAiAccountHierarchyApplied`; `last_ai_org_chart_applied` date-time, `lastAiOrgChartApplied`; `modified` date-time, `modified`; `name` text, `name`; `owner_id` ID, `ownerId`; `parent_account_id` ID, `parentAccountId`; `parent_account_relation_type_id` ID, `parentAccountRelationTypeId`; `phone1` text, `phone1`; `phone2` text, `phone2`; `phone3` text, `phone3`; `phone4` text, `phone4`; `phone5` text, `phone5`; `picture_id` ID, `pictureId`; `quick_parent_account_name` text, `quickParentAccountName`; `revision` number, `revision`; `share_mode` option, `shareMode`; `state_province` text, `stateProvince`; `unit_id` ID, `unitId`; `zip_code` text, `zipCode`.

- Operators for option fields: `eq`, `not-eq`
- Operators for ID fields: `eq`, `not-eq`, `null`, `not-null`
- Operators for text fields: `eq`, `ieq`, `not-eq`, `not-ieq`, `contains`, `icontains`, `starts`, `istarts`, `ends`, `iends`, `null`, `not-null`, `empty`, `not-empty`
- Operators for date-time fields: `eq`, `not-eq`, `gt`, `gte`, `lt`, `lte`, `between`, `null`, `not-null`
- Operators for number fields: `eq`, `not-eq`, `gt`, `gte`, `lt`, `lte`, `between`, `null`, `not-null`
- Operators for true or false fields: `eq`

The builder offers the standard entities and their fields from the published API spec. Your own
custom fields and custom entities are not in it: they are in your space's own spec
([data model](/concepts/data-model/#your-own-fields-and-entities)).

## A query in code

This query lists the accounts in Seattle or Portland changed since 2026-01-01, newest change
first, with four fields each:

**REST · cURL**

```bash
COEVERA_BASE="https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}"

# -d sends each value as written, so the comma in filter[city] stays raw: Seattle OR Portland.
curl -sS --fail-with-body -G "$COEVERA_BASE/entities/Accounts" \
  -d "filter[city]=Seattle,Portland" \
  -d "filter[modified]=2026-01-01" \
  -d "filter-op[modified]=gte" \
  -d order-by=-modified \
  -d load-only=id,name,city,modified \
  -d first=10 \
  -u "$COEVERA_API_KEY:$COEVERA_API_SECRET"
```

**REST · TypeScript**

```ts
import { Buffer } from "node:buffer";
import { env } from "node:process";

const base = `https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}`;
const auth = Buffer.from(`${env.COEVERA_API_KEY}:${env.COEVERA_API_SECRET}`).toString("base64");

// Each OR value is encoded on its own and the values are joined with a raw comma. Do not build
// this with URLSearchParams: it sends the comma as %2C, which is a literal comma.
const cities = ["Seattle", "Portland"].map(encodeURIComponent).join(",");
const query = [
  `filter[city]=${cities}`,
  "filter[modified]=2026-01-01",
  "filter-op[modified]=gte",
  "order-by=-modified",
  "load-only=id,name,city,modified",
  "first=10",
].join("&");
const res = await fetch(`${base}/entities/Accounts?${query}`, { headers: { Authorization: `Basic ${auth}` } });
const body = await res.json();
if (!res.ok) throw new Error(`HTTP ${res.status} ${body.name}: ${body.message}`);
console.log(`${body.total} matching accounts:`, JSON.stringify(body.data, null, 2));
```

**REST · Python**

```python
import json
import os
import sys
from urllib.parse import quote

import requests

BASE = "https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}"
session = requests.Session()
session.auth = (os.environ["COEVERA_API_KEY"], os.environ["COEVERA_API_SECRET"])

# Each OR value is encoded on its own and the values are joined with a raw comma. Do not pass
# the filter in params=: requests sends the comma as %2C, which is a literal comma.
cities = ",".join(quote(city, safe="") for city in ["Seattle", "Portland"])
query = "&".join([
    f"filter[city]={cities}",
    "filter[modified]=2026-01-01",
    "filter-op[modified]=gte",
    "order-by=-modified",
    "load-only=id,name,city,modified",
    "first=10",
])
response = session.get(f"{BASE}/entities/Accounts?{query}", timeout=30)
body = response.json()
if not response.ok:
    sys.exit(f"HTTP {response.status_code} {body.get('name')}: {body.get('message')}")
print(body.get("total"), "matching accounts:", json.dumps(body["data"], indent=2))
```

**GraphQL · cURL**

```bash
COEVERA_GRAPHQL="https://{region}.api.coevera.com/api/v100/app/space/{space_id}/graphql/public"

curl -sS --fail-with-body "$COEVERA_GRAPHQL" \
  -u "$COEVERA_API_KEY:$COEVERA_API_SECRET" \
  -H "Content-Type: application/json" \
  -d @- <<'JSON'
{
  "query": "{ entities { account { getByCriteria(first: 10, filter: { AND: [{ city: { eq: [\"Seattle\", \"Portland\"] } }, { modified: { gte: \"2026-01-01T00:00:00\" } }] }, orderBy: [{ modified: Desc }]) { totalCount edges { node { id name city modified } } } } } }"
}
JSON
```

**GraphQL · TypeScript**

```ts
import { Buffer } from "node:buffer";
import { env } from "node:process";

const endpoint = `https://{region}.api.coevera.com/api/v100/app/space/{space_id}/graphql/public`;
const auth = Buffer.from(`${env.COEVERA_API_KEY}:${env.COEVERA_API_SECRET}`).toString("base64");

const ACCOUNTS = `{
  entities {
    account {
      getByCriteria(
        first: 10
        filter: {
          AND: [
            { city: { eq: ["Seattle", "Portland"] } }
            { modified: { gte: "2026-01-01T00:00:00" } }
          ]
        }
        orderBy: [{ modified: Desc }]
      ) {
        totalCount
        edges { node { id name city modified } }
      }
    }
  }
}`;
const res = await fetch(endpoint, {
  method: "POST",
  headers: { Authorization: `Basic ${auth}`, "Content-Type": "application/json" },
  body: JSON.stringify({ query: ACCOUNTS }),
});
const body = await res.json();
// GraphQL reports errors in the body, often with HTTP 200: check both.
if (!res.ok || body.errors) throw new Error(`HTTP ${res.status}: ${JSON.stringify(body.errors ?? body)}`);
const accounts = body.data.entities.account.getByCriteria;
console.log(`${accounts.totalCount} matching accounts:`, JSON.stringify(accounts.edges, null, 2));
```

**GraphQL · Python**

```python
import json
import os
import sys

import requests

ENDPOINT = (
    "https://{region}.api.coevera.com/api/v100/app/space/"
    "{space_id}/graphql/public"
)
AUTH = (os.environ["COEVERA_API_KEY"], os.environ["COEVERA_API_SECRET"])

ACCOUNTS = """
{
  entities {
    account {
      getByCriteria(
        first: 10
        filter: {
          AND: [
            { city: { eq: ["Seattle", "Portland"] } }
            { modified: { gte: "2026-01-01T00:00:00" } }
          ]
        }
        orderBy: [{ modified: Desc }]
      ) {
        totalCount
        edges { node { id name city modified } }
      }
    }
  }
}
"""
response = requests.post(ENDPOINT, json={"query": ACCOUNTS}, auth=AUTH, timeout=30)
body = response.json()
# GraphQL reports errors in the body, often with HTTP 200: check both.
if not response.ok or body.get("errors"):
    sys.exit(f"HTTP {response.status_code}: {json.dumps(body.get('errors', body))}")
accounts = body["data"]["entities"]["account"]["getByCriteria"]
print(accounts["totalCount"], "matching accounts:", json.dumps(accounts["edges"], indent=2))
```

**Recorded response · REST · 200 OK**

```json
{
  "success": true,
  "total": 2,
  "page_info": {
    "start_cursor": "WyIyMDI2LTEwLTE1VDA1OjA2OjM2Ljk2NTEwOCswMDowMCIsICIwYTAwMDAwMC0wMDAwLTQwMDAtYTAwMC0wMDAwMDAwMDAwMDEiXQ==",
    "end_cursor": "WyIyMDI2LTEwLTE1VDA1OjA2OjM1LjU4NDMyNiswMDowMCIsICIwYTAwMDAwMC0wMDAwLTQwMDAtYTAwMC0wMDAwMDAwMDAwMDIiXQ==",
    "has_previous_page": false,
    "has_next_page": false
  },
  "data": [
    {
      "id": "0a000000-0000-4000-a000-000000000001",
      "modified": "2026-10-15 09:00:01.380782+00:00",
      "city": "Portland",
      "name": "Wide World Importers"
    },
    {
      "id": "0a000000-0000-4000-a000-000000000002",
      "modified": "2026-10-15 09:00:00.000000+00:00",
      "city": "Seattle",
      "name": "Northwind Analytics"
    }
  ]
}
```

**Recorded response · GraphQL · 200 OK**

```json
{
  "data": {
    "entities": {
      "account": {
        "getByCriteria": {
          "totalCount": 2,
          "edges": [
            {
              "node": {
                "id": "0a000000-0000-4000-a000-000000000001",
                "name": "Wide World Importers",
                "city": "Portland",
                "modified": "2026-10-15T09:00:01.380782+00:00"
              }
            },
            {
              "node": {
                "id": "0a000000-0000-4000-a000-000000000002",
                "name": "Northwind Analytics",
                "city": "Seattle",
                "modified": "2026-10-15T09:00:00.000000+00:00"
              }
            }
          ]
        }
      }
    }
  }
}
```

_Recorded on 2026-10-15 against API 6.5.2 in the docs sandbox, de-identified._

## Filter

- `filter[<field>]=<value>` keeps the records whose field matches; `filter-op[<field>]` names the
  operator, `eq` when you leave it out. Several filters must all match.
- A comma-separated value is an OR for the list operators: `filter[city]=Seattle,Portland`
  matches either city. Send a comma that belongs to the value as `%2C`: `filter[city]=Seattle%2CPortland`
  looks for one city called "Seattle,Portland".
- Many HTTP libraries encode every comma as `%2C`, among them `URLSearchParams` and the `params`
  of Python's `requests`, which turns an OR list into one value that matches nothing. Encode each
  value on its own and join them with a raw comma, as the sample does.
- A raw `+` in a query string arrives as a space. Encode values, so `+` is sent as `%2B`.
- Filter on a related record's field with a dotted path: `filter[owner.email]=dana.whitfield@example.com`.
  A path reaches one related record, not that record's relations: `filter[step.pipeline_id]`
  finds the opportunities in one pipeline, while `filter[step.pipeline.name]` is refused with
  HTTP 400, code 117. An opportunity's `primary_account` cannot be filtered on either (code 117):
  `filter[account_relations.account_id]` finds the opportunities linked to an account, primary or
  not.
- Send each parameter once. A repeated key, such as `filter[city]` twice, is refused with HTTP
  400, code 117 `ERROR_PARAMETER_INVALID` ("Parameter filter\[city] is duplicated within the
  query."): put the values in one list instead. An unknown parameter is refused with HTTP 400,
  code 118 `ERROR_PARAMETER_UNKNOWN`.

### Operators

| Operator                                 | Meaning                                                                 | Value                           |
| ---------------------------------------- | ----------------------------------------------------------------------- | ------------------------------- |
| `eq`, `ieq`, `not-eq`, `not-ieq`         | Equal, equal ignoring case, and their negations                         | One or more (OR)                |
| `contains`, `icontains`                  | Contains the value; `icontains` ignores case                            | One or more                     |
| `starts`, `istarts`, `ends`, `iends`     | Starts or ends with the value; the `i` forms ignore case                | One or more                     |
| `gt`, `gte`, `lt`, `lte`                 | Greater or less than, or equal; for numbers, dates, date-times and text | Exactly one                     |
| `between`                                | Inside a range; both ends are included                                  | Two: `filter[revision]=355,356` |
| `null`, `not-null`, `empty`, `not-empty` | Has no value, has one; empty text, non-empty text                       | None                            |

- These are all the operators. Any other, such as `not-contains`, is refused with HTTP 400, code
  117\.
- A second value for `gt`, `gte`, `lt` or `lte` is refused with HTTP 400, code 117, and so is a
  value for `null`, `not-null`, `empty` or `not-empty`.
- A text field that was never set is empty, not null: `filter-op[address]=empty` finds it,
  `filter-op[address]=null` does not.
- Dates and date-times are compared as the [dates page](/concepts/dates-and-time-zones/#send-date-times)
  describes: a value without an offset is UTC.

## Sort

`order-by=created,-modified` sorts by `created` ascending, then by `modified` descending. A
dotted path sorts by a related record's field: `order-by=owner.name`.

## load-only and expand

- `load-only=id,name,owner.id` returns only those fields, which keeps pages small. It accepts a
  raw or an encoded comma.
- `expand=owner` returns the related record itself instead of its URL. With `load-only`, name the
  relation there too, or it is left out: `load-only=id,name,owner&expand=owner` returns the whole
  owner, `load-only=id,name,owner.name&expand=owner` only the owner's name, and
  `load-only=id,name&expand=owner` no owner at all.
- `include-deleted=true` adds soft-deleted records to a list.
- Get-by-ID, `GET /entities/{Entity}/{id}`, takes `expand` and nothing else: `load-only` and
  `include-deleted` there are refused with HTTP 400, code 118. `include-deleted` works on lists
  only.

## The same query in GraphQL

A repository's `getByCriteria` takes the conditions as a `filter` object, `orderBy`, `first`,
`after` and `includeDeleted`:

- Field names are camelCase: `owner_id` is `ownerId`.
- A condition is `{ field: { operator: values } }`. `eq` and the text operators take a list, which
  is an OR; `gt`, `gte`, `lt` and `lte` take one value; `between` two.
- Combine conditions with `AND`, `OR` and `NOT`. There are no `not-…` operators: wrap the
  condition in `NOT`. `not-null` is `{ field: { null: false } }`.
- Option fields take the GraphQL enum value where REST takes a number: REST
  `filter[account_class]=2` is `{ accountClass: { eq: [ClassA] } }`.
- GraphQL selects only the fields you name in `edges { node { … } }`; there is no "every field".
- Not every field REST filters on can be filtered in GraphQL. Opportunities' `closing_date`, for
  example, has no GraphQL filter. The builder says so when it leaves a condition out.

> **MCP filters look different:** The MCP server uses its own filter syntax, and `limit` instead of `first`. See
> [filters and pagination in MCP](/ai/filters-and-pagination/).

## Limits

| Limit                      | Value                                                                          | What happens                                                   |
| -------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------- |
| One key per parameter      | `filter[field]`, `filter-op[field]` once each                                  | A repeated key: HTTP 400, code 117                             |
| Unknown parameters         | Not accepted                                                                   | HTTP 400, code 118                                             |
| OR across fields           | Not in REST                                                                    | REST combines different fields with AND only; GraphQL has `OR` |
| Dotted paths               | One related record deep                                                        | `filter[step.pipeline.name]`: HTTP 400, code 117               |
| Values per operator        | `gt`, `gte`, `lt`, `lte`: one; `between`: two; `null` and `empty` family: none | Otherwise HTTP 400, code 117                                   |
| Page size                  | `first` up to 100                                                              | See [pagination](/concepts/pagination/)                        |
| Custom fields and entities | Not in the builder                                                             | Use your space's own spec                                      |
