DocsDevelopersReact SDK

React SDK

@rootcx/sdk provides React hooks, components, and a direct HTTP client for interacting with the RootCX Core from your frontend.


Setup

npm install @rootcx/sdk

Requires React 18+. ESM-only. Works with Vite, Next.js, and other modern bundlers.

import { RuntimeProvider } from "@rootcx/sdk";

createRoot(document.getElementById("root")!).render(
  <RuntimeProvider baseUrl="https://<your-ref>.rootcx.com">
    <App />
  </RuntimeProvider>
);

All hooks require a RuntimeProvider ancestor.


Hooks

useAuth

Return Type Description
user AuthUser | null Current authenticated user
isAuthenticated boolean True if user is not null
loading boolean True during auth operations
authMode AuthMode | null Server auth config (providers, password enabled, magic link enabled)
login (email, password) => Promise<void> Password login
register (data: RegisterInput) => Promise<void> Create account
logout () => Promise<void> Destroy session
oidcLogin (providerId) => Promise<void> Redirect to OIDC provider
magicLinkConsume (token) => Promise<void> Consume a magic link token

useAppCollection

useAppCollection<T>(appId: string, entity: string, query?: QueryOptions)
Return Type Description
data T[] Records matching query
total number Total count (ignoring limit/offset)
loading boolean
error string | null
create (data) => Promise<T> Create one record
bulkCreate (data[]) => Promise<T[]> Create multiple records
update (id, data) => Promise<T> Partial update
remove (id) => Promise<void> Delete
refetch () => void Re-fetch manually

useAppRecord

useAppRecord<T>(appId: string, entity: string, recordId: string | null)
Return Type Description
data T | null The record (null if not loaded or recordId is null)
loading boolean
error string | null
update (data) => Promise<T> Partial update
remove () => Promise<void> Delete
refetch () => void

If recordId is null, nothing is fetched.


useCoreCollection

useCoreCollection<T>(entity: string)

Read-only access to platform collections (currently users). Hits GET /api/v1/users directly.

Return Type Description
data T[] Records
loading boolean
error string | null
refetch () => void

No mutations. Core collections are managed by the platform.

Do not use useAppCollection with core:users as the entity — it hits the per-app collections API and returns 404. Use useCoreCollection("users") instead.

usePermissions

Return Type Description
roles string[] Resolved roles for current user
permissions string[] Flat list of permission keys
can (key) => boolean Check permission (supports wildcards: *, app:crm:*)
loading boolean
error string | null
refetch () => void

Client-side only. The Core enforces RBAC server-side on every request.


useCrons

useCrons(appId: string)
Return Type Description
data CronSchedule[] All crons for this app
loading boolean
error string | null
create (input: CreateCronInput) => Promise<CronSchedule> Create a cron
update (id, patch: UpdateCronInput) => Promise<CronSchedule> Update
remove (id) => Promise<void> Delete
trigger (id) => Promise<{ msgId: number }> Fire immediately
refetch () => void

useIntegration

useIntegration(integrationId: string)
Return Type Description
connected boolean True if at least one connection exists
connections Connection[] All connections for this integration
loading boolean
connect () => Promise<void | { type: "credentials", schema }> Start OAuth (void = redirect) or get credential schema
submitCredentials (creds) => Promise<void> Submit manual credentials
remove (connectionId: string) => Promise<void> Remove connection
call (action, input?) => Promise<unknown> Execute an integration action

useIdentity

useIdentity<T>(identityKind: string, query?: QueryOptions)

Federated query across all apps sharing the same identityKind.

Return Type Description
data IdentityRecord<T>[] Records with _source: { app, entity } metadata
total number Total count
loading boolean
error string | null
refetch () => void

useRuntimeClient

const client = useRuntimeClient();

Returns the RuntimeClient instance from context. Use for imperative API calls in event handlers.


Components

AuthGate

Pre-built login/registration UI. Wrap your app to show auth when not authenticated.

<AuthGate appTitle="My App">
  {({ user, logout }) => <MyApp />}
</AuthGate>
Prop Type Description
appTitle string Shown in form header
renderLoading ReactNode Custom loading state
renderForm (props: AuthFormSlotProps) => ReactNode Custom form (receives mode, error, submitting, onSubmit, providers, passwordLoginEnabled, onOidcLogin)
children ({ user, logout }) => ReactNode Render function when authenticated

Authorized

Conditionally render based on RBAC permission.

