Every Personyze REST endpoint shares a common path-parameter syntax for filtering, projecting columns, ordering, and limiting results. The same modifiers — where, columns, column, order_by, order_by_desc, limit — work consistently across all objects, with one shortcut form for primary-key lookups.
Prerequisites: REST API Overview and Authentication — that’s where the URL shape and credentials are explained.
General URL shape
After the object name, the URL path is a sequence of /<modifier>/<value>/... pairs:
/rest/<object>/[<id>|]<modifier>/<value>/<modifier>/<value>/...
Notes:
- Order doesn’t matter.
/limit/100/where/x=yis equivalent to/where/x=y/limit/100. - Each modifier appears at most once per request. Multiple
whereconditions go inside a singlewhere/segment using&and|operators (see below). - Values are URL-decoded before parsing — so a column value containing
/,=,&, or%must be percent-encoded.
Single-id form (primary-key lookup)
A bare numeric segment in the first position is a primary-key lookup:
GET /rest/<object>/<id>
This returns one record (an object, not a one-element array), or 400 Row not found if the ID doesn’t exist.
GET /rest/placeholders/14
GET /rest/users/57291 # PK = user_id
For lookups by external identifier (your SKU, CRM ID, etc.), use the where form with internal_id:
GET /rest/users/where/internal_id=42
GET /rest/products/where/internal_id=SKU-1234
where — filtering
Filters the result set. The simplest form is where/<column><op><value>:
| Operator | Meaning | Example |
|---|---|---|
= |
equals | where/status=active |
!= |
not equals | where/status!=draft |
> |
greater than | where/last_session_time>1700000000 |
>= |
greater or equal | where/age_from>=18 |
< |
less than | where/time<1700000000 |
<= |
less or equal | where/age_to<=65 |
: |
in (comma list) | where/id:1,2,3,4 |
!: |
not in | where/status!:draft,archived |
Combining conditions with & (AND) and | (OR)
where/<cond1>&<cond2> # both must match (AND)
where/<cond1>|<cond2> # either matches (OR)
where/<a>&<b>|<c>&<d> # (a AND b) OR (c AND d) — & binds tighter than |
Examples:
where/status=active&age_from>=18
where/email=alice@example.com|email=bob@example.com
where/category:books,tools&is_in_stock=yes
Indexed-column requirement
REST endpoints that wrap large tables (users, events, sessions_archive, summary_actions, etc.) reject queries whose where or order_by doesn’t ride an index. The error message names the indexed columns the endpoint exposes:
Cannot do this operation on whole table: column "uset_id" is not indexed.
Please, use "where" condition or order by an indexed column
(user_id, last_session_time, data_last_modified, fb_id, email, internal_id).
Composite indexes appear in [col1, col2, col3] form. Only the leading column of a composite is usable as a single-column filter:
... or order by an indexed column
(user_id, [product_internal_id, user_id, transaction_time], status, time).
Here, where/product_internal_id=... rides the composite index, but where/transaction_time>... alone does not — it’s the third column of the composite, useful only when combined with product_internal_id and user_id.
columns — projecting specific columns
Restrict the projected columns. Comma-separated list. Default is “all documented columns” for the object.
GET /rest/users/where/internal_id=42/columns/user_id,first_name,last_name
Unknown columns: for objects that support dynamic column names (users, articles, products), unknown columns are returned as null. For objects with fixed schemas, you’ll get 400 Unknown column.
column (singular) — flat array of values
Shorthand for “select one column, return a flat array of values” instead of an array of objects.
GET /rest/users/where/last_session_time>1700000000/column/email
→ ["alice@example.com", "bob@example.com", ...]
Versus the plural columns form:
GET /rest/users/where/last_session_time>1700000000/columns/email
→ [{"email":"alice@example.com"}, {"email":"bob@example.com"}, ...]
Useful when you want to feed the result directly into another system that expects a list (e.g. building an audience export, deduping email addresses).
order_by / order_by_desc
order_by/<col>[,<col>...] # ascending
order_by_desc/<col>[,<col>...] # descending
Multiple columns: ties on the first column are broken by the second, and so on. Each direction (order_by or order_by_desc) applies to all listed columns.
GET /rest/users/order_by_desc/last_session_time/limit/100
The first listed column must be an indexed column (or the leading column of a composite index) when there’s no where clause — otherwise the endpoint rejects the request as a full-table scan.
limit — pagination
limit/<count> # first <count> rows
limit/<offset>,<count> # skip <offset> then take <count>
Hard cap is 1,000 rows per request. Larger values are rejected with 400 Only can select up to 1000 rows.
GET /rest/users/order_by_desc/last_session_time/limit/100
GET /rest/users/order_by_desc/last_session_time/limit/100,100 # rows 100..199
Cursor pagination — preferred for large result sets
Deep offset values get slow because the database has to re-read all skipped rows. For larger result sets, paginate by walking the indexed order_by column with cursor-style where:
# page 1
GET /rest/users/order_by_desc/last_session_time/limit/1000
# page 2 — pass the last seen value
GET /rest/users/where/last_session_time<1730000000/order_by_desc/last_session_time/limit/1000
# page 3 — pass the last seen value from page 2
GET /rest/users/where/last_session_time<1729000000/order_by_desc/last_session_time/limit/1000
The cursor value is whatever the last record in the previous page had for the indexed column. This stays fast indefinitely — each page is an indexed range scan, not a “skip 50,000 rows” linear scan.
< not <=) plus a tiebreaker on a unique column — e.g. where/last_session_time<X&user_id<Y.Putting it together
GET /rest/products
/where/category=tools&is_in_stock=yes
/columns/id,internal_id,title,price,inventory
/order_by/title
/limit/100
(Newlines added for readability — actually all on one line, no spaces.)