Cross-user search

Reading across every user in the app: listing users, searching them, and sweeping their records. This is what Python is usually here for — exports, backfills, admin tooling and scheduled jobs.

Secret key only

Every method here reads across user boundaries, so every one requires a secret key and raises InsufficientScopeError (403) without it. None of these calls belong anywhere a user could reach them.

get_all_users_from_appAPI key only

GET /api/app/users

authix.get_all_users_from_app(
    *, limit: int | None = None, cursor: str | None = None,
) -> Page
ParamTypeRequiredDescription
limitintNo1–100. Defaults to 20.
cursorstrNonext_cursor from the previous page.

Returns: Page

Python
1page = authix.get_all_users_from_app(limit=100)
2
3for user in page:
4    print(user["email"])

Newest first.

iter_all_usersAPI key only

GET /api/app/users (paged)

authix.iter_all_users(
    *, limit: int | None = None, max_pages: int | None = None,
) -> Iterator[dict]
ParamTypeRequiredDescription
limitintNoPage size. Use 100 for a full sweep — fewer round trips, same total records.
max_pagesintNoBound the traversal.

Returns: Iterator[dict]

Python
1for user in authix.iter_all_users(limit=100):
2    process(user)

search_in_all_app_usersAPI key only

GET /api/app/users/search

authix.search_in_all_app_users(
    *, q: str | None = None, keys: dict | None = None,
    limit: int | None = None, cursor: str | None = None,
) -> Page
ParamTypeRequiredDescription
qstrNoFree-text match across searchable user fields.
keysdictNoExact-match filter, e.g. {'isVerified': True}.
limitintNo1–100.
cursorstrNoFor the next page.

Returns: Page

Python
1page = authix.search_in_all_app_users(
2    q="example.com",
3    keys={"isVerified": True},
4    limit=100,
5)

Throws: ValidationError (400) when filtering on a field that is not searchable — the allowlist exists so a filter cannot be used to probe arbitrary columns.

get_all_users_data_from_appAPI key only

GET /api/app/users/data

authix.get_all_users_data_from_app(
    *, category: str | None = None,
    limit: int | None = None, cursor: str | None = None,
) -> Page
ParamTypeRequiredDescription
categorystrNoRestrict to one category.
limitintNo1–100.
cursorstrNoFor the next page.

Returns: Page

Python
1page = authix.get_all_users_data_from_app(category="orders", limit=100)

Each record carries the userId of its owner, which is what makes a write-back after a sweep possible.

search_in_all_app_users_dataAPI key only

GET /api/app/users/data/search

authix.search_in_all_app_users_data(
    *, q: str | None = None, category: str | None = None,
    keys: dict | None = None,
    limit: int | None = None, cursor: str | None = None,
) -> Page
ParamTypeRequiredDescription
qstrNoFree-text match.
categorystrNoRestrict to one category.
keysdictNoExact-match filter on payload fields.
limitintNo1–100.
cursorstrNoFor the next page.

Returns: Page

Python
1page = authix.search_in_all_app_users_data(
2    category="orders",
3    keys={"status": "refunded"},
4    limit=100,
5)

iter_all_users_dataAPI key only

GET /api/app/users/data (paged)

authix.iter_all_users_data(
    *, category: str | None = None, q: str | None = None,
    keys: dict | None = None,
    limit: int | None = None, max_pages: int | None = None,
) -> Iterator[dict]
ParamTypeRequiredDescription
categorystrNoRestrict to one category.
qstrNoFree-text match. Passing q or keys switches to the search endpoint.
keysdictNoExact-match filter.
limitintNoPage size.
max_pagesintNoBound the traversal.

Returns: Iterator[dict]

Python
1for row in authix.iter_all_users_data(q="invoice", max_pages=10):
2    process(row)

Lists or searches depending on whether q/keys are given, so one loop covers both.

Exporting every user to CSV

Python
1import csv
2from neuctra_authix import Authix
3
4authix = Authix(app_id=APP_ID, secret_key=SECRET_KEY)
5
6# iter_* is a generator, so a 50,000-user export never holds more than
7# one page in memory — the row is written and discarded.
8with open("users.csv", "w", newline="") as fh:
9    writer = csv.DictWriter(fh, fieldnames=["id", "email", "name", "createdAt"])
10    writer.writeheader()
11
12    for user in authix.iter_all_users(limit=100):
13        writer.writerow({k: user.get(k) for k in writer.fieldnames})

Why a generator matters here

Building a list of 50,000 users before writing the file would hold the entire dataset in memory. Iterating writes each row and discards it, so peak memory is one page regardless of account size.

Backfilling a field

Python
1# A backfill is the classic Python job: read every record across every
2# user, transform it, write it back. Bound it and log progress — a sweep
3# that silently stops halfway is worse than one that fails loudly.
4processed = 0
5
6for record in authix.iter_all_users_data(category="orders", limit=100):
7    if record.get("currency"):
8        continue
9
10    authix.update_user_data(
11        user_id=record["userId"],
12        data_id=record["id"],
13        data={"currency": "USD"},
14        version=record["version"],
15    )
16
17    processed += 1
18    if processed % 500 == 0:
19        print(f"{processed} records updated")

Mind the request quota

A sweep that reads and writes every record consumes two requests per record. On a large account that alone can exhaust a monthly quota — check your plan limit before starting, and use max_pages to test the job on a slice first.

Writing back with version is also worth the extra care: a backfill running while users are active will hit conflicts, and a 409 you ignore is a record you silently skipped.

Related