This guide explains how to query documents using POST /:application/search, the Filter AST format, compound query composition, and how ACL enforcement interacts with search results.
- Overview
- Request Body
- Filter AST — Leaf Nodes
- Compound Queries
- ACL Enforcement
- Search Discoverability
- Cross-References
POST /:application/search
Authorization: Bearer <jwt>
Content-Type: application/json
The search endpoint queries documents within a specific application. The :application path segment must match a configured application name — unknown applications return 404.
ACL enforcement mirrors the sync endpoint: search uses the same listAccessibleDocs() gate as changesSince. A caller only ever receives documents they are authorised to read — private docs from other users are never returned, regardless of what filter is supplied.
{
"collection": "planners",
"filter": { "type": "filter", "field": "meta.name", "op": "contains", "value": "Plan" }
}| Field | Type | Required | Description |
|---|---|---|---|
collection |
string |
✅ | Collection name within the application |
filter |
object |
✅ | Filter AST node (leaf or compound). Must have a type field. |
Response:
{ "results": [ { "_key": "planner-2026", "meta": { "name": "Year Plan" }, ... } ] }Error responses:
| Status | Meaning |
|---|---|
| 400 | Missing collection, missing/invalid filter, or filter missing type field |
| 401 | Missing or invalid JWT |
| 404 | Unknown application name |
A leaf node filters on a single field:
{
"type": "filter",
"field": "meta.name",
"op": "contains",
"value": "Plan"
}| Property | Description |
|---|---|
type |
Always "filter" for a leaf node |
field |
Dot-path to the field to match (e.g. "status", "meta.priority") |
op |
Comparison operator (see table below) |
value |
Value to compare against |
| Operator | Meaning |
|---|---|
eq |
Equal |
ne |
Not equal |
contains |
String contains (case-sensitive substring match) |
gt |
Greater than |
gte |
Greater than or equal |
lt |
Less than |
lte |
Less than or equal |
The Filter AST is the native query format of jsnosqlc — the storage abstraction used internally by the sync engine. The full expressiveness of the store is available to callers; no translation layer is applied.
Example — find all high-priority tasks:
{
"collection": "tasks",
"filter": {
"type": "filter",
"field": "priority",
"op": "eq",
"value": "high"
}
}Combine leaf nodes using and, or, and not to express multi-condition queries.
{
"type": "and",
"filters": [
{ "type": "filter", "field": "status", "op": "eq", "value": "open" },
{ "type": "filter", "field": "priority", "op": "eq", "value": "high" }
]
}{
"type": "or",
"filters": [
{ "type": "filter", "field": "status", "op": "eq", "value": "open" },
{ "type": "filter", "field": "status", "op": "eq", "value": "in-progress" }
]
}{
"type": "not",
"filters": [
{ "type": "filter", "field": "status", "op": "eq", "value": "done" }
]
}and, or, and not nodes can be nested to arbitrary depth:
{
"type": "and",
"filters": [
{ "type": "filter", "field": "status", "op": "eq", "value": "open" },
{
"type": "or",
"filters": [
{ "type": "filter", "field": "priority", "op": "eq", "value": "high" },
{ "type": "filter", "field": "dueDate", "op": "lt", "value": "2026-04-01" }
]
}
]
}Full request example — open tasks that are high-priority or overdue:
POST /todo/search
Authorization: Bearer eyJhbGci...
Content-Type: application/json
{
"collection": "tasks",
"filter": {
"type": "and",
"filters": [
{ "type": "filter", "field": "status", "op": "eq", "value": "open" },
{
"type": "or",
"filters": [
{ "type": "filter", "field": "priority", "op": "eq", "value": "high" },
{ "type": "filter", "field": "dueDate", "op": "lt", "value": "2026-04-01" }
]
}
]
}
}The search endpoint enforces the same ACL rules as the sync changesSince endpoint. Before executing your filter, the server calls listAccessibleDocs() and ANDs its result with your filter:
effective query = your filter AND (own docs OR accessible cross-namespace docs)
The accessible document set includes:
- All documents the caller owns (any visibility)
- Documents shared with the caller via
sharedWith(visibility"shared") - Documents from orgs the caller belongs to (visibility
"org", whenX-Org-Idis provided) - Documents with
visibility: "public"from any owner
Your filter cannot bypass ACL enforcement — it is always applied additively by the server. A caller cannot retrieve documents outside their authorised scope by crafting a filter that matches private documents belonging to other users.
Which documents appear in search results from other users depends on visibility:
| Visibility | Appears in other users' search results |
|---|---|
private |
❌ No |
shared |
Only for users listed in sharedWith for this app |
org |
Org members only |
public |
✅ Yes — any authenticated user |
Share token only (visibility ≠ "public") |
❌ No |
Important: Minting a share token does not make a document discoverable in search. A share token is for direct-link access only. To make a document appear in search results, set
visibility: "public". See ADR-016 and docs/sharing.md §6.