Usage Examples

This page provides practical examples of how to use the Solibo SDK in various scenarios.

Kotlin (Multiplatform / JVM / Android)

Full Application Setup (Android)

A typical integration in an Android application involves configuring the SDK with platform-specific implementations for Fingerprinter and PushTokenProvider. See the advanced setup guide for detailed implementation examples. Using a Dependency Injection framework like Koin is recommended.

ApiModule.kt

import no.solibo.oss.sdk.SoliboSDK
import no.solibo.oss.sdk.auth.Auth
import org.koin.dsl.module

val apiModule = module {
    single<SoliboSDK> {
        SoliboSDK.createMobile(
            userPoolId = "eu-west-1_...",
            clientId = "...",
            settings = get(),           // Multiplatform Settings (e.g., SharedPreferences)
            fingerprinter = get(),      // Platform implementation
            pushTokenProvider = get(),  // e.g., Firebase Messaging
            configure = {
                onRefreshFailure = {
                    // Global refresh failure handler (e.g., redirect to login)
                    get<Auth>().clearSession()
                }
                
                // Optional: custom Ktor HttpClient configuration
                httpClientConfig = {
                    install(Logging) {
                        level = LogLevel.INFO
                    }
                }
            }
        )
    }
    
    // Expose sub-APIs for easier injection
    single { get<SoliboSDK>().api }
    single { get<SoliboSDK>().auth }
}

MainApplication.kt

import android.app.Application
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin

class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        startKoin {
            androidContext(this@MainApplication)
            modules(
                platformModule, // Module containing Fingerprinter and PushTokenProvider
                apiModule
            )
        }
    }
}

Full Application Setup (iOS)

For iOS applications using Swift, you can initialize the SDK through a shared KMP helper or directly. Below is an example of initializing the SDK in your App struct or AppDelegate.

iOSApp.swift

import SwiftUI
import Shared // Your shared KMP module

