OQL

Limits and errors

OQL hard limits, the row cap, the timeout, and the error categories you can expect.

Last updated Jul 16, 2026

OQL has a small set of hard limits, enforced before a query runs. When your query exceeds one, you get a typed error message instead of a partial or truncated result.

Limits

LimitValueNotes
Query timeoutLong-running queries are cut offServer-enforced; returns ExecutionError
Max limit1000 rowsValues above are rejected, not clamped
Max group_by fields3Duplicate fields are rejected
like wildcards per pattern3% and _ both count
like pattern length100 charactersNull bytes are rejected
DAYS_AGO argument±36500Roughly ±100 years
Distinct value set16 MB per groupDistinct aggregates and distinct: true collect unique values in memory (roughly 700k IDs)

Why limits are strict, not soft

A silently truncated result looks complete, so anything you compute from it is quietly wrong. OQL never does that. An over-limit query raises a validation error, and hitting the row cap sets truncated: true on the result:

{
  "rows": [ /* up to your limit */ ],
  "row_count": 100,
  "truncated": true
}

truncated: true means more rows exist beyond the requested limit. Raise limit (up to 1000) or refine where.

Error categories

OQL raises two kinds of error.

ValidationError

The query is malformed or references something that does not exist. Caught before the query runs. Examples:

Unknown entity 'widgets'
Unknown field 'foo' on entity 'contacts'
Field 'background' is not filterable
Field 'first_name' is not sortable
Operator '<' is not compatible with type 'string' on field 'first_name'
'limit' must be <= 1000, got 5000
'like' pattern has 5 wildcards, max is 3
'group_by' has 4 fields, max is 3
Invalid date '2026-13-99' for 'created_at' (expected ISO 8601)
'between' value must be a 2-element array
Multi-operator condition on field 'amount' is not supported

Reference resolution failures are also ValidationErrors and point you to where to look up valid values:

Invalid status_id 'foo'. Valid values: lead, prospect, customer, ...
Invalid owner_id '507f...'. Must be a valid user ID.
Invalid pipeline_id '507f...'. Use context() to see valid pipelines.

ExecutionError

The query was well-formed but failed at runtime. Almost always a timeout:

Query timed out (10000ms limit)

Timeouts usually indicate a where that returns far too many rows before filtering kicks in. Add a more selective filter (a date range, an owner_id, a status), or reduce the scope by querying a narrower entity.

Grouping or distinct-collecting over too many unique values can also exceed the aggregation memory limit:

Query exceeded the aggregation memory limit. Narrow it with a where
filter or fewer groups before aggregating.

For high-cardinality fields like id or contact_id over large collections, prefer count() (a plain row count) over count(distinct id), or scope the query with where first.

Reducing the chance of timeouts

  • Filter on selective fields first. owner_id, assignee_id, contact_id, status, status_id, pipeline_id, and the timestamp fields (created_at, modified_at, close_date, date) narrow the result set quickly and are good starting points.
  • Prefer = and in over like for known values.
  • Use date functions (THIS_QUARTER(), DAYS_AGO) to bound time ranges; running over an unbounded history is the most common source of slow queries.
  • When aggregating, narrow with where first. group_by works on whatever survives the filter.