ConceptsQuerying and the query builder
Querying and the query builder
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.
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, such as
list accounts, lists them with its fields. Build a query with
the builder, or read on for the rules it follows.
Build a query
The query builder needs JavaScript. Its default query lists Accounts; this is the REST request and the same query in GraphQL.
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=30query {
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 } }
}
}
}
}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).
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:
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"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));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))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 } } } } } }"
}
JSONimport { 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));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)){
"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"
}
]
}{
"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"
}
}
]
}
}
}
}
}Show all 29 linesShow less
Filter
filter[<field>]=<value>keeps the records whose field matches;filter-op[<field>]names the operator,eqwhen you leave it out. Several filters must all match.- A comma-separated value is an OR for the list operators:
filter[city]=Seattle,Portlandmatches either city. Send a comma that belongs to the value as%2C:filter[city]=Seattle%2CPortlandlooks for one city called "Seattle,Portland". - Many HTTP libraries encode every comma as
%2C, among themURLSearchParamsand theparamsof Python'srequests, 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, whilefilter[step.pipeline.name]is refused with HTTP 400, code 117. An opportunity'sprimary_accountcannot 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 117ERROR_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 118ERROR_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,ltorlteis refused with HTTP 400, code 117, and so is a value fornull,not-null,emptyornot-empty. - A text field that was never set is empty, not null:
filter-op[address]=emptyfinds it,filter-op[address]=nulldoes not. - Dates and date-times are compared as the dates page 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.idreturns only those fields, which keeps pages small. It accepts a raw or an encoded comma.expand=ownerreturns the related record itself instead of its URL. Withload-only, name the relation there too, or it is left out:load-only=id,name,owner&expand=ownerreturns the whole owner,load-only=id,name,owner.name&expand=owneronly the owner's name, andload-only=id,name&expand=ownerno owner at all.include-deleted=trueadds soft-deleted records to a list.- Get-by-ID,
GET /entities/{Entity}/{id}, takesexpandand nothing else:load-onlyandinclude-deletedthere are refused with HTTP 400, code 118.include-deletedworks 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_idisownerId. - A condition is
{ field: { operator: values } }.eqand the text operators take a list, which is an OR;gt,gte,ltandltetake one value;betweentwo. - Combine conditions with
AND,ORandNOT. There are nonot-…operators: wrap the condition inNOT.not-nullis{ field: { null: false } }. - Option fields take the GraphQL enum value where REST takes a number: REST
filter[account_class]=2is{ 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.
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.: 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 |
| Custom fields and entities | Not in the builder | Use your space's own spec |