Skip to content

Query, sort, and bulk writes

List, bulk update, and bulk delete share the same filter encoding. The JavaScript, Dart, and Python SDKs build it for you.

GET /api/p/:projectId/:table?page=1&pageSize=20

page defaults to 1 and pageSize defaults to 20. data is { "items", "total", "page", "pageSize" } (see REST conventions). SDK list() returns that data object.

Chained where clauses combine with $and of per-field objects. Python uses the same operators in snake_case (is_equal_to, is_greater_than, and the rest of the table).

SDK option Mongo
isEqualTo $eq
isNotEqualTo $ne
isGreaterThan $gt
isGreaterThanOrEqualTo $gte
isLessThan $lt
isLessThanOrEqualTo $lte
isIn $in
isNotIn $nin
matches $regex with $options: "i"

matches is a case-insensitive regex. The SDKs do not escape the pattern; pass a regex or a literal. Escape user input before sending if you want contains-style matching. The server still rejects $regex values that are too long or use disallowed quantifiers. See Limits.

const published = await client
.collection('posts')
.where('status', { isEqualTo: 'published' })
.where('views', { isGreaterThan: 100 })
.orderBy('views', { descending: true })
.list({ page: 1, pageSize: 10 });

Pass null explicitly when the filter must be JSON null. Omitting a key means “operator omitted”.

The same list over HTTP:

Terminal window
curl -sG "https://api.tefily.com/api/p/$PROJECT_ID/posts" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode 'page=1' \
--data-urlencode 'pageSize=10' \
--data-urlencode 'filter={"$and":[{"status":{"$eq":"published"}},{"views":{"$gt":100}}]}' \
--data-urlencode 'sort={"views":-1}' \
--data-urlencode 'fields=["title","status"]'

orderBy('name') → {"name": 1}. orderBy('createdAt', { descending: true }) → {"createdAt": -1}. You can chain multiple orderBy calls. Python is order_by("views", descending=True).

On REST, pass sort as JSON (and filter as JSON) on the query string. Sort values are 1 or -1. When sort is omitted, the default is { "_createdAt": -1 }.

select(['title', 'status']) sends fields=["title","status"]. _id is always returned. Unknown names are 400.

await client.collection('posts').select(['title', 'status']).list();

Use Query.get(id) (not Collection.get) when you also want select / join / expand on a single document. Collection.get(id) still returns the full document.

Related documents are nested on the parent. At most 5 join/expand entries. Duplicate as is 400.

join always embeds an array (capped at 50 by default, max 100). Parent pageSize does not apply to nested arrays. If the array length equals limit, there may be more — query the related collection directly to load the rest. A scalar expand of a reference that is not an array unwraps to one object. join is always an array.

const posts = await client
.collection('posts')
.select(['title', 'authorId'])
.join('authorId', { from: 'users', as: 'author', fields: ['name', 'email'] })
.join('_id', { from: 'comments', as: 'comments', foreignField: 'postId', limit: 10 })
.list({ page: 1, pageSize: 10 });

expand('authorId') uses the reference field’s optional target collection. Scalar references become one object or null. Array references stay arrays (same cap). Without a stored target, use join or you get 400. Python is expand("authorId").

The server always applies the list rules of the joined collection. The client cannot skip them. If that list endpoint is disabled, the request is 404. If list is denied, 403. If list is allowed with a filter, unauthorized related documents are omitted.

REST: fields and expand are JSON query parameters on list and get.

createMany (create_many in Python) POSTs a JSON array to the collection URL (a JSON object is still single create). At most 100 documents. Validation and create rules run on every item before any insert; one failure inserts nothing. It requires the create endpoint to be enabled.

const created = await client.collection('posts').createMany([
{ title: 'Hello', status: 'draft' },
{ title: 'World', status: 'draft' },
]);

updateMany / deleteMany (update_many / delete_many in Python) require at least one where. They map to PATCH / DELETE on the collection URL.

updateMany sends a partial patch. Fields you omit, including required ones, stay as they are. Values you send are type-checked. Unknown fields are rejected. If the update endpoint has an owner compare, that rule also sees this patch. A status-only patch can be denied. See Access rules.

await client
.collection('posts')
.where('status', { isEqualTo: 'draft' })
.updateMany({ status: 'archived' });
await client
.collection('posts')
.where('status', { isEqualTo: 'archived' })
.deleteMany();

The data API has no realtime subscription. Poll list or get.

  • At most 5 expand entries. Error: Invalid "expand": at most 5 entries are allowed.
  • Join limit defaults to 50 and must be 1–100. Error: Invalid "expand" limit: must be between 1 and 100.
  • There is no pageSize maximum.
  • $regex must be a string, at most 64 characters, and must not contain +, *, or {. Errors: Filter operator $regex requires a string, Filter operator $regex is too long, Filter operator $regex contains disallowed quantifiers. $options is only legal beside $regex. matches sends $regex plus $options: "i" and does not enforce the length or quantifier rules; the server does.
  • Blocked operators return 400 Filter operator $… is not allowed: $where, $expr, $jsonSchema, $text, $function, $accumulator, $let, $map, $reduce. Any other $ key outside the allow-list is rejected too.
  • Allowed on the filter query parameter, beyond the SDK table: logical $and, $or, $not, and field operators $elemMatch, $exists, $size, $all, $type, plus the operators in the table above.
  • Unknown fields / select names: 400 Unknown field <name> or Unknown fields: a, b. _id is always projected.
  • The joined collection uses its list endpoint. Disabled → 404 Endpoint not found or disabled. Denied → 403 with the rule reason.