App Data Management

Shared records that belong to the application rather than to any one user — announcements, catalogues, feature flags, pricing tables. Seven methods, all scoped by the configured appId.

Dart
1// App data belongs to the app, not to any user.
2final created = await authix.addAppData(
3  const AddAppDataParams(
4    dataCategory: 'announcements',
5    data: {'title': 'We are live', 'pinned': true},
6  ),
7);
8
9created.id;              // 'ck…'
10created.version;         // 0
11created.data!['title'];  // 'We are live'

Secret key only

App-wide data is readable and writable by every user of your app, so these routes are account-level: a secret key or an admin dashboard session. Called with a publishable key they return InsufficientScopeError. Put them behind your own backend and expose only what your app needs.

The DataItem type

App-wide records and user records share one shape. Pass DataItem.fromJson as itemParser to get a typed id, version and dataCategory plus an indexer for the payload.

MemberTypeMeaning
idStringRecord ID.
dataCategoryStringThe category it was filed under.
versionintBumped on every write. Pass it back on update for optimistic concurrency.
createdAt / updatedAtDateTime?Parsed timestamps, when the API sent them.
parentIdString?Parent record, for nested shapes.
operator []dynamicRead any payload field: item['title'].
payloadMap<String, dynamic>The payload with the identity fields stripped out.
fieldsMap<String, dynamic>The full decoded record, identity fields included.
Dart
1final page = await authix.getAppData<DataItem>(
2  const GetAppDataParams(category: 'announcements'),
3  itemParser: DataItem.fromJson,
4);
5
6for (final item in page) {
7  item.id;             // typed String
8  item.version;        // typed int — keep it to update safely
9  item.dataCategory;   // 'announcements'
10  item['title'];       // any payload field, via the [] operator
11  item.payload;        // the payload without the identity fields
12  item.fields;         // the full decoded record as a Map
13}

Methods

addAppData

POST /app/:appId/data/:dataCategory

Future<DataItemResponse> addAppData(AddAppDataParams params)
ParamTypeRequiredDescription
dataCategoryStringYesCategory namespace — travels in the URL, not the body.
dataMap<String, dynamic>YesThe record body.

Returns: DataItemResponse

Dart
1final created = await authix.addAppData(
2  const AddAppDataParams(
3    dataCategory: 'announcements',
4    data: {
5      'title': 'Version 2 is out',
6      'body': 'Dark mode, faster sync.',
7      'publishedAt': '2026-08-08',
8    },
9  ),
10);

Throws: ConfigurationError when dataCategory is empty. PermissionDeniedError when the plan's app-data storage limit is reached.

Your map is sent as the body verbatim — no envelope, and no appId is merged in, because the app id is already in the path. In v1 both were, and ended up stored inside the record.

getAppData

GET /app/:appId/app-data[/:category]

Future<Page<T>> getAppData<T>(GetAppDataParams params, {ItemParser<T>? itemParser})
ParamTypeRequiredDescription
categoryString?NoRestrict to one category. Omit it to list every category.
limitint?NoPage size. Defaults to 20, capped at 100.
cursorString?NonextCursor from the previous page.
itemParserItemParser<T>?NoMaps each record into your own model.

Returns: Page<T>

Dart
1// One category
2final news = await authix.getAppData(
3  const GetAppDataParams(category: 'announcements', limit: 50),
4);
5
6// Everything
7final all = await authix.getAppData(const GetAppDataParams());

Cursor-paginated, newest first. In v1 this returned the whole list — an app with a large catalogue would load all of it into memory.

iterateAppData

GET /app/:appId/app-data (repeated)

Stream<T> iterateAppData<T>(GetAppDataParams params, {ItemParser<T>? itemParser, int? maxPages})
ParamTypeRequiredDescription
paramsGetAppDataParamsYesSame filters as getAppData. Any cursor you set is ignored.
itemParserItemParser<T>?NoMaps each record into your own model.
maxPagesint?NoStop after this many pages.

Returns: Stream<T>

Dart
1await for (final item in authix.iterateAppData(
2  const GetAppDataParams(category: 'catalogue'),
3)) {
4  print(item['sku']);
5}

Follows the cursor for you and yields records as they arrive.

getSingleAppData

GET /app/:appId/data/:dataId

Future<DataItemResponse> getSingleAppData({required String dataId})
ParamTypeRequiredDescription
dataIdStringYesRecord ID.

Returns: DataItemResponse

Dart
1final item = await authix.getSingleAppData(dataId: 'app_data_123');
2print(item.data!['title']);

Throws: ConfigurationError when dataId is empty. NotFoundError when the record does not exist.

searchInAppData

POST /app/:appId/app-data/search

Future<Page<T>> searchInAppData<T>(SearchAppDataParams params, {ItemParser<T>? itemParser})
ParamTypeRequiredDescription
qString?NoSubstring match across the whole record.
categoryString?NoRestrict to one category.
keysMap<String, dynamic>?NoExact field match, e.g. {'pinned': true}.
limitint?NoPage size. Defaults to 20, capped at 100.
cursorString?NonextCursor from the previous page.

Returns: Page<T>

Dart
1final results = await authix.searchInAppData(
2  const SearchAppDataParams(
3    category: 'announcements',
4    q: 'dark mode',
5    keys: {'pinned': true},
6  ),
7);

Every argument is optional — calling it with an empty params object searches all app data. Both filters are index-backed in Postgres.

updateAppData

PATCH /app/:appId/data/:dataId

Future<DataItemResponse> updateAppData(UpdateAppDataParams params)
ParamTypeRequiredDescription
dataIdStringYesRecord to update.
dataMap<String, dynamic>YesFields to write. Fields you omit are left alone.
versionint?NoThe version you last read. Omit it and the write always wins.

Returns: DataItemResponse

Dart
1await authix.updateAppData(
2  const UpdateAppDataParams(
3    dataId: 'app_data_123',
4    data: {'title': 'Version 2.1 is out'},
5    version: 4,
6  ),
7);

Throws: ConfigurationError when dataId is empty. VersionConflictError when version no longer matches.

Uses PATCH — the only method in the SDK that does. It is a partial update.

deleteAppData

DELETE /app/:appId/data/:dataId

Future<Map<String, dynamic>> deleteAppData(DeleteAppDataParams params)
ParamTypeRequiredDescription
dataIdStringYesRecord to delete.

Returns: Map<String, dynamic>

Dart
1await authix.deleteAppData(
2  const DeleteAppDataParams(dataId: 'app_data_123'),
3);

Permanent.

App data vs user data

User dataApp data
Scoped byuserId (pinned to the session for pk_ keys)appId from your config
Reachable withPublishable or secret keySecret key only
ListingCursor-paginated PageCursor-paginated Page
Update verbPUTPATCH
Category lives inRequest bodyURL path
Atomic batchesbatch()Not available

Related