Skip to main content
The Portal SDK lets an embedded portal show a customer their onboarding project — its fields, milestones, tasks, and comments — and let them make the changes you allow. It has two layers:

React SDK

@statisfy/portal-react — drop-in components (<OnboardingProject/>, <ProjectModule/>, <Comments/>) and headless hooks (useProject, useComments).

REST API

The /sdk/v1 endpoints the React SDK calls. Use them directly if you’re not on React.
Everything is scoped to the customer in the session token — the portal never sends a customer id, and a project or task that isn’t the session’s is reported as 404 (existence is never leaked).

Authentication

Every call needs a portal session token (a short-lived JWT) as a bearer, plus your publishable key:
The session token comes from the portal’s passwordless sign-in — the customer enters an emailed one-time code and Statisfy returns a token scoped to them. The React SDK does all of this for you: <PortalAuthGate> owns the sign-in and refresh, and supplies <StatisfyProvider>. See Portal sign-in for the raw endpoints, and Publishable keys to create a key.
Visibility is enforced server-side. A real (non-preview) external viewer only receives fields, tasks, and comments that have been shared with the customer — see External sharing. An internal preview session sees everything.

What the customer can see and edit

  • Fields marked Internal are removed from the response entirely.
  • Tasks hidden from the portal are dropped from the task list.
  • Comments that weren’t shared with the customer are filtered out; comments the customer posts are always shared.
  • Writes are gated by meta.can_edit and each field’s editable flag.

REST API

Base path: /sdk/v1. All responses are JSON. Error responses:

Resolve the onboarding project

Returns the customer’s onboarding project id under onboarding_project__c_, which you then read with GET /projects/{id}.
Where that id comes from: onboarding_project__c_ is the Onboarding Project field on the Account — a system lookup field (pointing at a project) provisioned automatically — you never create it. The endpoint resolves it for the account the session token is scoped to, so each signed-in customer gets their own project with no id in your code. To make it resolve, set that field on the account: open the account record and pick its project in Onboarding Project, or populate it in bulk via import, the API, or field automation. An account whose field is empty returns null here, and <OnboardingProject/> renders its empty state instead of falling back to another account’s project. <OnboardingProject/> and <ProjectModule/> split on where the project id comes from. OnboardingProject reads this account field automatically — each signed-in customer sees their own project, no configuration beyond a configId. ProjectModule instead shows one project the portal’s author pins in Settings, the same project for every viewer; it’s visible only to the account that owns the pinned project, and every other signed-in account gets the empty state. Use OnboardingProject for the common per-account case; reach for ProjectModule only when every viewer should land on the same project.
In Portal Studio the field is surfaced in Settings → Components as the shared, Locked property “Onboarding project source” — read-only, because it is pinned platform-wide: re-pointing it would change onboarding for every portal at once. Configure the per-account value in Statisfy, not in the portal.

Read a project

Returns the project’s values, human-readable display strings, field schema, server-ordered tasks, and a progress meter.
Render display[key] ?? properties[key]; send only canonical properties back on writes. tasks is a flat, depth-first list — build the milestone → subtask tree client-side from parent_id, and never re-sort it.

Update a project or task

Body is the changed canonical values only:
Both return the refreshed project payload. A non-editable field or invalid option is rejected with a 400 (not_editable / invalid_option / invalid_type).

Comments

GET returns customer-visible comments oldest-first:
POST takes { "value": "..." } and returns the created comment. Portal-posted comments are always shared with the customer. PATCH (author only) edits the text.

React SDK

Install and wrap your app in <StatisfyProvider> (see Portals overview); it supplies the base URL, token minting, and publishable key to every hook and component.

<OnboardingProject/>

The signed-in account’s own onboarding project — header, progress, milestone journey, editable task list, project comments, and a task-detail drawer, resolved from the account field described above. configId is required: it’s what puts the component in the portal’s config manifest (so its Settings card renders), and without it the component can never resolve — there’s no embed-mode fallback.
It distinguishes three states: resolving (renders nothing), resolved with the field unset (“No onboarding project is set up for your account yet.”), and a failed config fetch (a distinct “couldn’t load configuration” message) — so a transient network error is never reported to a customer as “you have no project.”

<ProjectModule/>

The pre-assembled view of one project pinned by the portal’s author — the same project for every viewer, not each account’s own. In a portal, configId names this instance’s config slot and the pinned project id comes from the portal’s Settings; outside a portal, pass projectId directly.
Passing both props is allowed — configId wins and projectId is ignored (with a console warning in development). The pinned project resolves against the signed-in account, so only users of the account that owns it can see it; everyone else gets the empty state. That’s intended: use OnboardingProject above for a per-account view.

<Comments/>

A standalone comment thread + composer — project-level, or task-level with a taskId. Used inside <ProjectModule/>, but also available on its own for custom layouts.

useProject(projectId?)

Headless data hook for custom layouts. Owns the config → project resolve, the milestone/subtask tree, and optimistic task/project writes with rollback.
The assembled equivalent of the no-arg resolve above is <OnboardingProject configId="onboarding" />.

useComments(projectId, taskId?)

Headless comments hook. Loads customer-visible comments and posts new ones (optimistically appended).

createProjectClient(config)

The React-unaware transport, if you want to call the API without hooks. Attaches the bearer token and publishable key on every request.