The auth object tells the platform how users connect their accounts with your service.
The simplest auth method. The user pastes their API key into a form, and the platform stores it securely.
auth: {
type: 'bearer_token',
// Keys that must NEVER be sent to the frontend
sensitiveKeys: ['accessToken'],
// Default credential values (empty = user must provide)
config: {
accessToken: '',
},
// Verify the token works by calling a "who am I" endpoint
userDetails: {
url: 'https://api.example.com/users/me',
method: 'GET',
headers: {
Authorization: 'Bearer [[accessToken]]',
'Content-Type': 'application/json',
},
bodyType: 'json',
body: {},
mapping: {
uid: '$.user.id', // Unique user identifier → metadata
name: '$.user.name', // Display name → metadata
},
},
}How it works:
- User enters their API key in the settings form
- Platform calls
saveMiniAppCredentialsmutation - If
get_tokenis defined, it's executed to exchange/validate credentials - Tokens and secrets are stored in
InstalledMiniApps.credentials - If
userDetailsis defined, mapped store/account fields are stored inInstalledMiniApps.metadata - Status set to
connected
Each installed miniapp persists three server-side bags on InstalledMiniApps:
| Field | Contents | [[key]] | {{key}} | Exposed to UI? |
|---|---|---|---|---|
credentials | Tokens, secrets, registration outputs (accessToken, webhookId, …) | Yes | No | Never |
metadata | Full userDetails.mapping result (storeId, storeName, uid, …) | Yes | Yes (runtime) | Never |
userInput | User-entered install form values | No | Yes | Pre-fill only |
[[key]] resolves from credentials first, then metadata (credentials win on key collision). Use camelCase keys in mappings (e.g. storeId) even when the HTTP header name differs (Store-Id: '[[storeId]]').
For services that require OAuth 2.0 authorization (Salla, Google, etc.).
auth: {
type: 'oauth2',
sensitiveKeys: ['accessToken', 'refreshToken'],
// OAuth configuration (available as {{key}} placeholders)
config: {
client_id: 'your-client-id',
client_secret: 'your-client-secret',
scope: 'read write',
response_type: 'code',
grant_type: 'authorization_code',
accessToken: '',
refreshToken: '',
},
// Build the authorization redirect URL
auth_url: {
url: 'https://provider.com/oauth/authorize?client_id={{client_id}}&scope={{scope}}&response_type={{response_type}}&redirect_uri={{redirect_uri}}',
method: 'GET',
headers: {},
bodyType: 'json',
body: {},
mapping: {},
},
// Exchange authorization code for tokens
get_token: {
url: 'https://provider.com/oauth/token',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
bodyType: 'json',
body: {
client_id: '{{client_id}}',
client_secret: '{{client_secret}}',
grant_type: '{{grant_type}}',
code: '{{code}}',
redirect_uri: '{{redirect_uri}}',
},
// Map response to credential keys
mapping: {
accessToken: '$.access_token',
refreshToken: '$.refresh_token',
expiresIn: '$.expires_in',
},
},
// Refresh expired tokens
refresh_token: {
url: 'https://provider.com/oauth/token',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
bodyType: 'json',
body: {
client_id: '{{client_id}}',
client_secret: '{{client_secret}}',
grant_type: 'refresh_token',
refresh_token: '[[refreshToken]]',
},
mapping: {
accessToken: '$.access_token',
refreshToken: '$.refresh_token',
expiresIn: '$.expires_in',
},
},
// Auto-refresh on 401 responses
auto_refresh: true,
// Fetch user identity after authentication
userDetails: {
url: 'https://api.provider.com/v2/oauth2/user/info',
method: 'GET',
headers: {
Authorization: 'Bearer [[accessToken]]',
'Content-Type': 'application/json',
},
bodyType: 'json',
body: {},
mapping: {
uid: '$.data.id',
name: '$.data.name',
},
},
// Post-auth setup (see §4.4)
registrationRequests: [],
}OAuth 2.0 Flow:
User clicks "Connect" in Marketplace
│
▼
┌─────────────────────┐
│ Browser popup opens │
│ GET /:appNs/oauth2 │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ Platform builds │
│ auth_url & redirects │
│ to provider │
└────────┬────────────┘
│ User authorizes
▼
┌─────────────────────┐
│ Provider redirects │
│ back with ?code=xxx │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ Platform executes │
│ get_token request │
│ (code → tokens) │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ Platform executes │
│ userDetails request │
│ (extract uid, name) │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ Tokens stored in │
│ credentials; │
│ userDetails → metadata │
│ registrationRequests │
│ executed (if any) │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ Popup closes, main │
│ window refreshes │
└─────────────────────┘When auto_refresh: true is set and any action request returns a 401 response:
- Platform automatically calls the
refresh_tokenrequest - New credentials are extracted via the mapping
- Credentials are updated in the database
- The original failed request is retried with the new token
This is handled transparently — no user interaction required.
Registration requests are API calls executed once right after a user successfully authenticates. Use them to:
- Register webhooks with the provider
- Fetch initial configuration (store ID, workspace info)
- Subscribe to events
auth: {
// ...auth config...
registrationRequests: [
// Register your webhook URL with the provider
{
url: 'https://api.provider.com/webhooks',
method: 'POST',
headers: {
Authorization: 'Bearer [[accessToken]]',
'Content-Type': 'application/json',
},
bodyType: 'json',
body: {
url: '{{webhookUrl}}', // Auto-injected: your webhook endpoint
events: ['order.created', 'customer.updated'],
secret: '{{webhookSecret}}',
},
mapping: {
webhookId: '$.id', // Saved to credentials for later reference
},
},
// Prefer auth.userDetails for store profile; use registrationRequests
// only for one-off setup outputs like webhook IDs.
{
url: 'https://api.provider.com/webhooks/subscribe',
method: 'POST',
headers: {
Authorization: 'Bearer [[accessToken]]',
'Store-Id': '[[storeId]]', // Resolved from metadata
},
bodyType: 'json',
body: {
url: '{{webhookUrl}}',
},
mapping: {
webhookSubscriptionId: '$.id',
},
},
],
}Available placeholders in registration requests:
| Placeholder | Source | Description |
|---|---|---|
[[accessToken]] | Stored credentials | The OAuth access token |
[[refreshToken]] | Stored credentials | The OAuth refresh token |
[[storeId]] | Stored metadata | Platform context from userDetails (e.g. Zid) |
[[any_key]] | credentials + metadata | Any key from either bag (credentials win) |
{{webhookUrl}} | System-generated | Your miniapp's webhook endpoint URL |
{{subdomain}} | System context | The tenant's subdomain |
{{uid}} | metadata / top-level uid | The authenticated user's ID |
{{configKey}} | auth.config | Any key from the auth config object |
Important: Object mappings in
registrationRequestsare persisted tocredentials.userDetailsmappings are persisted tometadatain full. Both bags are available for[[key]]placeholders in sync headers, actions, and automations.