<Authorized permission="app:crm:contacts.delete" fallback={<span>No access</span>}>
  <DeleteButton />
</Authorized>
Prop Type Description
permission string Required permission key
fallback ReactNode Rendered if denied
children ReactNode Rendered if allowed

PermissionsProvider

Share a single permissions fetch across multiple Authorized components.

<PermissionsProvider>
  <Authorized permission="...">...</Authorized>
  <Authorized permission="...">...</Authorized>
</PermissionsProvider>

QueryOptions

Used by useAppCollection, useAppRecord, and useIdentity.

Field Type Description
where WhereClause Filter conditions
orderBy string Sort column
order "asc" | "desc" Sort direction
limit number Max records
offset number Skip count
linked boolean | string[] Resolve federated identity links

Where operators

Operator Description
$eq Equal (also works as direct value: { status: "active" })
$ne Not equal
$gt, $gte Greater than (or equal)
$lt, $lte Less than (or equal)
$like, $ilike Pattern match (case-sensitive / insensitive)
$in, $nin In / not in array
$contains Array field contains value
$isNull Is null check
$and, $or, $not Logical combinators

RuntimeClient

Direct HTTP client for non-React code or imperative use.

import { RuntimeClient } from "@rootcx/sdk";
const client = new RuntimeClient({ baseUrl: "https://<your-ref>.rootcx.com" });

Constructor options

Option Type Default Description
baseUrl string Auto-detected Core API URL. Checks VITE_ROOTCX_URL, then window.location.origin, then http://localhost:9100.
accessToken string null Initial Bearer token (JWT or share token).
persist boolean true If false, tokens are never read from or written to localStorage. Use for public share sessions.
autoRefresh boolean true If false, 401 responses are returned without attempting a token refresh. Required for share-token sessions.

Public share visitor example:

const client = new RuntimeClient({ accessToken: shareToken, persist: false, autoRefresh: false });
const info = await client.getPublicShareInfo(); // → { appId, context }
const data = await client.rpc(info.appId, "get_board", { board_id: info.context.board_id });

Auth

Method Signature
login (email, password) => Promise<LoginResponse>
register (data: RegisterInput) => Promise<{ user: AuthUser }>
magicLinkConsume (token) => Promise<LoginResponse>
logout () => Promise<void>
me () => Promise<AuthUser>
getAuthMode () => Promise<AuthMode>

Data

Method Signature
listRecords (appId, entity, opts?) => Promise<T[]>
queryRecords (appId, entity, query: QueryOptions) => Promise<{ data: T[], total }>
createRecord (appId, entity, data) => Promise<T>
bulkCreateRecords (appId, entity, data[]) => Promise<T[]>
getRecord (appId, entity, id) => Promise<T>
updateRecord (appId, entity, id, data) => Promise<T>
deleteRecord (appId, entity, id) => Promise<void>
identityQuery (kind, query?) => Promise<{ data: T[], total }>

RPC

Method Signature
rpc (appId, method, params?) => Promise<unknown>

Roles and permissions

Method Signature
listRoles () => Promise<RoleDefinition[]>
createRole (data) => Promise<{ message }>
updateRole (name, data) => Promise<{ message }>
deleteRole (name) => Promise<{ message }>
listRoleAssignments () => Promise<RoleAssignment[]>
assignRole (userId, role) => Promise<{ message }>
revokeRole (userId, role) => Promise<{ message }>
getPermissions (userId?) => Promise<EffectivePermissions>
getAvailablePermissions () => Promise<PermissionDeclaration[]>

Integrations

Method Signature
callIntegration (id, action, input?) => Promise<unknown>
bindIntegration (appId, integrationId, connectionId) => Promise<void>
unbindIntegration (appId, integrationId) => Promise<void>
listConnections (integrationId) => Promise<Connection[]>
integrationAuthStatus (id) => Promise<{ connected, connectionCount }>
integrationAuthStart (id) => Promise<{ redirectUrl? }>
integrationAuthSubmit (id, credentials, label?) => Promise<void>
integrationAuthDisconnect (id) => Promise<void>

Jobs

Method Signature
enqueueJob (appId, payload) => Promise<{ msg_id }>
listJobs (appId, opts?) => Promise<Job[]>

Crons

