Developers
Set your API contextPick a region and space ID once. Every sample fills in your base URL.
Where do I find these?

In Coevera: open your space's API application and choose Show Access. It shows the space ID and the base URL, whose host starts with your region.

From the API: if you know the space ID, the discovery endpoint GET /api/discovery/spaces/{space_id} returns its region and base URLs, without credentials.

Only the region and space ID are kept, in this browser. Never paste a key or password here.

More in the quickstart

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.

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
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 } }
      }
    }
  }
}

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 } } } } } }"
}
JSON
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));
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))
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "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"
    }
  ]
}
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "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, 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 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.

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
Custom fields and entities Not in the builder Use your space's own spec
Verified on 2026-10-15 against API 6.5Behaviour-verified

Every sample on this page ran against the docs sandbox and returned the response shown.