Skip to content

Errors

Every rejected bridge call throws a SollarError.

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.

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)
}
}
CodeRetriableCause
ERR_PERMISSION_DENIEDnoThe user declined, or the tenant policy forbids it. Degrade; do not loop.
ERR_SCOPE_NOT_DECLAREDnoThe scope is not in the manifest. A build-time mistake, not a runtime condition.
ERR_TENANT_POLICYnoThe administrator has disabled this capability organisation-wide.
ERR_PERMISSION_REVOKEDnoGranted earlier, revoked since. Re-request at the point of use.
CodeRetriableCause
ERR_NOT_AUTHENTICATEDnoNo session. Call sollar.auth.signIn().
ERR_TOKEN_EXCHANGE_FAILEDyesThe identity provider refused or was unreachable.
ERR_AUDIENCE_MISMATCHnomanifest.auth.audience does not match a registered backend.
ERR_SIGNIN_CANCELLEDnoThe user closed the authentication sheet.
CodeRetriableCause
ERR_HOST_NOT_ALLOWEDnoThe host is not in network.connect. Requires a new version.
ERR_OFFLINEyesNo connectivity.
ERR_TIMEOUTyes
ERR_QUOTA_EXCEEDEDyesA 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.

CodeRetriableCause
ERR_NO_ROOM_CONTEXTnoLaunched from the home grid, not from a room. sollar.room.current() returns null rather than throwing.
ERR_ROOM_ENCRYPTEDnoA server-side path attempted to post into an E2EE room. Structural — see Bridge API.
ERR_NOT_A_MEMBERnoThe user is not in that room.
ERR_MESSAGE_REJECTEDnoContent policy, or the user declined the send confirmation.
CodeRetriableCause
ERR_USER_CANCELLEDnoThe picker was dismissed. Expected, not exceptional.
ERR_FILE_TOO_LARGEno
ERR_UNSUPPORTED_TYPEno
ERR_STORAGE_FULLnosollar.storage quota is 256 KB. Use IndexedDB for more.
CodeRetriableCause
ERR_ACTION_NOT_FOUNDnoNot declared in the manifest, or no handler registered.
ERR_INPUT_INVALIDnoInput failed input_schema. The message names the failing path.
ERR_OUTPUT_INVALIDnoYour handler returned something output_schema rejects.
ERR_CONFIRMATION_REQUIREDnoconfirm policy demanded a human and none was available.
ERR_CONFIRMATION_DENIEDnoThe human declined.
ERR_ELICITATION_UNAVAILABLEnoNo interactive surface — a background or scheduled invocation.
CodeRetriableCause
ERR_NOT_IMPLEMENTEDnoA Planned method on this client version. Guard with sollar.supports().
ERR_VERSION_UNSUPPORTEDnoThe mini app requires a newer bridge than this client has.
ERR_INTERNALyesA host-side fault. Report it with the message.

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.

// Teaches nothing. The agent will retry the same call.
throw new Error('ERR_CONFLICT')
// 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.