How headless Cavuno works
Understand which responsibilities belong to Cavuno, the SDK, and your frontend.
A
JCavuno supplies the managed job board backend. @cavuno/board is the typed interface to that backend. Your application owns the frontend experience and its runtime.
That boundary lets you replace Cavuno’s hosted frontend without rebuilding job storage, search, candidate accounts, applications, employer workflows, or the other enabled backend capabilities.
The request path
123456Visitor→ your route, component, or server function→ @cavuno/board→ Cavuno Board API→ typed data or BoardApiError→ your rendered response
Every client method uses a board-scoped base path:
1{baseUrl}/v1/boards/{board}/...
The client builds that path, serializes requests, attaches configured headers, parses JSON, and throws typed API errors. It does not render HTML or choose routes.
Ownership by layer
| Layer | Owns | Does not own |
|---|---|---|
| Cavuno | Persisted data, public Board API responses, search, business rules, feature entitlements, authentication endpoints | Your components, route tree, hosting, or browser session storage |
| SDK | Request construction, response types, namespace methods, auth storage adapters, pagination, errors, and helper packages | Page templates, automatic route generation, hidden token refresh, or deployment |
| Your frontend | Routes, layout, accessibility, forms, analytics presentation, cache policy, session boundary, hosting, and deployment | Cavuno’s internal data model or operator administration |
Start with board context
Call board.context() when assembling the application shell. It returns the board identity, analytics configuration, custom fields, contact/social identity, and capability flags.
12345678910import { createBoardClient } from '@cavuno/board';const board = createBoardClient({board: 'pk_0123456789abcdef0123456789abcdef',});const context = await board.context();console.log(context.name);console.log(context.features.jobAlerts);
Treat context.features as the runtime capability contract. For example, only link to your alerts route when features.jobAlerts is true, and read features.talentDirectory (off | public | employers_only) when deciding whether /talent is public, employer-only, or disabled. Two flags gate whole surface families: features.nativeApplications is false on an external-applications-only board (hide the built-in apply flow and employer ATS surfaces; apply rejects with applications_native_disabled), and features.messaging is false when applicant–employer messaging is off (hide inbox, message CTAs, and unread badges; the me.conversations family rejects with messaging_disabled). Flipping nativeApplicationsEnabled off also unpublishes the board's published native-apply jobs to drafts through an asynchronous drain, so expect job lists to shrink over the seconds after the flip — and re-enabling does not republish them.
Feature flags are not only presentational. The API remains authoritative and can still reject a request, so the frontend must also handle typed errors.
What the API intentionally does not return
The Board API returns data and configuration—not hosted page-builder JSON or Cavuno’s rendered layouts. Recreate the pages you need using the returned data and helper packages.
When moving an indexed hosted board to headless, preserve canonical public paths where possible. In particular, job details use /companies/:companySlug/jobs/:jobSlug; /jobs/:keyword remains a listing route.
Verify the boundary
You have the model right when:
- Changing a component does not require changing Cavuno data APIs.
- Adding an optional route begins with the corresponding context feature.
- A public data request works without a candidate session.
- A user-specific request carries that user’s authorization only for that call.
- Your frontend—not the SDK—decides how a loading, empty, denied, or failed state looks.
Next, choose a stable board identifier and publishable key.