Personyze Wiki Personyze Wiki docs
Open Personyze
Developer Resources

REST API — Path Parameters

The path-parameter syntax shared by every Personyze REST endpoint: where, columns, column, order_by, order_by_desc, limit, plus single-id lookups and the indexed-column requirement on large tables.

Updated 8 hours ago 6 min read
A
by Admin

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=y is equivalent to /where/x=y/limit/100.
  • Each modifier appears at most once per request. Multiple where conditions go inside a single where/ 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.

How to handle the indexed-column error.When you hit a ‘not indexed’ error, the error body tells you exactly which columns ARE indexed for that endpoint. Pick one of those, restructure your query, and you’re done. The Objects Reference also lists indexed columns per object, but the live error message is the authoritative source.

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.

Tiebreakers for cursor pagination.Cursor pagination skips rows that have the exact same cursor value as the boundary. If many rows share a timestamp, you may miss some. To be safe, use a strict inequality (< 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.)

Next steps

📚 Objects ReferenceNow apply the path syntax against real endpoints — column lists, method support, indexed columns, and copy-paste examples for every object. Read →
🛠 Developer ResourcesBack to the main hub, with links to SDKs and other developer tooling. Read →