SDK fundamentals

Understand Cavuno’s headless model, identifiers, auth ownership, pagination, and errors.

The SDK is deliberately small: it turns Cavuno’s public Board API into typed namespaces and leaves rendering, routing, caching, and session ownership to your application. These six concepts prevent the most common production mistakes.

Read this section in order

  1. How headless Cavuno works — decide which system owns each responsibility.
  2. Board identifiers and publishable keys — connect a deployment to a stable board identity.
  3. Authentication and session ownership — choose a browser-owned or server-owned session without leaking tokens.
  4. Data fetching and pagination — distinguish page size, cursors, collection caps, and cache directives.
  5. Handle SDK errors — preserve typed failures and give users the correct recovery path.
  6. Client-side applications — understand the security and session tradeoffs when the browser owns the SDK client.

The rules to remember

  • A pk_… key identifies a board; it does not authenticate a user.
  • A shared server client must stay stateless. Pass each user’s authorization in that request’s trailing options.
  • Refresh tokens rotate and are single-use. The SDK never performs a hidden refresh after a 401.
  • List methods return envelopes. Render data, and carry the opaque nextCursor forward unchanged.
  • Every non-2xx response throws BoardApiError. Branch on status/code guards instead of matching human-readable messages.

Verify the fundamentals

Run this through the client you created during setup:

ts
const [context, firstPage] = await Promise.all([
board.context(),
board.jobs.list({ limit: 1 }),
]);
if (context.object !== 'public_board' || firstPage.object !== 'list') {
throw new Error('The Board API returned an unexpected contract');
}
console.log(`${context.name}: ${firstPage.data.length} job card(s) returned`);

The command should print the name of the board connected by your pk_… key. An empty board prints 0 job card(s) returned; that is a successful list response, not an error.

Next step

Create one /jobs route that renders data from board.jobs.list({ limit: 20 }), then deliberately preserve its loading, empty, and thrown-error states. Use Jobs and search for the exact browse/search split and pagination behavior.