About Queries
This document provides detailed information on how to use query filters in the ledger service, in order to precisely narrow down records that the api caller is interested in. The filtering mechanism follows (unrelated to the actual DB the ledger uses) the syntax of MongoDB query filters and is implemented through query parameters.
API Supported Endpoints
Filtering can be applied to all records that have an endpoint that lists them, e.g. /intents, /wallets, etc.
CLI Supported Commands
Filtering is supported by the CLI on list commands e.g. intent list, wallet list, etc.
Query Operators
At the moment, the following query operators are supported:
| Operator | Explanation |
|---|---|
$eq | (equal to) |
$gt | (greater than) |
$gte | (greater than or equal to) |
$lt | (less than) |
$lte | (less than or equal to) |
$in | (in array) |
$ne | (not equal to) |
$nin | (not in array) |
$regex | (pattern matching) |
Filtering Syntax
Filters are passed through query parameters. The syntax follows the pattern of some.field.path.$operator=value. If no operator is listed, $eq is assumed.
The path of a field is the same as in the response when fetching a record of the same type. So, if intent has a data property with a handle that is a string, the path for filtering by handle is data.handle
If the query params contain multiple filters, they must all be a match for a record to be returned.
Using filters via the Ledger API
Below are some examples to illustrate how filters can be used:
GET /v2/intents?data.handle.$eq=my-intent
# these two calls are equivalent
GET /v2/intents?data.handle=my-intent The above filter will match intents where data.handle equals my-intent.
GET /v2/wallets?data.schema.$eq=bank-walletThe above filter will match wallets the schema of which is bank-wallet.
GET /v2/wallet?meta.status.$eq=activeThe above filter will match wallets that are marked as active.
Using filters with CLI
The CLI supports filters via the --filter flag. The query must be a valid JSON object, passed as a string and surrounded by single quotes.
Its keys must match the supported field.path.$operator=value syntax.
For example:
intent list --filter '{"meta.moment.$gte":"2025-11-20T21:53:20.536Z", "meta.status.$in": ["pending", "processing"]}'Using Filters with SDK
The ledger SDK supports filtering with typings to aid with autocomplete and type-checking. Here is an example of how to use filters with the SDK:
const { wallets, response, page } = await sdk.wallet.list({
page: {
index: 0,
limit: 10,
},
"meta.moment.$gt": new Date('2021-01-01T00:00:00Z'),
});Due to technical limitations, the only property type that does not support autocomplete and type-checks is filtering of individual items in array fields like data.claims in intents. Exact matches may still be filtered and autocompleted with something like the following:
const { wallets, response, page } = await sdk.wallet.list({
page: {
index: 0,
limit: 10,
},
"data.claims.$eq": [{
action: 'transfer',
amount: 50,
source: 'abcd',
target: 'defg',
symbol: 'usd'
}],
});Array Property Filtering
The below section describes the principles for filtering of items contained in arrays.
For array properties like claims in intents, two types of filtering can be applied:
-
Specific Index Filtering
GET /v2/intents?data.claims.0.amount.$gt=20This filter will match intents where the first claim's
amountis greater than 20. Equivalent SDK:const { wallets, response, page } = await sdk.wallet.list({ page: { index: 0, limit: 10, }, "data.claims.0.amount.$gt": 20, }); -
Any Element Filtering
GET /v2/intents?data.claims.amount.$gt=20This filter will match intents where any claim's
amountis greater than 20. Equivalent SDK:const { wallets, response, page } = await sdk.wallet.list({ page: { index: 0, limit: 10, }, "data.claims.amount.$gt": 20, });
Custom Field Filtering
Any field under data.custom can be filtered using the same syntax:
GET /v2/wallets?data.custom.accountType.$eq=savings
GET /v2/anchors?data.custom.paymentId.$eq=PAY-12345Custom fields are stored as JSONB and are queryable with all supported operators. However, custom fields are not indexed by default and filtering on them over large datasets may impact performance. Some resources have specific custom fields indexed — see each resource's list endpoint documentation for details.
Full-Text Search ($plainTextQuery)
In addition to the field operators above, intents support a full-text search parameter, $plainTextQuery.
The term is matched against the intent's handle, ledger, schema, meta.status, and per-claim fields (source and target wallet addresses, symbol, and action).
GET /v2/intents?$plainTextQuery=acmeconst { intents } = await sdk.intent.list({
page: { index: 0, limit: 10 },
"$plainTextQuery": "acme",
});intent list --filter '{"$plainTextQuery":"acme"}'Behavior
- Whole-word matching. The term must match a complete word or identifier — there is no prefix or partial matching. Searching
5512matches the wallet addresssvgs:5512@example.com, but551matches nothing. - Wallet addresses can be searched by part. A wallet address has the form
schema:handle@domain(e.g.svgs:5512@example.com). The handle (5512), the domain (example.com), and the handle-and-domain together (5512@example.com) are each searchable on their own. Fragments that are not a complete part — such asexamplewithout.com— do not match. - Case-insensitive. Matching ignores letter case, so
acmeandACMEare equivalent. - Combinable.
$plainTextQuerycan be combined with regular field filters; all conditions must match. - Broad terms may time out. A very broad term that matches a large share of records can be slow. If searches run too long they are timed out and return an error.
What matches
For an intent whose handle is acme and target wallet address is svgs:5512@example.com:
| Search term | Matches? |
|---|---|
acme | ✅ (the intent handle) |
5512@example.com | ✅ (the wallet handle and domain) |
5512 | ✅ (the wallet handle) |
example.com | ✅ (the domain) |
acm | ❌ (not a complete word) |
551 | ❌ (not a complete word) |
example | ❌ (not the complete domain) |
Combining text search with other filters
$plainTextQuery can be combined with the field filters above — every condition must match. For example, find intents involving a wallet address, restricted to transfers:
GET /v2/intents?$plainTextQuery=5512@example.com&data.claims.action=transferconst { intents } = await sdk.intent.list({
page: { index: 0, limit: 10 },
"$plainTextQuery": "5512@example.com",
"data.claims.action": "transfer",
});intent list --filter '{"$plainTextQuery":"5512@example.com","data.claims.action":"transfer"}'Pagination
List endpoints return paginated results. Use the page query parameter to control pagination:
GET /v2/wallets?page.index=0&page.limit=20page.index— zero-based page number (default:0)page.limit— records per page (default:20)
Filterable Fields
Each resource supports a specific set of filterable fields. Common fields available across most resources include:
data.handle— record handledata.schema— record schemameta.status— record statusmeta.labels— record labelsmeta.domain— domain scopemeta.moment— record timestampdata.custom.<field>— any custom field
See each resource's list endpoint for the full set of supported filters.
Known Limitations
- Some fields are not filterable, depending on record. If a user attempts to filter by a non-supported field, an error will be returned indicating which fields are not supported.
- Currently, some filtering is done in memory. Future versions of the ledger will optimize this by processing more filters in the database for efficient querying.
- For now, querying is done in query params on
GETendpoints. Due to syntax limitations, we do not currently support$andand$oroperators. Everything is assumed to be under one root$and. In the future, we may introducePOSTendpoints for search, where these operators may be used to build more complex expressions.