@main
struct SoliboHomeApp: App {
    init() {
        // Initialize Koin and the SDK with platform implementations
        HelperKt.doInitKoin(
            fingerprinter: iOSFingerprinter(),
            pushTokenProvider: iOSPushTokenProvider(),
            userPoolId: "eu-west-1_...",
            clientId: "..."
        )
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

The HelperKt and doInitKoin methods are typically defined in your shared KMP module to bridge between Swift and Kotlin. For platform-specific implementations, see the advanced setup guide.

Fetching User Profile

import no.solibo.oss.sdk.SoliboSDK
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {
    val sdk = SoliboSDK.createMobile(
        userPoolId = "eu-west-1_...",
        clientId = "..."
    )
    // sdk.api.setInitialBaseUrl("https://home.solibo.no/api") // Optional, default is https://api.home.solibo.no
    
    try {
        val user = sdk.api.users.showUser()
        println("Hello, ${user.name?.given}!")
    } catch (e: Exception) {
        println("Error fetching user: ${e.message}")
    }
}

Updating a Board Member (Admin)

import no.solibo.oss.sdk.SoliboSDK
import no.solibo.oss.sdk.api.gen.models.UpdateBoardMemberCommand

suspend fun updateMember(sdk: SoliboSDK, boardId: String, memberId: String) {
    sdk.api.board.updateBoardMember(
        boardId = boardId,
        memberId = memberId,
        updateBoardMemberCommand = UpdateBoardMemberCommand(
            role = "Chairman"
        )
    )
}

Real-time Chat Subscription (Event Bus)

import no.solibo.oss.sdk.SoliboSDK
import no.solibo.oss.sdk.api.gen.models.ConversationMessagePayload

fun subscribeToMessages(sdk: SoliboSDK, conversationId: Long) {
    sdk.eventBus.onEvent { event ->
        val payload = event.payload
        if (payload is ConversationMessagePayload && payload.conversationId == conversationId) {
            println("New message: ${payload.message.text}")
        }
    }
}

MFA Management

import no.solibo.oss.sdk.SoliboSDK
import no.solibo.oss.sdk.api.gen.models.MfaType

suspend fun setupMfa(sdk: SoliboSDK) {
    // 1. Associate TOTP
    val association = sdk.auth.createTOTPMfa()
    println("Scan this secret in your app: ${association.secretCode}")
    
    // 2. Verify and enable
    sdk.auth.verifyCreateTOTPMfa(code = "123456")
    sdk.auth.createMfaPreference(MfaType.TOTP)
}

suspend fun handleMfaChallenge(sdk: SoliboSDK, code: String) {
    // The SDK automatically handles challenge type tracking
    val result = sdk.auth.answerMfaChallenge(code)
    if (result.tokens != null) {
        println("MFA successful!")
    }
}

Uploading a Document with Progress (Kotlin)

Use sdk.api.documents.uploadDocument(...) when you want the shared KMP upload flow, including optional progress updates:

import no.solibo.oss.sdk.SoliboSDK
import no.solibo.oss.sdk.api.UploadDocumentRequest
import no.solibo.oss.sdk.api.UploadProgress
import no.solibo.oss.sdk.api.gen.models.DocumentType

suspend fun uploadMeetingMinutes(
    sdk: SoliboSDK,
    companyId: Long,
    fileName: String,
    bytes: ByteArray,
) {
    var lastPercent = -1

    val document =
        sdk.api.documents.uploadDocument(
            request = UploadDocumentRequest(
                companyId = companyId,
                documentType = DocumentType.MEETING,
                fileName = fileName,
                bytes = bytes,
                contentType = "application/pdf",
            ),
        ) { progress: UploadProgress ->
            val percent = (progress.fraction * 100).toInt()

            if (percent != lastPercent) {
                lastPercent = percent
                println("Upload progress: $percent% (${progress.loaded}/${progress.total})")
            }
        }

    println("Uploaded document ${document.id} -> ${document.fileName}")
}

Creating a Post with Integrated Documents (Kotlin)

val result = sdk.api.integratedDocuments.createHomepagePost(
    companyId = companyId,
    command = CreatePostCommand(
        title = "Welcome",
        content = inlineDocumentImageSlot("entrance", "Building entrance"),
    ),
    image = DocumentInput.Upload("cover.jpg", coverBytes, "image/jpeg"),
    attachments = listOf(
        DocumentInput.Existing(sourceDocumentId = houseRulesDocumentId),
    ),
    inlineDocuments = mapOf(
        "entrance" to DocumentInput.Upload("entrance.jpg", entranceBytes, "image/jpeg"),
    ),
)

println("Created post ${result.resource.id}")

The SDK requests tickets and uploads new binaries concurrently. Existing inputs do not upload anything; the final backend mutation creates contextual references for them.

Creating a Newsletter with an Inline Image (Kotlin)

val result = sdk.api.integratedDocuments.createNewsletter(
    companyId = companyId,
    command = CreateNewsletterCommand(
        subject = "Summer update",
        plainText = "See the renovated entrance.",
        htmlText = "<p>See the renovated entrance:</p>" +
            inlineDocumentImageSlot(
                "entrance",
                "Renovated entrance",
                InlineDocumentImageOptions(
                    width = 600,
                    className = "newsletter-hero",
                    style = "display: block; max-width: 100%; height: auto;",
                ),
            ),
    ),
    inlineDocuments = mapOf(
        "entrance" to DocumentInput.Upload(
            "entrance.jpg",
            entranceBytes,
            "image/jpeg",
        ),
    ),
)

The persisted htmlText contains data-solibo-document-id, not a signed URL. API reads expose renderedHtmlText with an authenticated short-lived URL, while sent email uses a revocable email capability URL.

Web (React / TypeScript)

Full Application Setup (React)

A typical integration in a React application involves configuring the SDK and wrapping the app with SoliboProvider. This should be placed inside your QueryClientProvider.

sdk.ts

import { CreateSdkArgs } from '@solibo/solibo-react';

export const sdkConfig: CreateSdkArgs = {
  // domain: 'home.solibo.no', // Optional, default is api.home.solibo.no
  cognitoUserPoolId: 'eu-west-1_...',
  
  // Fingerprinter for Cognito Advanced Security (optional)
  fingerprinter: {
    print: (username: string) => {
      // Access your platform's fingerprinting logic (e.g., AmazonCognitoAdvancedSecurityData)
      return (window as any).AmazonCognitoAdvancedSecurityData?.getData(
        username,
        'user-pool-id',
        'client-id'
      );
    },
  },

  // Global refresh failure handler (e.g., redirect to login)
  refreshFailureHandler: {
    onRefreshFailure: () => {
      localStorage.removeItem('user_session');
      window.location.href = '/auth/login';
    },
  },
};

App.tsx

import { StrictMode } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { SoliboProvider } from '@solibo/solibo-react';
import { sdkConfig } from './sdk';

const queryClient = new QueryClient();

export default function App() {
  return (
    <StrictMode>
      <QueryClientProvider client={queryClient}>
        <SoliboProvider config={sdkConfig}>
          <YourRoutes />
        </SoliboProvider>
      </QueryClientProvider>
    </StrictMode>
  );
}

Using @solibo/solibo-react Hooks

The @solibo/solibo-react package provides React hooks and SoliboProvider. Import query factories/keys from @solibo/solibo-query, and import SDK clients/models from @solibo/solibo-sdk.

Fetching current user

import { useUser } from '@solibo/solibo-react'

function UserProfile() {
  const { data: user, isPending, isError } = useUser()

  if (isPending) return <div>Loading...</div>
  if (isError || !user) return <div>Error: Could not load user</div>

  return (
    <div>
      <h1>Welcome, {user.name.given}!</h1>
      <p>Email: {user.email}</p>
    </div>
  )
}

Fetching Board Members

import { useBoardMembers } from '@solibo/solibo-react'

function BoardMembersList({ companyId }: { companyId: bigint }) {
  const { data, isLoading, error } = useBoardMembers({ companyId })
  const members = data?.pages.flatMap(page => page.items) ?? []

  if (isLoading) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>

  return (
    <ul>
      {members?.map(member => (
        <li key={member.id}>{member.name.given} {member.name.family}</li>
      ))}
    </ul>
  )
}

Using a Mutation Hook

import { useCreateBoardMember } from '@solibo/solibo-react'

function AddMemberForm({ boardId }: { boardId: bigint }) {
  const mutation = useCreateBoardMember()

  const handleSubmit = (event: React.FormEvent) => {
    event.preventDefault()
      
    // .mutateAsync for promise based mutations
    mutation.mutate({
      boardId,
      createBoardMemberCommand: {
        userId: '...',
        role: 'Member'
      }
    })
  }

  return (
    <form onSubmit={handleSubmit}>
      {/* form fields */}
      <button type="submit" disabled={mutation.isPending}>
        Add Member
      </button>
      {mutation.isError && <div>Error: {mutation.error.message}</div>}
    </form>
  )
}

Document Upload and Download (React / TypeScript)

For React applications, the document wrappers live in @solibo/solibo-react. The matching framework-agnostic option factories live in @solibo/solibo-query.

Creating a post with integrated documents

import {
  inlineDocumentImageSlot,
} from '@solibo/solibo-sdk'
import { useCreateHomepagePostWithDocuments } from '@solibo/solibo-react'

function CreatePost({ companyId, entrance }: { companyId: number; entrance: File }) {
  const createPost = useCreateHomepagePostWithDocuments()

  return (
    <button
      onClick={() =>
        createPost.mutate({
          companyId,
          command: {
            title: 'Welcome',
            content: inlineDocumentImageSlot('entrance', 'Building entrance'),
          },
          attachments: [{ sourceDocumentId: 9183 }],
          inlineDocuments: {
            entrance: { file: entrance },
          },
          onUploadProgress: ({ fileName, fraction }) => {
            console.log(fileName, Math.round(fraction * 100))
          },
        })
      }
    >
      Publish
    </button>
  )
}

Creating a newsletter with an inline image

import {
  InlineDocumentImageOptions,
  inlineDocumentImageSlot,
} from '@solibo/solibo-sdk'
import { useCreateNewsletterWithDocuments } from '@solibo/solibo-react'

function CreateNewsletter({ companyId, entrance }: { companyId: number; entrance: File }) {
  const createNewsletter = useCreateNewsletterWithDocuments()

  return (
    <button onClick={() => createNewsletter.mutate({
      companyId,
      command: {
        subject: 'Summer update',
        plainText: 'See the renovated entrance.',
        htmlText: '<p>See the renovated entrance:</p>' +
          inlineDocumentImageSlot(
            'entrance',
            'Renovated entrance',
            new InlineDocumentImageOptions({
              width: 600,
              className: 'newsletter-hero',
              style: 'display: block; max-width: 100%; height: auto;',
            }),
          ),
      },
      inlineDocuments: {
        entrance: { file: entrance },
      },
    })}>
      Create newsletter
    </button>
  )
}

Uploading a private company document with the SDK facade (TypeScript)

When you want the shared upload flow without TanStack Query, call the facade’s sdk.api.documents.uploadDocument(...) helper and pass a progress callback:

import {
  DocumentType,
  SoliboClient,
} from '@solibo/solibo-sdk'

async function uploadMeetingMinutes(
  sdk: SoliboClient,
  companyId: number,
  file: File,
) {
  const bytes = new Int8Array(await file.arrayBuffer())

  const document = await sdk.api.documents.uploadDocument({
    request: {
      companyId,
      documentType: DocumentType.Meeting,
      fileName: file.name,
      bytes,
      contentType: file.type || undefined,
    },
    onUploadProgress: (progress) => {
      console.log(
        `Upload ${Math.round(progress.fraction * 100)}% (${progress.loaded}/${progress.total})`,
      )
    },
  })

  console.log(`Uploaded document ${document.id}`)
  return document
}

Uploading a private company document with @solibo/solibo-query

Use @solibo/solibo-query when you want to keep your own SDK wiring but reuse the shared TanStack mutation options:

import { useState } from 'react'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { DocumentType, SoliboClient } from '@solibo/solibo-sdk'
import { uploadDocumentMutationOptions } from '@solibo/solibo-query'

function UploadButton(props: {
  sdk: SoliboClient
  companyId: number
  file: File
}) {
  const queryClient = useQueryClient()
  const [progress, setProgress] = useState(0)
  const uploadDocument = useMutation(
    uploadDocumentMutationOptions(props.sdk, queryClient),
  )

  const handleUpload = async () => {
    await uploadDocument.mutateAsync({
      companyId: props.companyId,
      documentType: DocumentType.Other,
      file: props.file,
      onUploadProgress: ({ fraction }) => {
        setProgress(Math.round(fraction * 100))
      },
    })
  }

  return (
    <>
      <button onClick={handleUpload}>Upload document</button>
      <progress max={100} value={progress} />
    </>
  )
}

Uploading a private company document with @solibo/solibo-react

import { useState } from 'react'
import { useUploadDocument } from '@solibo/solibo-react'
import { DocumentType } from '@solibo/solibo-sdk'

function UploadButton({ companyId, file }: { companyId: number; file: File }) {
  const uploadDocument = useUploadDocument()
  const [progress, setProgress] = useState(0)

  const handleUpload = async () => {
    await uploadDocument.mutateAsync({
      companyId,
      documentType: DocumentType.Other,
      file,
      onUploadProgress: ({ fraction }) => setProgress(Math.round(fraction * 100)),
    })
  }

  return (
    <>
      <button onClick={handleUpload}>Upload document</button>
      <progress max={100} value={progress} />
    </>
  )
}

The upload wrappers automatically handle the backend’s signed upload flow for you. You pass a File, and the wrapper reuses the shared KMP DocumentsApi upload helpers to create the document, upload the bytes, verify the upload, and clean up on failure.

Uploading to a public conversation with @solibo/solibo-react

import { useState } from 'react'
import { usePublicUploadDocumentToConversation } from '@solibo/solibo-react'

function PublicConversationUploadButton(props: {
  companySlug: string
  conversationId: number
  file: File
}) {
  const uploadDocument = usePublicUploadDocumentToConversation()
  const [progress, setProgress] = useState(0)

  const handleUpload = async () => {
    await uploadDocument.mutateAsync({
      companySlug: props.companySlug,
      conversationId: props.conversationId,
      file: props.file,
      onUploadProgress: ({ fraction }) => {
        setProgress(Math.round(fraction * 100))
      },
    })
  }

  return (
    <>
      <button onClick={handleUpload}>Upload public document</button>
      <progress max={100} value={progress} />
    </>
  )
}

For multiple files, use useMultiUploadPublicDocumentToConversation(). The SDK creates one internal upload group id for the batch so the backend can render one logical upload event without making the group id a client concern.

Using the low-level signed-upload helpers (TypeScript)

Use raw interop only when you need to drive a signed-upload endpoint manually. For the normal private document flow, prefer sdk.api.documents.uploadDocument(...), the Query mutation factory, or the React wrapper above.

import { DocumentType, SoliboClient, UploadBinaryRequest } from '@solibo/solibo-sdk'

async function uploadWithSignedEndpoint(
  sdk: SoliboClient,
  companyId: bigint,
  file: File,
) {
  const uploadRequest = new UploadBinaryRequest({
    fileName: file.name,
    bytes: new Int8Array(await file.arrayBuffer()),
    contentType: file.type || undefined,
    verifyUpload: true,
  })

  const requestHeaders = new Map(
    sdk.raw.api.documents.createUploadHeaders(uploadRequest).asJsReadonlyMapView(),
  )
  sdk.raw.api.documents.setNextRequestHeaders(requestHeaders)

  const response = await sdk.raw.api.documents.createDocument(
    companyId,
    DocumentType.Other,
    undefined,
    false,
  )

  await sdk.api.documents.completeSignedUpload({
    headers: response.headers,
    request: uploadRequest,
    onUploadProgress: ({ fraction, loaded, total }) => {
      console.log(
        `Upload ${Math.round(fraction * 100)}% (${loaded}/${total})`,
      )
    },
  })

  return await response.body()
}

Uploading a document linked to another resource

import { useUploadDocumentBelongsTo } from '@solibo/solibo-react'
import { DocumentType } from '@solibo/solibo-sdk'

function UploadMeetingAttachment(props: {
  companyId: number
  meetingId: number
  file: File
}) {
  const uploadDocument = useUploadDocumentBelongsTo()

  const handleUpload = async () => {
    await uploadDocument.mutateAsync({
      companyId: props.companyId,
      documentType: DocumentType.Meeting,
      belongsToId: props.meetingId,
      file: props.file,
    })
  }

  return <button onClick={handleUpload}>Upload attachment</button>
}

Creating a directory

Use useCreateDocumentDirectory() for folders instead of the file upload wrappers:

import { useCreateDocumentDirectory } from '@solibo/solibo-react'
import { DocumentType } from '@solibo/solibo-sdk'

function CreateFolder({ companyId }: { companyId: number }) {
  const createDirectory = useCreateDocumentDirectory()

  const handleCreate = async () => {
    await createDirectory.mutateAsync({
      companyId,
      documentType: DocumentType.Board,
      file: new File(['directory'], 'Board Documents'),
    })
  }

  return <button onClick={handleCreate}>Create folder</button>
}

Resolving a private document download URL

import { useGetDocumentURL } from '@solibo/solibo-react'

function DownloadLink(props: { companyId: number; documentId: number }) {
  const { data, isPending } = useGetDocumentURL({
    companyId: props.companyId,
    documentId: props.documentId,
  })

  if (isPending) return <span>Loading...</span>
  if (!data?.url) return <span>Missing URL</span>

  return (
    <a href={data.url} target="_blank" rel="noreferrer">
      Open document
    </a>
  )
}

useGetDocumentURL() returns a UrlWrapper body with a signed url. It does not auto-redirect, which lets your app decide whether to open, preview, or download the file.

Resolving a public document URL

import { usePublicGetDocumentURL } from '@solibo/solibo-react'

function PublicDocumentLink({ documentId }: { documentId: number }) {
  const { getDocumentURL } = usePublicGetDocumentURL({
    companyIdOverride: 42,
  })

  const handleOpen = async () => {
    const { url } = await getDocumentURL(documentId)
    window.open(url, '_blank', 'noopener')
  }

  return <button onClick={handleOpen}>Open public document</button>
}

For public conversation documents, call getDocumentURL(conversationId, documentId) and provide slug in the hook params. Any size or fields options you pass to usePublicGetDocumentURL(...) are forwarded for both public company and public conversation document URLs.

MFA Management (React)

The SDK provides hooks for managing Multi-factor Authentication. The useAnswerMfaChallenge hook automatically tracks the current challenge type, so you don’t need to pass it explicitly if you just initiated an auth flow.

import { useState } from 'react'
import { 
  useRemoveMfa, 
  useCreateTOTPMfa,
  useCreateMfaPreference, 
  useAnswerMfaChallenge,
  useInitiateAuth,
  useVerifyCreateTOTPMfa,
} from '@solibo/solibo-react'
import { MfaType, AuthChallengeType } from '@solibo/solibo-sdk'

function Login() {
  const loginMutation = useInitiateAuth()
  const verifyMutation = useAnswerMfaChallenge()
  const [mfaChallenge, setMfaChallenge] = useState<AuthChallengeType | null>(null)
  const [code, setCode] = useState('')

  const handleLogin = async (e: React.FormEvent) => {
    e.preventDefault()
    const result = await loginMutation.mutateAsync({
      creds: { login: '...', pwd: '...' },
      fcmDevice: 'WEB'
    })

    if (result.challenge) {
      setMfaChallenge(result.challenge.type)
    }
  }

  const handleVerify = () => {
    // No need to pass challengeType! The SDK tracks it.
    verifyMutation.mutate({ code })
  }

  if (mfaChallenge) {
    return (
      <div>
        <p>Enter your {mfaChallenge} code:</p>
        <input value={code} onChange={e => setCode(e.target.value)} />
        <button onClick={handleVerify}>Verify</button>
      </div>
    )
  }

  return (
    <form onSubmit={handleLogin}>
      {/* login fields */}
      <button type="submit">Login</button>
    </form>
  )
}

function MfaSettings() {
  const removeMfa = useRemoveMfa()
  const setPreference = useCreateMfaPreference()

  return (
    <div>
      <button onClick={() => removeMfa.mutate()}>Disable MFA</button>
      <button onClick={() => setPreference.mutate({ mfaType: MfaType.SMS })}>
        Use SMS MFA
      </button>
    </div>
  )
}

function TotpSetup() {
  const startTotp = useCreateTOTPMfa()
  const verifyTotp = useVerifyCreateTOTPMfa()
  const [secret, setSecret] = useState<string | null>(null)
  const [code, setCode] = useState('')

  const handleStart = async () => {
    const res = await startTotp.mutateAsync()
    setSecret(res.secretCode!)
  }

  const handleVerify = () => {
    verifyTotp.mutate({ code, deviceName: 'My Phone' })
  }

  if (secret) {
    return (
      <div>
        <p>Your TOTP Secret: {secret}</p>
        <input value={code} onChange={e => setCode(e.target.value)} />
        <button onClick={handleVerify}>Verify & Enable</button>
      </div>
    )
  }

  return <button onClick={handleStart}>Enroll in TOTP</button>
}

Buildings and Sections

Kotlin: update an import draft with buildings and detached sections

import no.solibo.oss.sdk.SoliboSDK
import no.solibo.oss.sdk.api.gen.models.CompanyStatus
import no.solibo.oss.sdk.api.gen.models.CreateBuilding
import no.solibo.oss.sdk.api.gen.models.CreateBuildingFloor
import no.solibo.oss.sdk.api.gen.models.CreateSection
import no.solibo.oss.sdk.api.gen.models.SectionFractions
import no.solibo.oss.sdk.api.gen.models.SectionType
import no.solibo.oss.sdk.api.gen.models.UpdateKartverketResultCommand

suspend fun enrichImportDraft(sdk: SoliboSDK, draftId: Long) {
    val draft = sdk.api.companies.showImportDraft(draftId).body()

    val updated =
        sdk.api.companies.updateImportDraft(
            draftId,
            UpdateKartverketResultCommand(
                company =
                    draft.result.company.copy(
                        managed = true,
                        nationality = 47L,
                        status = CompanyStatus.ACTIVE,
                        buildings =
                            listOf(
                                CreateBuilding(
                                    name = "Bygg A",
                                    kartverketId = "building-1",
                                    constructionYear = 1999L,
                                    floors =
                                        listOf(
                                            CreateBuildingFloor(
                                                floorNumber = 2L,
                                                floorTypeCode = "H",
                                                kartverketId = "floor-1",
                                            ),
                                        ),
                                ),
                            ),
                        detachedSections =
                            listOf(
                                CreateSection(
                                    classification = SectionType.BOLIG,
                                    buildingId = 1L,
                                    floorId = 1L,
                                    hNr = "H0201",
                                    identifier = "A-201",
                                    fractions =
                                        SectionFractions(
                                            owner = "2/4",
                                            sectioning = "2/4",
                                            inLoanOne = "1/4",
                                            inLoanTwo = "1/4",
                                        ),
                                    roomCount = 3L,
                                    unitType = "Apartment",
                                    unitTypeCode = "APT",
                                ),
                            ),
                    ),
            ),
        ).body()

    println("Draft now has ${updated.company.buildings?.size ?: 0} building(s)")
}

CreateCompany is currently exposed through the import-draft workflow rather than @solibo/solibo-query / @solibo/solibo-react wrappers.

TypeScript SDK: read the company summary and update a section

import {
  SectionType,
  createSoliboClient,
} from '@solibo/solibo-sdk'

async function syncSectionSummary() {
  const sdk = createSoliboClient({
    auth: {
      kind: 'browser',
      userPoolId: 'eu-west-1_...',
      clientId: '...',
    },
  })

  const detailed = await sdk.api.companies.showCompanyDetailed({ companyId: 1 })
  console.log(detailed.sections?.count, detailed.sections?.businessCount)

  const updated = await sdk.api.sections.updateSection({
    companyId: 1,
    sectionId: 1,
    input: {
      companyId: 1n,
      sectionType: SectionType.Bolig,
      hnr: 'H0301',
      identifier: 'A-301',
      ownerFraction: '2/4',
      sectioningFraction: '2/4',
      wealthFraction: '2/4',
    },
  })

  console.log(updated.hNr, updated.identifier)
}

@solibo/solibo-query: build query and mutation options

import { QueryClient } from '@tanstack/query-core'
import { SectionType, SoliboClient } from '@solibo/solibo-sdk'
import {
  sectionsQueryOptions,
  updateSectionMutationOptions,
} from '@solibo/solibo-query'

function buildSectionOptions(sdk: SoliboClient) {
  const queryClient = new QueryClient()

  const sections = sectionsQueryOptions(sdk, {
    companyId: 1n,
    fields: 'id,identifier,hNr,building,floor,ownerFraction',
  })

  const updateSection = updateSectionMutationOptions(sdk, queryClient)

  return {
    sections,
    updateSection,
    updatePayload: {
      companyId: 1n,
      sectionId: 1n,
      cmd: {
        companyId: 1n,
        sectionType: SectionType.Bolig,
        hnr: 'H0301',
        identifier: 'A-301',
        ownerFraction: '2/4',
        sectioningFraction: '2/4',
        wealthFraction: '2/4',
      },
    },
  }
}

updateSectionMutationOptions(...), createSectionMutationOptions(...), createSubSectionMutationOptions(...), and deleteSectionMutationOptions(...) now also invalidate companyDetailed(...) so React apps refresh CompanyDetailed.sections after section changes.

@solibo/solibo-react: show building metadata and update a section

import {
  useCompanyDetailed,
  useSections,
  useUpdateSection,
} from '@solibo/solibo-react'
import { SectionType } from '@solibo/solibo-sdk'

function SectionsPanel({ companyId }: { companyId: bigint }) {
  const company = useCompanyDetailed({ id: companyId })
  const sections = useSections({
    companyId,
    fields: 'id,identifier,hNr,building,floor,ownerFraction',
  })
  const updateSection = useUpdateSection()

  const items = sections.data?.pages.flatMap(page => page.items) ?? []

  return (
    <div>
      <p>Total sections: {String(company.data?.sections?.count ?? 0n)}</p>
      {items.map(section => (
        <button
          key={String(section.id)}
          onClick={() =>
            updateSection.mutate({
              companyId,
              sectionId: section.id,
              cmd: {
                companyId,
                sectionType: SectionType.Bolig,
                hnr: 'H0301',
                identifier: 'A-301',
                ownerFraction: '2/4',
                sectioningFraction: '2/4',
                wealthFraction: '2/4',
              },
            })
          }
        >
          {section.identifier} ({section.building?.name} / {section.floor?.floorTypeCode})
        </button>
      ))}
    </div>
  )
}

Real-time Notifications (Event Bus)

import { useEffect } from 'react'
import { useSoliboApi } from '@solibo/solibo-react'

function NotificationListener() {
  const { eventBus } = useSoliboApi()

  useEffect(() => {
    // onEvent returns an unsubscribe function
    const unsubscribe = eventBus.onEvent(({ payload }) => {
      // Discriminator-based type check
      if (payload.type === 'solibo.common.infrastructure.websockets.payload.ConversationMessagePayload') {
        alert('New message received!')
      }
    })

    return () => unsubscribe()
  }, [eventBus])

  return <div>Monitoring for live updates...</div>
}

Machine-to-Machine (M2M)

For backend services, scripts, or other non-interactive clients, the SDK supports M2M authentication using OAuth 2.0 Client Credentials flow.

Setup (Kotlin)

import no.solibo.oss.sdk.SoliboSDK
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {
    val sdk = SoliboSDK.createM2M(
        clientId = "your-client-id",
        clientSecret = "your-client-secret"
    )
    
    val user = sdk.api.users.showUser()
    println("Service acting as: ${user.name?.given}")
}

Setup (TypeScript)

import { createSoliboClient } from '@solibo/solibo-sdk';

async function run() {
  const sdk = createSoliboClient({
    auth: {
      kind: 'm2m',
      clientId: 'your-client-id',
      clientSecret: 'your-client-secret',
    },
  });
  
  const user = await sdk.api.users.showUser({});
  console.log(`Service acting as: ${user.name?.given}`);
}

M2M clients are normally scoped server-side, so the SDK omits scopes from the token request unless you explicitly provide them.


Solibo AS