Method Signature
listCrons (appId) => Promise<CronSchedule[]>
createCron (appId, input: CreateCronInput) => Promise<CronSchedule>
updateCron (appId, id, patch) => Promise<CronSchedule>
deleteCron (appId, id) => Promise<void>
triggerCron (appId, id) => Promise<{ msgId }>

Webhooks

Method Signature
listWebhooks (appId) => Promise<Webhook[]>

Agents

const result = await client.invokeAgent(appId, opts, onEvent, signal?);
Parameter Type Description
appId string The agent's app ID.
opts InvokeAgentOptions { message: string, sessionId?: string, fileIds?: string[] }
onEvent (event: AgentEvent) => void Callback fired on each SSE event.
signal AbortSignal Optional. Cancel the invocation.

Returns Promise<AgentDoneEvent> with { type: "done", response: string, sessionId: string, tokens: number }.

Event types (discriminated union on type):

Event Key fields
chunk delta, sessionId
tool_call_started callId, toolName, input, sessionId
tool_call_completed callId, toolName, output, error, durationMs, sessionId
approval_required approvalId, toolName, args, reason, sessionId
session_compacted summary, sessionId
sub_agent_chunk appId, delta
done response, sessionId, tokens
error error, sessionId

Throws RuntimeApiError if the HTTP call fails, the stream has no body, or the agent errors without completing.

All event interfaces are exported from @rootcx/sdk: AgentEvent, AgentDoneEvent, AgentChunkEvent, AgentErrorEvent, AgentToolCallStartedEvent, AgentToolCallCompletedEvent, AgentApprovalRequiredEvent, AgentSessionCompactedEvent, AgentSubAgentChunkEvent.

Shares

Method Signature
createPublicShare (appId, opts: { context: Record<string, unknown> }) => Promise<{ id, url, token, tokenPrefix, context, createdAt }>
revokePublicShare (appId, shareId) => Promise<{ message: string }>
listPublicShares (appId) => Promise<PublicShareListing[]>
getPublicShareInfo () => Promise<{ appId: string, context: Record<string, unknown> }>

The token in createPublicShare is returned once at creation time and never shown again. Store it immediately.

getPublicShareInfo resolves a share token (passed as accessToken in the constructor) to its app and context. Use it from anonymous/public sessions.

Low-level

Method Signature
getBaseUrl () => string
getAccessToken () => string | null
setTokens (access, refresh) => void
authFetch (url, init?) => Promise<Response>
fetchJson (url, init?) => Promise<T>

Types

interface AuthUser {
  id: string;
  email: string;
  displayName: string | null;
  createdAt: string;
}

interface AuthMode {
  authRequired: boolean;
  setupRequired: boolean;
  passwordLoginEnabled: boolean;
  magicLinkEnabled: boolean;
  providers: OidcProvider[];
}

interface OidcProvider { id: string; displayName: string }
interface RegisterInput { email: string; password: string; displayName?: string }
interface LoginResponse { accessToken: string; refreshToken: string; expiresIn: number; user: AuthUser }
interface EffectivePermissions { roles: string[]; permissions: string[] }
interface RoleDefinition { name: string; description: string | null; inherits: string[]; permissions: string[] }
interface RoleAssignment { userId: string; role: string; assignedAt: string }
interface PermissionDeclaration { key: string; description: string }

interface QueryOptions {
  where?: WhereClause;
  orderBy?: string;
  order?: "asc" | "desc";
  limit?: number;
  offset?: number;
  linked?: boolean | string[];
}

interface QueryResult<T> { data: T[]; total: number }
type IdentityRecord<T> = T & { _source: { app: string; entity: string } }

interface CronSchedule {
  id: string; appId: string; name: string; schedule: string;
  timezone: string | null; payload: Record<string, unknown>;
  overlapPolicy: "skip" | "queue"; enabled: boolean;
  pgJobId: number | null; createdBy: string | null;
  createdAt: string; updatedAt: string;
}

interface CreateCronInput { name: string; schedule: string; timezone?: string; payload?: Record<string, unknown>; overlapPolicy?: "skip" | "queue" }
interface UpdateCronInput { schedule?: string; payload?: Record<string, unknown>; overlapPolicy?: "skip" | "queue"; enabled?: boolean }
interface Job { msg_id: number; app_id: string; payload: Record<string, unknown>; user_id: string | null; read_ct: number; enqueued_at: string }
interface IntegrationSummary { id: string; name: string; version: string; description: string; actions: ActionDefinition[]; configSchema: Record<string, unknown>; webhooks: string[] }