# Errors

> Every error code the Sollar bridge can throw, what causes it, and whether retrying helps.

Source: https://miniapp.sollar.com/reference/errors/

---

Every rejected bridge call throws a `SollarError`.

```ts
interface SollarError extends Error {
  code: SollarErrorCode
  message: string       // human-readable, safe to log, never contains a token
  retriable: boolean
}
```

Branch on `code`. Never parse `message` — it is written for humans and it is translated.

```js
try {
  await sollar.files.pick({ accept: ['application/pdf'] })
} catch (e) {
  switch (e.code) {
    case 'ERR_PERMISSION_DENIED':
      showManualUploadInstead()
      break
    case 'ERR_USER_CANCELLED':
      break                          // not an error; the user changed their mind
    default:
      if (e.retriable) scheduleRetry()
      else report(e)
  }
}
```

## Permission and scope

| Code | Retriable | Cause |
|---|---|---|
| `ERR_PERMISSION_DENIED` | no | The user declined, or the tenant policy forbids it. Degrade; do not loop. |
| `ERR_SCOPE_NOT_DECLARED` | no | The scope is not in the manifest. A build-time mistake, not a runtime condition. |
| `ERR_TENANT_POLICY` | no | The administrator has disabled this capability organisation-wide. |
| `ERR_PERMISSION_REVOKED` | no | Granted earlier, revoked since. Re-request at the point of use. |

## Authentication

| Code | Retriable | Cause |
|---|---|---|
| `ERR_NOT_AUTHENTICATED` | no | No session. Call `sollar.auth.signIn()`. |
| `ERR_TOKEN_EXCHANGE_FAILED` | yes | The identity provider refused or was unreachable. |
| `ERR_AUDIENCE_MISMATCH` | no | `manifest.auth.audience` does not match a registered backend. |
| `ERR_SIGNIN_CANCELLED` | no | The user closed the authentication sheet. |

## Network

| Code | Retriable | Cause |
|---|---|---|
| `ERR_HOST_NOT_ALLOWED` | no | The host is not in `network.connect`. Requires a new version. |
| `ERR_OFFLINE` | yes | No connectivity. |
| `ERR_TIMEOUT` | yes | |
| `ERR_QUOTA_EXCEEDED` | yes | A rate limit on this bridge method. Back off. |

`ERR_HOST_NOT_ALLOWED` is the one developers hit most in their first week. It is not a bug: the
allowlist is enforced natively so that a compromised page cannot reach a new destination. Add the
host to the manifest and rebuild.

## Rooms and messages

| Code | Retriable | Cause |
|---|---|---|
| `ERR_NO_ROOM_CONTEXT` | no | Launched from the home grid, not from a room. `sollar.room.current()` returns `null` rather than throwing. |
| `ERR_ROOM_ENCRYPTED` | no | A server-side path attempted to post into an E2EE room. Structural — see [Bridge API](/reference/bridge-api/). |
| `ERR_NOT_A_MEMBER` | no | The user is not in that room. |
| `ERR_MESSAGE_REJECTED` | no | Content policy, or the user declined the send confirmation. |

## Files and storage

| Code | Retriable | Cause |
|---|---|---|
| `ERR_USER_CANCELLED` | no | The picker was dismissed. Expected, not exceptional. |
| `ERR_FILE_TOO_LARGE` | no | |
| `ERR_UNSUPPORTED_TYPE` | no | |
| `ERR_STORAGE_FULL` | no | `sollar.storage` quota is 256 KB. Use IndexedDB for more. |

## Actions

| Code | Retriable | Cause |
|---|---|---|
| `ERR_ACTION_NOT_FOUND` | no | Not declared in the manifest, or no handler registered. |
| `ERR_INPUT_INVALID` | no | Input failed `input_schema`. The message names the failing path. |
| `ERR_OUTPUT_INVALID` | no | Your handler returned something `output_schema` rejects. |
| `ERR_CONFIRMATION_REQUIRED` | no | `confirm` policy demanded a human and none was available. |
| `ERR_CONFIRMATION_DENIED` | no | The human declined. |
| `ERR_ELICITATION_UNAVAILABLE` | no | No interactive surface — a background or scheduled invocation. |

## Platform

| Code | Retriable | Cause |
|---|---|---|
| `ERR_NOT_IMPLEMENTED` | no | A `Planned` method on this client version. Guard with `sollar.supports()`. |
| `ERR_VERSION_UNSUPPORTED` | no | The mini app requires a newer bridge than this client has. |
| `ERR_INTERNAL` | yes | A host-side fault. Report it with the message. |

## Writing errors your users can act on

The same rule applies to the errors *you* return from action handlers, and it matters more there,
because an AI agent reads them and decides what to do next.

```js
// Teaches nothing. The agent will retry the same call.
throw new Error('ERR_CONFLICT')
```

```js
// Actionable. The agent stops, and can explain what happened.
throw new Error(
  'Purchase order 4471 was already approved on 2026-08-30 by another approver. ' +
  'No further approval is needed.'
)
```

An opaque code makes an agent guess. A sentence makes it stop.
