Introduction
The OEM API enables setting an authentication flow for users who automatically access an Astrato tenant without a password. The flow is highlighted in the image below.
Example Environment
The link below provides a detailed example of how to test and set an OEM authentication flow in Astrato.
Astrato Tenant Settings | Get Client ID & Secret
In the Astrato, tenants navigate to the Administration section.
In the Administration section, click on View System Settings, which is at the top section.
Look for the OEM Management API section in the System Settings section; if it's the first time, click on Enable.
If it has already been enabled, click on View.
When you click on View, you'll see the Client_ID and Client_Secret.
If a new set is needed, click on Rotate.
Copy them to set up your OEM API authentication flow.
Authentication Flow
The flow is explained in a sequence of requests:
Requests to manage API tokens and session ticket endpoints should not be triggered publicly by the browser. Client ID, Client Secret, and an obtained astrato access token are confidential and, when exposed publicly, will lead to providing full control to the tenant to the world. For security reasons, request those endpoints from a backend service that only exposes a Ticket ID.
ℹ️ Expiry of tokens / sessions
Get oem token: valid for 10 minutes
Use oem token to create tickets: ticket is valid for 60 seconds
Use ticket to create session: session is valid for 7 days
You can manually invalidate a session using this endpoint: https://app.astrato.io/auth/proxy/0/sign_out
Please note, expiry is not custom.
Steps:
POST
/auth/proxy/m2m/tokenAuthentication: public
To get a token, you must authenticate your next requests from Client App Service to the Astrato OEM API.
⚠️ Note: If the user email does not already exist in the system, a temporary user will be created.
This user will not have access permissions unless assigned to a group.
Make sure to include a group (viagroupIdsorgroupNames) that has the required permissions.
Request:
curl --location 'https://app.astrato.io/auth/proxy/m2m/token' \
--header 'Content-Type: application/json' \
--data '{
"clientId": "your client id",
"clientSecret": "your client secret"
}'For example, in PHP it can be used as
function getManagementApiToken() {
$client = new Client();
$headers = ['Content-Type' => 'application/json'];
$body = json_encode([
'clientId' => $_ENV['ASTRATO_CLIENT_ID'],
'clientSecret' => $_ENV['ASTRATO_CLIENT_SECRET']
]);
$url = $_ENV['ASTRATO_URL'] . '/auth/proxy/m2m/token';
$request = new Request('POST', $url, $headers, $body);
$res = $client->send($request);
$result = json_decode($res->getBody(), true)['access_token'];
return $result;
}Response:
{
"access_token": "XXXXXXXXJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiT0...",
"expires_in": 1699891513107
}POST
/oem/setupAuthentication: m2m bearer token
Setup endpoint, which prepares a user session. If a user account doesn't exist, it will be created in a tenant
Request using groupids:
curl --location 'https://app.astrato.io/oem/setup' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI...' \
--data-raw '{
"email": "XXXXXX@astrato.io",
"name": "Developer Viewer",
"groupIds": ["XXXXXX-07bb-429a-8795-5164fddc9e17","XXXXXX-2708-4444-8e71-a3cdfe6e24f8"]
}'
Request using groupNames:
curl --location 'https://app.astrato.io/oem/setup' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI...' \
--data-raw '{
"email": "XXXXXX@astrato.io",
"name": "OEM User",
"groupNames": [
"OEM2"
]
}'
An example in PHP:
function getSessionTicket($accessToken, $email, $groupIds = []) {
$client = new Client();
$headers = [
'Content-Type' => 'application/json',
'Authorization' => "Bearer $accessToken"
];
$body = json_encode([
'email' => $email,
'groupIds' => $groupIds
]);
$request = new Request('POST', $_ENV['ASTRATO_URL'] . '/oem/setup', $headers, $body);
$res = $client->sendAsync($request)->wait();
$result = json_decode($res->getBody(), true);
return $result['ticket'];
}Email(required): if a user with such an email doesn't exist, the account will be created
groupIds (optional): list of groups to temporarily assign users. Membership won't make permanent changes to group membership or be visible on group management views.
Response:
{
"ticket": "XXXXXXX-b1cb-4a84-844e-8XXXXXXXX"
}
GET /auth/proxy/oem/ticket/:ticketId?embed
Authentication: public
It creates a user session cookie under the hood and redirects to Astrato UI. Due to cookie policy, this link cannot be used by embeds. To make it work with embeds, the browser needs to make a top-level request, and when it's done, an embed with a proper link should be injected into the front end.
PHP Example:
rxjs.fetch.fromFetch(`${ASTRATO_URL}auth/proxy/oem/ticket/${data.ticketId}?embed`, { credentials: 'include', selector: response => response.json() }
The resolved user will be returned with groupIds that were requested on OEM/setup
Logout a user from Astrato
To logout a user, call this URL via API:
Dynamic filtering via browser event
This setup lets you pass any filter into Astrato using a single reusable action, as long as the payload includes:
qualified field name
filter value
Payload format
<qualified field name>|<filter value>;
Multiple filters:
Product Dim.Hierarchy Lvl2 Name|Fresh,Frozen;
Product Dim.Hierarchy Lvl3 Name|Canned Foods,Desserts;
How the action works
1. Listen to event
On browser event:
DYNAMIC FILTERING
2. Split into filter instructions
Split
Browser event payloadby;This creates a list of filter instructions
3. Loop through each instruction
For each Filter Instruction:
Extract field name
split(Filter Instruction, "|")[1]Extract value
split(Filter Instruction, "|")[2]
4. Apply filter dynamically
Set filter (Filter Field Name) to (Filter Field Value) as Applied
📋Copy action blocks | Paste directly into Actions workspaces
📋Copy action blocks | Paste directly into Actions workspaces
{"serializedActions":{"type":"on_custom_event","id":"}rZ)#TNOtP/kiW`Pu`g%","x":652,"y":562,"inputs":{"message_type":{"shadow":{"type":"custom_text","id":"/3vb%*+5#=77/?EFH2gh","fields":{"TEXT":"DYNAMIC FILTERING"}}},"DO":{"block":{"type":"loop_for_each","id":"/cKxoZ_|p-`$I*Mz-#Ul","fields":{"iterator":"Filter Instruction"},"inputs":{"list":{"block":{"type":"text_split_to_list","id":"m5My=XP]4.H4x^f#x;ED","inputs":{"text1":{"shadow":{"type":"custom_text","id":"n%;@an[wLKtf|9EG{bA`","fields":{"TEXT":""}},"block":{"type":"custom_event_data","id":"EdgDwh_$O#p#w-uTe4oH","fields":{"data_type":"payload"}}},"text2":{"shadow":{"type":"custom_text","id":"IYWcl!MA#Q~f#F5I9Ldy","fields":{"TEXT":";"}}}}}},"do":{"block":{"type":"loop_for_each","id":"D#Z_U./XdRVy?k.-1;n$","fields":{"iterator":"Comma Sep Filter value"},"inputs":{"list":{"block":{"type":"text_split_to_list","id":"[KwB:3$IMv(n.b0%rRG:","inputs":{"text1":{"shadow":{"type":"custom_text","id":"n%;@an[wLKtf|9EG{bA`","fields":{"TEXT":""}},"block":{"type":"text_split","id":"H[xFkj;i`=08t/KrG``o","inputs":{"text1":{"shadow":{"type":"custom_text","id":"^G~]cdAS|_0x[G0FO@W]","fields":{"TEXT":""}},"block":{"type":"iteratorValue","id":"`VU`960@Uu2ZvEQQB~jq","extraState":{"foreachBlockId":"/cKxoZ_|p-`$I*Mz-#Ul"},"fields":{"selected_iterator":"/cKxoZ_|p-`$I*Mz-#Ul"}}},"text2":{"shadow":{"type":"custom_text","id":"(*`_+Yz%_!*Yeg6M0uLG","fields":{"TEXT":"|"}}},"index":{"shadow":{"type":"custom_math_number","id":"i`~Dxn7s$nsamLQ3(rc*","fields":{"NUM":2}}}}}},"text2":{"shadow":{"type":"custom_text","id":"?nI;[sFfb~De:ZHA7($[","fields":{"TEXT":","}}}}}},"do":{"block":{"type":"set_filter_v1","id":"iURdL@(d|EVMql4^4MP:","fields":{"STATE":"APPLIED"},"inputs":{"FIELD":{"block":{"type":"text_split","id":"0Lxx/{b*R7{{_cCH`*]B","inputs":{"text1":{"shadow":{"type":"custom_text","id":"^G~]cdAS|_0x[G0FO@W]","fields":{"TEXT":""}},"block":{"type":"iteratorValue","id":"B3(xto*@ej8XHm}fgaf$","extraState":{"foreachBlockId":"/cKxoZ_|p-`$I*Mz-#Ul"},"fields":{"selected_iterator":"/cKxoZ_|p-`$I*Mz-#Ul"}}},"text2":{"shadow":{"type":"custom_text","id":"`#(O9[jxc839o1=Bo~Uy","fields":{"TEXT":"|"}}},"index":{"shadow":{"type":"custom_math_number","id":"/#Nl((NthhMJ(ong#^CG","fields":{"NUM":1}}}}}},"apply-filter-field":{"shadow":{"type":"multiselect_dropdown","id":"%U=4x?`$qF$m2i?%+1bu","fields":{"TEXT":[]}},"block":{"type":"iteratorValue","id":"E3K/rdfWZfHe*JF=tmWN","extraState":{"foreachBlockId":"/cKxoZ_|p-`$I*Mz-#Ul"},"fields":{"selected_iterator":"D#Z_U./XdRVy?k.-1;n$"}}}}}}}}}}}}}},"workspaceId":"-D0n@*aj9~7(}8znJ~Bh"}
Example Payloads
Single Filter
Payload
Product Dim.Hierarchy Lvl2 Name|Fresh;
Result
Field:
Product Dim.Hierarchy Lvl2 NameValue:
BeautyFilter applied instantly
Multi-filter example
Payload
Region Dim.Country Name|United Kingdom;
Date Dim.Year|2026;
Result
Applies both filters in one event
Test browser trigger payload
Test in iframe
const iframe = document.querySelector('iframe');
function sentMessage(type, payload) {
iframe.contentWindow.postMessage(
{
type: type, // or just `type`
payload: payload // or just `payload`
},
'*'
);
}
sentMessage('DYNAMIC FILTERING', 'Product Dim.Hierarchy Lvl2 Name|Fresh,Grocery;');
Test in embed link / preview mode
function sentMessage(type, payload) {
postMessage(
{
type: type, // or just `type`
payload: payload // or just `payload`
},
'*'
);
}
sentMessage('DYNAMIC FILTERING', 'Product Dim.Hierarchy Lvl2 Name|Fresh,Grocery;');
Example of Row-Level Filters using the OEM API
To set up this feature, you first need to add a parameter as a filter in your semantic layer. This feature lets you filter datasets dynamically using parameters or variables instead of fixed values. Parameters act as placeholders that can be controlled by user input, actions, or pre-defined logic—making dashboards more interactive and adaptable to different user needs.
To try this out:
Open your semantic layer
Click on the menu of the table, to Edit/view the table
Click on the filter icon
When filtering, make sure to select the paramter option, where the default is "value"
curl --location 'https://app.astrato.io/oem/setup' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI...' \
--data-raw '{
"email": "XXXXXX@astrato.io",
"name": "Developer Viewer",
"filterParameters": {
"[myData].[float]": 100.1,
"[greeting].[string]": "Welcome",
"[org_id].[string]": "ID8700089023"
"[region].[undefined]": null
},
"groupIds": [
"XXXXXX-07bb-429a-8795-5164fddc9e17",
"XXXXXX-2708-4444-8e71-a3cdfe6e24f8"
]
}'
ℹ️ "[region].[undefined]": null if using undefined, we remove filters. This may be used when starting a session for admins, or users that don't require data reduction.
Example data types to use:
float
string
int
boolean
date
timestamp
Unbranded login page for OEM / customer-facing analytics
If you have customer facing analytics, you can use an unbranded login page for your customers. Simply have customers login using the link below, and they'll return to this page automatically if they need to login again. Please not to access this page the URL parameter hideInfo=true is required.
Send an invite to your customers, with an unbranded invite link
To generate an unbranded signup link, from an Astrato invite URL
Generate the invite link, from the left side of the Astrato lobby
Click the invite URL (in incognito mode, or when not logged in)
Copy the new redirected URL
Add in
hideInfo=true&directly after the?









