Holaboss:Docs
SDK

Portal data

The customer's tables and files — createApiTransport / createPortalData and the PortalData surface (listRows, insertRow, patchRow, deleteRow, uploadFile, fileLink).

Reference · SDK

The portal-data surface is what a customer-facing portal reads and writes: the org's shared tables (orders, services, deliverables, and each running service's own data) and the customer's files. Everything here is scoped by the session token to one signed-in customer.

Setup

Both factories take the same { baseUrl, token } config. createApiTransport is the chat/thread transport; createPortalData is the tables-and-files surface. Point baseUrl at your same-origin proxy ("") or the gateway, and give token a getter that returns a fresh access token.

hola-client.ts
import { createApiTransport, createPortalData } from "@holaboss/client";

const config = {
  baseUrl: "",                                       // same-origin proxy answers /api/*
  token: async () => (await currentAccessToken()) ?? "",
};

export const chat = createApiTransport(config);
export const data = createPortalData(config);

PortalData

Tables & rows

listTables(): Promise<PortalTable[]>

The tables this customer can see. A table the org hasn't shared is absent, not empty — it answers as if it didn't exist.

listRows(slug: string, limit?: number): Promise<PortalRowPage>

Rows plus how many there really are. The server caps a page, so PortalRowPage carries the total — returning the array alone would let a dashboard present "the most recent 100" as "all of them".

insertRow(slug: string, row: Record<string, unknown>): Promise<string>   // → new row id
patchRow(slug: string, rowId: string, patch: Record<string, unknown>): Promise<number>
deleteRow(slug: string, rowId: string): Promise<number>

Writes respect the org's schema: insertRow/patchRow accept only the table's writable_fields (pass an empty string in a patch to clear a field), and deleteRow only removes rows the org marked deletable and only when nothing depends on them — otherwise it rejects with the reason.

Files

listFiles(): Promise<PortalFile[]>
uploadFile(file: File, message?: string): Promise<{ rowId: string }>
fileLink(rowId: string): Promise<{ url: string; filename: string }>

uploadFile sends bytes and the server writes the row (a storage key is never the caller's to state). fileLink is not a plain URL — the download route needs the customer's token, and an <a href> navigation carries no Authorization header, so a direct link would 401. Call fileLink to fetch a signed URL the browser can then follow with no header of its own:

const { url, filename } = await data.fileLink(rowId);
// <a href={url} download={filename}>Download</a>

Everything is gated twice: by the org's per-table read/write/delete permissions and by the session token. There's no way to reach a table or a row the signed-in customer isn't allowed to.

Types

PortalTable, PortalRow, PortalRowPage, PortalField, PortalFile, PortalDocument, PortalDocumentSummary, and the PortalConflict error are all exported from @holaboss/client.

On this page