Mini app ↔ superapp API
This section defines the integration API between mini apps and the Sollar superapp. It is the contract: what a mini app may ask Sollar for, what Sollar may ask a mini app to do, and what crosses the boundary in each direction.
Three planes
Section titled “Three planes”The contract has three surfaces, and they have different trust properties. Confusing them is the root of most integration mistakes.
┌──────────────────────────────────────────┐ │ SOLLAR CLIENT │ │ ┌────────────────────────────────────┐ │ ① bridge ◀──────┼──│ mini app (WebView, own origin) │ │ in-process │ └───────────────┬────────────────────┘ │ │ │ │ │ ┌───────────────▼────────────────────┐ │ │ │ native runtime — permission gate, │ │ │ │ network gate, token exchange │ │ │ └───────────────┬────────────────────┘ │ └──────────────────┼───────────────────────┘ │ ② HTTPS, short-lived │ audience-scoped token ┌────────▼─────────┐ │ YOUR BACKEND │ └────────┬─────────┘ │ ③ signed server calls ┌────────▼─────────┐ │ SOLLAR SERVERS │ └──────────────────┘| Plane | Direction | Transport | Authenticated by | Trust |
|---|---|---|---|---|
| ① Bridge | mini app → runtime | In-process message channel | The signed manifest | The runtime trusts the manifest, never the page |
| ② Backend | mini app → your server | HTTPS | RFC 8693 exchanged token | Your server trusts the token, never the client |
| ③ Server | your server ↔ Sollar | HTTPS | HMAC-SHA256 request signature | Mutual, over a shared signing secret |
Plane ① is not a security boundary you control. The user’s device runs the WebView. Anything your JavaScript receives, a determined user can also produce. The boundary that matters is ②, and it is enforced by your server checking a token.
Plane ① — the bridge
Section titled “Plane ① — the bridge”Documented in full at Bridge API. Two properties define it:
Web first. Camera, microphone, geolocation, files, notifications and biometrics are standard Web APIs, not bridge methods. The bridge covers only the Sollar domain — identity, rooms, messages, Sollar storage, agent actions.
The manifest is the ceiling. Every permission, every reachable host and every agent action is declared in a signed file. There is no runtime negotiation that widens any of them.
Plane ② — mini app to your backend
Section titled “Plane ② — mini app to your backend”Your mini app calls your own API over fetch, restricted to the hosts in network.connect, with a
token obtained from the bridge.
const { access_token } = await sollar.auth.getToken()
const res = await fetch('https://erp.acme.example/api/approvals', { headers: { Authorization: `Bearer ${access_token}` }})The token is issued by Keycloak through an RFC 8693 token exchange performed by the native
runtime, not by the page. It is short-lived and its aud is your backend and nothing else — if it
leaks, another mini app’s backend refuses it.
Your backend verifies signature, aud, iss and exp before doing anything:
import { createRemoteJWKSet, jwtVerify } from 'jose'
const jwks = createRemoteJWKSet( new URL('https://auth.sollar.com/realms/acme-corp/protocol/openid-connect/certs'))
const { payload } = await jwtVerify(token, jwks, { issuer: 'https://auth.sollar.com/realms/acme-corp', audience: 'acme-erp-backend',})
const appUserId = payload.sub // the identity you may act onconst actor = payload.act?.sub // present when an AI agent is actingFull detail in Authentication.
Plane ③ — your backend to Sollar
Section titled “Plane ③ — your backend to Sollar”When your backend calls Sollar — to place a card in a room, to update a badge — the request is signed. The recipe is Slack’s, which is the best-documented of its kind:
base = "v0:" + timestamp + ":" + raw_bodysignature = "v0=" + hex(HMAC_SHA256(signing_secret, base))headers X-Sollar-Signature, X-Sollar-Request-TimestampVerify with a constant-time comparison, and reject a timestamp outside a short window to block replay. HMAC-SHA256, not SHA-1 — the scheme works with SHA-1, but it is not a defensible choice for a new design.
The same signature protects webhooks Sollar sends to you. Verify them the same way, before parsing the body.
Identity across the planes
Section titled “Identity across the planes”One human, four identifiers, deliberately:
| Identifier | Scope | Who sees it |
|---|---|---|
sollar_id | Global, immutable | Nobody. Never leaves Sollar infrastructure. |
user_id | Unique within a tenant | Tenant administrators, administrative APIs |
app_user_id | Unique per (user, mini app) | Your mini app and your backend |
vendor_id | Unique per (user, developer) | Optional, with explicit consent |
app_user_id = HMAC(tenant_key, sollar_id ‖ mini_app_id). Two mini apps serving the same person get
different identifiers that cannot be correlated. A breach of your database does not expose the
underlying Sollar identity, and the tenant can rotate its key to break every correlation at once.
vendor_id exists for the legitimate case where one company runs several mini apps and wants to
recognise the same user across them. It requires explicit consent and is not the default.
Rate limits
Section titled “Rate limits”| Plane | Limit |
|---|---|
| Bridge — read methods | 120/minute per mini app instance |
Bridge — message.send | 10/minute, each with a confirmation |
Bridge — actions.elicit | 5 per action invocation |
| Server → Sollar | 600/minute per vendor, burst 60 |
Exceeding a bridge limit throws ERR_QUOTA_EXCEEDED with retriable: true. Back off; do not spin.
Versioning and compatibility
Section titled “Versioning and compatibility”- The bridge carries its own version. Read it with
sollar.version; test for a method withsollar.supports(). - Server APIs are versioned in the path:
/v1/…. - Removing anything requires a major version and a published deprecation window.
- A raised compatibility baseline never breaks an installed mini app.
Error model
Section titled “Error model”The bridge throws SollarError with a stable code and a retriable flag —
see Errors.
Server planes use RFC 9457 problem details:
{ "type": "https://miniapp.sollar.com/errors/room-encrypted", "title": "Room is end-to-end encrypted", "status": 409, "detail": "Server-side posting is not possible in an encrypted room. Send from the user's client with sollar.message.send(), or use an agent that is a member of the room.", "instance": "/v1/rooms/!abc:sollar.com/messages"}detail is written to be actionable — for a human reading a log, and for an AI agent deciding
whether to try something else. A bare error code teaches neither.