# Mini app ↔ superapp API

> The integration contract between a mini app and Sollar — three planes, one identity model, and what crosses each boundary.

Source: https://miniapp.sollar.com/reference/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.

> **NOTE**
This is the API *of the mini app environment*. Whether Sollar's servers will offer a general
integration API outside that environment is not decided, and nothing here should be read as
committing to one. If you are looking for "the Sollar API" in the sense of a public server-to-server
platform API, it does not exist yet.

## 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

Documented in full at [Bridge API](/reference/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

Your mini app calls your own API over `fetch`, restricted to the hosts in `network.connect`, with a
token obtained from the bridge.

```js
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:

```js

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
```

> **DANGER**
`payload.sub` is the only user identity you may authorise against. The `app_user_id` your JavaScript
received from `sollar.identity.get()` is a display convenience, and treating it as an authorisation
input is the defining vulnerability of this platform category.

Full detail in [Authentication](/guides/authentication/).

## 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_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.

> **CAUTION**
**Plane ③ cannot post into an end-to-end encrypted room.** Your server is not a cryptographic member
and Sollar's servers do not hold the keys. In the Enterprise and Sovereign tiers, encryption is on
by default, so this is the normal case.

Use `sollar.message.send()` from the user's client, or an AI agent that is a cross-signed member of
the room. Design for this before you build the notification feature, not after.

## 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

| 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

- 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](/reference/baseline/) never breaks an installed mini app.

## Error model

The bridge throws `SollarError` with a stable `code` and a `retriable` flag —
see [Errors](/reference/errors/).

Server planes use RFC 9457 problem details:

```json
{
  "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.
