Bridge API
sollar.* is the only non-standard global a mini app gets. It is deliberately small.
The rule that shapes this API
Section titled “The rule that shapes this API”Web first. The bridge covers only what the web cannot do.
Camera, microphone, geolocation, file access, notifications, biometrics — these are standard Web APIs that the engine already provides, with its own permission prompts. Sollar does not wrap them, because a wrapper would be a worse version of something you already have.
// Right — the platform's own API, with the engine's own prompt.const stream = await navigator.mediaDevices.getUserMedia({ video: true })const position = await new Promise((ok, err) => navigator.geolocation.getCurrentPosition(ok, err))const cred = await navigator.credentials.get({ publicKey }) // WebAuthn// Wrong — these do not exist, and will not.sollar.camera.take()sollar.getLocation()sollar.getDeviceContacts()This is not minimalism for its own sake. Apple’s Guideline 4.7.2 states that an app “may not extend
or expose native platform APIs or technologies to the software without prior permission from
Apple.” Every native capability added to this bridge is a compliance question requiring Apple’s
prior approval — a business-development timeline, not a sprint. sollar.sendMessage() is not
extending a platform API; sollar.getDeviceContacts() is.
What you came looking for and will not find
Section titled “What you came looking for and will not find”If you arrive from WeChat, you will look for these. They are absent, on purpose:
| Sollar | Why | |
|---|---|---|
wx.requestPayment | — | No in-app payment API. See Store compliance for how money works. |
<web-view> | sollar.ui.openExternal() | The embedded-webview component is the single largest vulnerability vector in this class of platform. External content opens in the system browser, with a visible address bar and lock. |
wx.getLocation | navigator.geolocation | The web API already has a permission prompt the engine controls. |
wx.chooseImage | <input type="file"> / getUserMedia | Same. |
wx.setStorage | localStorage, IndexedDB | Partitioned per mini app by the engine’s own origin isolation. |
wx.request | fetch | Restricted to the manifest’s network allowlist, enforced natively. |
sollar.app
Section titled “sollar.app”sollar.version: string // bridge version, e.g. "1.0.0"sollar.supports(method: string): boolean // feature detectionsollar.app.id: stringsollar.app.version: stringsollar.app.locale: string // BCP 47, the user's Sollar localesollar.app.theme: 'light' | 'dark'sollar.app.onThemeChange(cb: (theme) => void): () => voidsollar.app.launchParams: Record<string, string> // deep-link parameterssollar.identity
Section titled “sollar.identity”sollar.identity.get(): Promise<Identity>interface Identity { app_user_id: string // stable for (this user, this mini app). Not correlatable. display_name?: string // requires identity.basic avatar_url?: string // requires identity.basic email?: string // requires identity.email tenant_id: string locale: string}app_user_id is derived as HMAC(tenant_key, sollar_id ‖ mini_app_id). Two mini apps serving the
same human receive different, non-correlatable identifiers. Without that, a mini app store becomes a
tracking network by construction.
sollar.auth
Section titled “sollar.auth”sollar.auth.getToken(): Promise<{ access_token: string, expires_in: number }>sollar.auth.signIn(): Promise<void> // external-oauth mode onlysollar.auth.signOut(): Promise<void>sollar.auth.state(): Promise<'signed-in' | 'signed-out'>Call getToken() before each use. Do not cache it, do not persist it, do not pass it anywhere
but your own backend. Tokens are short-lived and the host renews them; caching buys you nothing and
widens the window on revocation.
The mini app never sees a refresh token, never holds a client_secret, and never receives any
long-lived secret. See Authentication.
sollar.room
Section titled “sollar.room”sollar.room.current(): Promise<Room | null> // requires room.contextinterface Room { room_id: string name?: string is_encrypted: boolean member_count: number}current() returns null when the mini app was launched from the home grid rather than from inside
a room. Handle that — it is the common case, not the exception.
Check is_encrypted. It changes what your backend can do; see below.
sollar.message
Section titled “sollar.message”sollar.message.send(input: { room_id: string, body: string, card?: Card}): Promise<{ event_id: string }> // requires room.sendSends as the user, from the user’s client, and prompts for confirmation each time. A mini app cannot post silently under someone’s name.
sollar.files
Section titled “sollar.files”sollar.files.pick(opts?: { accept?: string[], // MIME types multiple?: boolean}): Promise<SollarFile[]> // requires files.read
sollar.files.save(input: { name: string, blob: Blob}): Promise<{ file_id: string }> // requires files.writeThis is Sollar storage — the files the user already has in Sollar. For files on the device, use
<input type="file"> and the File System Access API, which are standard and need no bridge.
sollar.actions
Section titled “sollar.actions”sollar.actions.handle( name: string, handler: (input: unknown, ctx: ActionContext) => Promise<unknown>): void
sollar.actions.elicit(req: { prompt: string, schema: JSONSchema}): Promise<unknown | null> // null = the human refused or cancelled
sollar.actions.progress(message: string): voidinterface ActionContext { invoked_by: 'agent' | 'user' agent_id?: string confirmed: boolean // did a human approve this specific call?}Register a handler for every action declared in the manifest. An action declared without a handler fails validation at build time.
See Agent actions for the full call cycle and for how to write a description an agent uses correctly.
sollar.ui
Section titled “sollar.ui”sollar.ui.setTitle(title: string): voidsollar.ui.toast(input: { message: string, kind?: 'info' | 'success' | 'error' }): voidsollar.ui.confirm(input: { title: string, body: string }): Promise<boolean>sollar.ui.openExternal(url: string): Promise<void>sollar.ui.close(): voidsollar.ui.share(input: { title: string, url: string }): Promise<void>openExternal() hands the URL to the system browser. It does not open an in-app browser, and there
is no configuration that makes it one — see the <web-view> row above.
sollar.storage
Section titled “sollar.storage”sollar.storage.get(key: string): Promise<unknown>sollar.storage.set(key: string, value: unknown): Promise<void>sollar.storage.remove(key: string): Promise<void>sollar.storage.clear(): Promise<void>A small key-value store that survives reinstall for the same (user, mini app) and, unlike
localStorage, syncs across the user’s devices. Quota is 256 KB. For anything larger, use IndexedDB
— which is local, partitioned to your synthetic origin, and not synced.
sollar.permissions
Section titled “sollar.permissions”sollar.permissions.query(scope: PermissionScope): Promise<'granted' | 'denied' | 'prompt'>sollar.permissions.request(scope: PermissionScope): Promise<'granted' | 'denied'>Only scopes declared in the manifest can be requested. Requesting an undeclared scope throws
ERR_SCOPE_NOT_DECLARED — it does not prompt the user.
Request at the moment of use, with the action visible on screen. A wall of prompts at launch is both a worse experience and a review finding.
Errors
Section titled “Errors”Every rejection is a SollarError:
interface SollarError extends Error { code: SollarErrorCode message: string // human-readable, safe to log, never contains a token retriable: boolean}try { await sollar.message.send({ room_id, body })} catch (e) { if (e.code === 'ERR_PERMISSION_DENIED') { // the user said no — degrade, do not re-prompt in a loop } else if (e.retriable) { // transient; back off and retry }}The full list is in Errors.
TypeScript
Section titled “TypeScript”The canonical type definitions are published at
sollar.d.ts. sollar create-mini-app installs them; otherwise:
curl -O https://miniapp.sollar.com/schemas/sollar.d.tsThe definition file is the contract. Where this page and sollar.d.ts disagree, the .d.ts wins.