Skip to content

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.

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 │
└──────────────────┘
PlaneDirectionTransportAuthenticated byTrust
Bridgemini app → runtimeIn-process message channelThe signed manifestThe runtime trusts the manifest, never the page
Backendmini app → your serverHTTPSRFC 8693 exchanged tokenYour server trusts the token, never the client
Serveryour server ↔ SollarHTTPSHMAC-SHA256 request signatureMutual, 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.

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.

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 on
const actor = payload.act?.sub // present when an AI agent is acting

Full detail in Authentication.

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_body
signature = "v0=" + hex(HMAC_SHA256(signing_secret, base))
headers X-Sollar-Signature, X-Sollar-Request-Timestamp

Verify 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.

One human, four identifiers, deliberately:

IdentifierScopeWho sees it
sollar_idGlobal, immutableNobody. Never leaves Sollar infrastructure.
user_idUnique within a tenantTenant administrators, administrative APIs
app_user_idUnique per (user, mini app)Your mini app and your backend
vendor_idUnique 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.

PlaneLimit
Bridge — read methods120/minute per mini app instance
Bridge — message.send10/minute, each with a confirmation
Bridge — actions.elicit5 per action invocation
Server → Sollar600/minute per vendor, burst 60

Exceeding a bridge limit throws ERR_QUOTA_EXCEEDED with retriable: true. Back off; do not spin.

  • The bridge carries its own version. Read it with sollar.version; test for a method with sollar.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.

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.