Find and inspect data
Use the read operations of the Management API to find out what an environment stores and to inspect the data of individual users. All read operations require the Data: Read permission or the Admin permission. They never change data.
The examples on this page send operations to https://personalization.service.eu.axinom.net/graphql-management with an Authorization: Bearer <token> header.
See Get started for how to obtain a token and send a request.
Choose a read operation
| Goal | Operation | Returns |
|---|---|---|
| Find which keys hold array items | listArrayKeys | Key names and the scopes that use them |
| Find which keys hold key-value entries | listKeyValueKeys | Key names and the scopes that use them |
| Inspect key-value entries, JSON and progress | listKeyValues | Entries with their kind, value, and owner |
| Inspect array items | listArrayItems | Items with their value, itemId, and owner |
All four operations take a single input argument and return a page of results with pageInfo.
Discover keys
Key listings return key names and the scopes that hold data under each key, without any values. Use them to learn which keys exist before you read the data under a key.
A key appears in the listing as soon as its first record is written and disappears when its last record is deleted. Keys are returned in ascending order, and each key is listed once, even when many users store data under it.
List array keys
query ListArrayKeys($input: ListArrayKeysInput!) {
listArrayKeys(input: $input) {
nodes {
key
scopes
}
pageInfo {
hasNextPage
endCursor
}
}
}
{
"input": {
"first": 50
}
}
List key-value keys
A key that holds both JSON and progress entries is listed once.
query ListKeyValueKeys($input: ListKeyValueKeysInput!) {
listKeyValueKeys(input: $input) {
nodes {
key
scopes
}
pageInfo {
hasNextPage
endCursor
}
}
}
{
"input": {
"first": 50
}
}
Read the scopes of a key
scopes lists every scope that has data under the key, in the order USER, PROFILE, APPLICATION.
Use it to decide how to filter the next read.
A key whose scopes contains only APPLICATION, for example, holds data that belongs to applications of profiles.
Read key-value entries
listKeyValues returns JSON entries and progress entries.
Every entry carries its kind and its owner.
query ListKeyValues($input: ListKeyValuesInput!) {
listKeyValues(input: $input) {
nodes {
key
kind
value
owner {
userId
profileId
applicationId
scope
key
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
The following variables read all JSON entries that one user stored with APPLICATION scope for one application:
{
"input": {
"filter": {
"userId": "11111111-1111-4111-8111-111111111111",
"applicationId": "22222222-2222-4222-8222-222222222222",
"scope": "APPLICATION",
"kind": "JSON"
},
"first": 25
}
}
Read progress entries
Set kind to PROGRESS to read playback progress.
Progress values are integers.
{
"input": {
"filter": {
"userId": "11111111-1111-4111-8111-111111111111",
"key": "playback:movie:100",
"kind": "PROGRESS"
}
}
}
Progress is read-only in the Management API.
Progress entries reflect what has been stored so far, so a value can trail live playback slightly.
Leave out kind to receive JSON and progress entries together, and check each entry's kind before you use its value.
Match entry content
Add a match to return only entries whose JSON value contains the given properties.
The following variables find JSON entries under one key whose value has "theme": "dark".
{
"input": {
"filter": {
"key": "ui:preferences"
},
"match": {
"value": { "theme": "dark" }
},
"first": 100
}
}
A match compares object properties. Scalar values, including progress values, never match. See Filtering, matching, and patching for the exact matching rules.
Read array items
listArrayItems returns the items of arrays.
Every item carries its own itemId, its stored value, the service-owned dateModified, and its owner.
query ListArrayItems($input: ListArrayItemsInput!) {
listArrayItems(input: $input) {
nodes {
id
itemId
value
dateModified
owner {
userId
profileId
applicationId
scope
key
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
The following variables read the first page of one user's favorites:
{
"input": {
"filter": {
"userId": "11111111-1111-4111-8111-111111111111",
"key": "content:favorites",
"scope": "USER"
},
"first": 50
}
}
itemId is the identifier that the customer supplied when the item was written.
It is null for older items that have neither a stored itemId nor a usable value.id.
See the Array API for the item model.
Find one item by identity
Use match.itemId to look for one item across the filtered data.
This example finds every owner's item that has the identity movie:100 under the favorites key.
{
"input": {
"filter": {
"key": "content:favorites"
},
"match": {
"itemId": "movie:100"
},
"first": 100
}
}
You can combine itemId and value in one match.
Every condition you give must hold.
Omit match to select every item that the filter selects.
An empty match is rejected.
Filter by owner and scope
Every field you supply in a filter narrows the result.
An empty filter selects the whole environment, which can be a large amount of data.
Prefer filters that include a key or a userId.
| Filter field | Effect |
|---|---|
key | Only records stored under this key |
userId | Only records of this user |
profileId | Only records of this profile, including PROFILE and APPLICATION scope data |
applicationId | Only APPLICATION scope data of this application |
scope | Only records written with this scope |
kind | Key-value reads only: JSON or PROGRESS |
Ids and scopes must agree.
A filter with scope: USER cannot include profileId or applicationId, and a filter with scope: PROFILE cannot include applicationId.
These contradictions are rejected with the FILTER_CONSTRAINT reason instead of silently matching nothing.
Paginate with cursors
Reads return one page at a time.
Use first to choose the page size, from 1 to 500.
The default is 20.
Pass the endCursor of one page as after in the next request to continue.
{
"input": {
"filter": {
"key": "content:favorites"
},
"first": 100,
"after": "<endCursor from the previous page>"
}
}
Follow hasNextPage, not the page size
Each page examines a bounded number of stored records.
When you use a match, a page can therefore contain fewer than first entries, or none at all, while pageInfo.hasNextPage is still true.
Keep requesting pages with the returned endCursor until hasNextPage is false.
{
"data": {
"listArrayItems": {
"nodes": [],
"pageInfo": {
"hasNextPage": true,
"endCursor": "<opaque cursor>"
}
}
}
}
An empty page with hasNextPage: true is a normal, successful response.
It means that the page examined records without finding a match, and that more records remain.
The following TypeScript sketch reads every page.
type Page = {
nodes: unknown[];
pageInfo: { hasNextPage: boolean; endCursor: string | null };
};
async function readAllPages(
fetchPage: (after: string | null) => Promise<Page>,
): Promise<unknown[]> {
const results: unknown[] = [];
let after: string | null = null;
while (true) {
const page = await fetchPage(after);
results.push(...page.nodes);
if (!page.pageInfo.hasNextPage) {
return results;
}
after = page.pageInfo.endCursor;
}
}
Treat cursors as opaque
A cursor is only valid for the operation, environment, filter, and match that produced it.
Do not decode, build, or change cursor values.
Do not reuse a cursor with a different operation, a different filter, or a different match.
Start a new traversal without after instead.
A cursor that does not fit the request is rejected with the CURSOR_CONSTRAINT reason.
Results appear in a stable order that depends on the filter. Continue with cursors rather than assuming a business order such as newest first.
endCursor is null when a page examined no records.
Page size and cursors for key listings
The key listings paginate the same way, with first and after.
They return each key once in ascending order.
A key that exists for the whole traversal appears exactly once.
Handle read errors
| Reason or code | Meaning |
|---|---|
FORBIDDEN | The token lacks Data: Read or Admin |
PAGINATION_CONSTRAINT | first is outside the range from 1 to 500 |
CURSOR_CONSTRAINT | after is malformed or belongs to another operation, filter, or match |
FILTER_CONSTRAINT | The filter contains ids that contradict its scope |
MATCH_CONSTRAINT | The match is empty or malformed |
Inspect errors[].extensions rather than the message text.
See Errors and limits for the full reference.
Next steps
- Write user data to change the data you found for one user.
- Bulk updates and deletions to change matching data for many users.