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
| Limit | Value | Notes |
|---|---|---|
| Query timeout | Long-running queries are cut off | Server-enforced; returns ExecutionError |
Max limit | 1000 rows | Values above are rejected, not clamped |
Max group_by fields | 3 | Duplicate fields are rejected |
like wildcards per pattern | 3 | % and _ both count |
like pattern length | 100 characters | Null bytes are rejected |
DAYS_AGO argument | ±36500 | Roughly ±100 years |
| Distinct value set | 16 MB per group | Distinct 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
=andinoverlikefor 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
wherefirst.group_byworks on whatever survives the filter.