Global App Search
Six back-office methods that operate across every user of your app: list them, search them, and reach all of their records at once. All are scoped by the configured appId.
Secret key only — server side
getAllUsersFromApp returns your user table. These routes are account-level and require a secret key; called with a publishable key they return InsufficientScopeError, so a decompiled app cannot enumerate your users.
That protection only holds if the secret key never leaves your server. Never pass one through --dart-define in a shipped build — defines are compiled into the binary in plain text.
Why these are POST requests
All six are reads, but they are sent as POST because the backend expects the filters in a request body. This matches the JavaScript and Python SDKs exactly, so every client talks to the API identically.
Methods
getAllUsersFromApp
POST /app/global/:appId/all-app-users
Future<Page<T>> getAllUsersFromApp<T>(GetAllAppUsersParams params, {ItemParser<T>? itemParser})| Param | Type | Required | Description |
|---|---|---|---|
| limit | int? | No | Page size. Defaults to 20, capped at 100 — a larger value is clamped, not rejected. |
| cursor | String? | No | nextCursor from the previous page. |
| itemParser | ItemParser<T>? | No | Maps each user into your own model. |
Returns: Page<T>
1final page = await authix.getAllUsersFromApp(
2 const GetAllAppUsersParams(limit: 50),
3);
4
5page.length; // users in this page
6page.hasMore; // true when more exist
7
8for (final user in page) {
9 print(user['email']);
10}Newest first. Omitted arguments are left out of the body entirely rather than sent as null.
iterateAllUsers
POST /app/global/:appId/all-app-users (repeated)
Stream<T> iterateAllUsers<T>(GetAllAppUsersParams params, {ItemParser<T>? itemParser, int? maxPages})| Param | Type | Required | Description |
|---|---|---|---|
| params | GetAllAppUsersParams | Yes | Page size. Any cursor you set is ignored — the traversal manages its own. |
| itemParser | ItemParser<T>? | No | Maps each user into your own model. |
| maxPages | int? | No | Stop after this many pages. Use it — a full sweep of a large app is a lot of requests. |
Returns: Stream<T>
1await for (final user in authix.iterateAllUsers(
2 const GetAllAppUsersParams(limit: 100),
3 maxPages: 50,
4)) {
5 print(user['email']);
6}searchInAllAppUsers
POST /app/global/:appId/all-app-users/search
Future<Page<T>> searchInAllAppUsers<T>(SearchAllAppUsersParams params, {ItemParser<T>? itemParser})| Param | Type | Required | Description |
|---|---|---|---|
| q | String? | No | Substring match across user fields. |
| keys | Map<String, dynamic>? | No | Exact field match. Restricted to the allowlist below. |
| limit | int? | No | Page size. Defaults to 20, capped at 100. |
| cursor | String? | No | nextCursor from the previous page. |
Returns: Page<T>
1// Substring match across all users
2final matches = await authix.searchInAllAppUsers(
3 const SearchAllAppUsersParams(q: 'john'),
4);
5
6// Exact field match
7final admins = await authix.searchInAllAppUsers(
8 const SearchAllAppUsersParams(
9 keys: {'role': 'admin', 'isActive': true},
10 ),
11);Throws: ValidationError when keys names a field outside the allowlist.
Only id, username, name, email, phone, address, role, isVerified and isActive may be filtered on. Credential columns are deliberately unreachable — filtering on 'password' is rejected, not silently ignored.
getAllUsersDataFromApp
POST /app/global/:appId/all-app-users-data
Future<Page<T>> getAllUsersDataFromApp<T>(GetAllAppUsersDataParams params, {ItemParser<T>? itemParser})| Param | Type | Required | Description |
|---|---|---|---|
| category | String? | No | Restrict to one data category. |
| limit | int? | No | Page size. Defaults to 20, capped at 100. |
| cursor | String? | No | nextCursor from the previous page. |
| itemParser | ItemParser<T>? | No | Maps each record into your own model. |
Returns: Page<T>
1final page = await authix.getAllUsersDataFromApp(
2 const GetAllAppUsersDataParams(category: 'orders', limit: 100),
3);
4
5print('${page.length} orders in this page');Reaches across users — the same records getUserData returns one user at a time.
searchInAllAppUsersData
POST /app/global/:appId/all-app-users-data/search
Future<Page<T>> searchInAllAppUsersData<T>(SearchAllAppUsersDataParams params, {ItemParser<T>? itemParser})| Param | Type | Required | Description |
|---|---|---|---|
| q | String? | No | Substring match across the whole record. |
| category | String? | No | Restrict to one data category. |
| keys | Map<String, dynamic>? | No | Exact field match, e.g. {'status': 'pending'}. |
| limit | int? | No | Page size. Defaults to 20, capped at 100. |
| cursor | String? | No | nextCursor from the previous page. |
Returns: Page<T>
1final pending = await authix.searchInAllAppUsersData(
2 const SearchAllAppUsersDataParams(
3 category: 'orders',
4 keys: {'status': 'pending'},
5 ),
6);The cross-user counterpart of searchInUserData. keys becomes a jsonb containment query and q a trigram substring match, both index-backed in Postgres — nothing is scanned in Node or on the device.
iterateAllUsersData
POST /app/global/:appId/all-app-users-data[/search] (repeated)
Stream<T> iterateAllUsersData<T>({String? category, String? q, Map<String, dynamic>? keys, int? limit, ItemParser<T>? itemParser, int? maxPages})| Param | Type | Required | Description |
|---|---|---|---|
| category | String? | No | Restrict to one data category. |
| q | String? | No | Substring match. Supplying it switches to the search route. |
| keys | Map<String, dynamic>? | No | Exact field match. Supplying it switches to the search route. |
| limit | int? | No | Page size. |
| maxPages | int? | No | Stop after this many pages. |
Returns: Stream<T>
1// Lists or searches depending on the arguments you give it.
2await for (final record in authix.iterateAllUsersData(
3 category: 'orders',
4 keys: {'status': 'pending'},
5 maxPages: 20,
6)) {
7 print(record['id']);
8}Picks the list or the search route for you depending on whether q or keys is given, then follows the cursor.
Paginating
Every method here returns the same Page as getUserData. There is no unbounded mode: 20 records by default, 100 maximum.
| Member | Type | Meaning |
|---|---|---|
| length | int | Records in this page. |
| hasMore | bool | Whether another page exists. |
| nextCursor | String? | Pass back as cursor for the next page. |
| total | int | Records in this page as the API reported it — not how many exist overall. |
| data | List<T> | The records — raw maps unless itemParser is given. Iterating the page yields these directly. |
| raw | Map<String, dynamic> | The undecoded response body. |
1// One page at a time
2var page = await authix.getAllUsersFromApp(
3 const GetAllAppUsersParams(limit: 100),
4);
5
6while (page.hasMore) {
7 page = await authix.getAllUsersFromApp(
8 GetAllAppUsersParams(limit: 100, cursor: page.nextCursor),
9 );
10 process(page);
11}
12
13// Or stream every user, following the cursor for you
14await for (final user in authix.iterateAllUsers(
15 const GetAllAppUsersParams(limit: 100),
16)) {
17 print(user['email']);
18}Typed results
1class AppUser {
2 final String id;
3 final String email;
4
5 const AppUser({required this.id, required this.email});
6
7 factory AppUser.fromJson(Map<String, dynamic> json) => AppUser(
8 id: json['id']?.toString() ?? '',
9 email: json['email']?.toString() ?? '',
10 );
11}
12
13final page = await authix.getAllUsersFromApp<AppUser>(
14 const GetAllAppUsersParams(limit: 50),
15 itemParser: AppUser.fromJson,
16);
17
18for (final user in page) { // a Page is an Iterable of its records
19 print(user.email);
20}Rate and volume
These endpoints reach across your whole dataset. Prefer a search with keys over a full sweep, bound every traversal with maxPages, and cache on your side — a full sweep on every launch will burn through your request quota.
Related