> ## Documentation Index
> Fetch the complete documentation index at: https://help.statisfy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Getting Started with the Statisfy SDK

> Install @statisfy/digital-workers-react, mint a session token, wrap your app in StatisfyProvider, and render your first Digital Worker chat.

This guide takes you from an empty React app to a working Digital Worker chat embedded in your product.

## Prerequisites

* A React 18 or 19 application.
* A **publishable key** for your workspace (`pk_live_…` or `pk_test_…`) and a **JWT secret** — generate both in your workspace's API keys section (see [Publishable Keys & Secrets](/admin/publishable_keys)).
* A backend that can sign a short-lived JWT with that secret. **The secret must never reach the browser.**

<Note>
  This guide covers embedding the SDK in **your own application**, where your backend mints the token. If you're building a Statisfy-hosted [Portal](/portals/overview) instead, the token comes from `POST /sdk/auth` rather than your backend — see [step 2](#2-supply-a-session-token).
</Note>

## 1. Install

```bash theme={null}
npm install @statisfy/digital-workers-react
```

Import the stylesheet once, near your app's entry point:

```tsx theme={null}
import '@statisfy/digital-workers-react/styles.css'
```

## 2. Supply a session token

Every SDK request carries a short-lived HS256 bearer token. The SDK never mints it — it calls your `getToken` callback and sends whatever you return. How you produce it depends on where the SDK is running.

### In your own app: your backend signs it

Sign a short-lived JWT with your **JWT secret** on your server, and expose an endpoint your frontend can call. Statisfy requires the standard `iat` / `exp` claims plus the end user's `email`, which is how the gateway resolves who is asking.

```ts theme={null}
// YOUR BACKEND — the secret never leaves your server.
import jwt from 'jsonwebtoken'

app.get('/statisfy-token', requireYourOwnAuth, (req, res) => {
  const token = jwt.sign(
    { email: req.user.email },
    process.env.STATISFY_JWT_SECRET,     // your workspace's JWT secret
    { algorithm: 'HS256', expiresIn: '1h' }
  )
  res.json({ token })
})
```

```ts theme={null}
// YOUR FRONTEND — fetch from your own endpoint; no Statisfy secret here.
async function fetchStatisfyToken(): Promise<string> {
  const res = await fetch('/statisfy-token')
  if (!res.ok) throw new Error(`Token mint failed: ${res.status}`)
  const { token } = await res.json()
  return token
}
```

<Warning>
  Never sign tokens in the browser. Only the publishable key is safe to ship client-side — the JWT secret is a server credential.
</Warning>

### In a Statisfy Portal: exchange at `/sdk/auth`

A hosted portal has no backend of its own, so it exchanges the signed-in user's identity (your tenant's Clerk/IdP JWT) for a Statisfy session token:

```ts theme={null}
async function mintStatisfyToken(userJwt: string): Promise<string> {
  const res = await fetch('https://api.statisfy.app/sdk/auth', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${userJwt}`,
      'X-Statisfy-Publishable-Key': 'pk_live_your_key',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ entity_type: 'portal', entity_id: 'your-portal-uuid' })
  })
  if (!res.ok) throw new Error(`Statisfy auth failed: ${res.status}`)
  const { token } = await res.json() // { token, token_type, expires_in, portal? }
  return token
}
```

The user's email must belong to a **customer** (a person on an account) of your tenant — that's how the gateway binds the session to a `customer_id`. If the email isn't a customer, `/sdk/auth` returns `403 not_a_customer`.

<Note>
  Either way the token is short-lived (the exchange defaults to **1 hour**, see `expires_in`). Cache it and re-mint before it expires — `getToken` (next step) is the natural place to do that.
</Note>

<Warning>
  Only the portal exchange produces a `customer_id` claim, and the project endpoints require one. With a token your own backend signed, the Digital Worker chat works but `ProjectModule` / `useProject` return `401 not_a_portal_session`.
</Warning>

## 3. Wrap your app in `StatisfyProvider`

`StatisfyProvider` supplies the gateway URL and authentication to every SDK component via React context. Set it up once, high in your tree:

```tsx theme={null}
import { StatisfyProvider } from '@statisfy/digital-workers-react'
import { useCallback } from 'react'

function Root() {
  // Return the current session token; re-mint when needed. Memoize so the
  // provider doesn't re-render its subtree on every render.
  const getToken = useCallback(async () => {
    return await getCachedOrFreshStatisfyToken() // your caching around the step-2 token source
  }, [])

  return (
    <StatisfyProvider
      baseUrl="https://api.statisfy.app"
      publishableKey="pk_live_your_key"
      getToken={getToken}
    >
      <YourApp />
    </StatisfyProvider>
  )
}
```

| Prop             | Type                            | Required | Description                                                                                                                            |
| ---------------- | ------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `baseUrl`        | `string`                        | yes      | Statisfy gateway base URL, e.g. `https://api.statisfy.app`.                                                                            |
| `getToken`       | `() => Promise<string \| null>` | yes      | Returns the current session token — from your backend or `/sdk/auth`, per [step 2](#2-supply-a-session-token). Called before requests. |
| `publishableKey` | `string`                        | no       | Your `pk_live_…` / `pk_test_…`. Required for the chat routes.                                                                          |

<Warning>
  Pass a **stable** `getToken` (wrap it in `useCallback`). The provider memoizes its context value on `getToken` identity, so an inline arrow function would re-render every consumer on each render.
</Warning>

## 4. Render your first component

Now any SDK component works anywhere beneath the provider — no connection props needed.

```tsx theme={null}
import { DigitalWorkerChat } from '@statisfy/digital-workers-react'

function SupportPanel() {
  return (
    <DigitalWorkerChat
      workerId="your-digital-worker-uuid"
      workerName="Onboarding Assistant"
      greeting="Hi! How can I help with your onboarding?"
      className="dw-h-[600px]"
    />
  )
}
```

That's a fully working chat: streaming replies, conversation history, unread tracking, and inline forms. See [Digital Worker Chat](/sdk/v0/digital_worker_chat) for every prop and the headless client.

To show a customer's onboarding project instead, render the [Project Module](/sdk/v0/projects):

```tsx theme={null}
import { ProjectModule } from '@statisfy/digital-workers-react'

// Omit projectId to auto-resolve the signed-in customer's onboarding project.
<ProjectModule />
```

## 5. Theming (optional)

The SDK's styles are self-contained and prefixed (`dw:`) so they won't collide with your app's CSS. Override the look with `--dw-*` CSS variables on any ancestor of the SDK components:

```css theme={null}
:root {
  --dw-primary: #4f46e5;        /* accent / buttons */
  --dw-radius: 12px;            /* corner rounding */
  /* …see the rendered widget for the full set of --dw-* variables */
}
```

## Troubleshooting

<Accordion title="401 user_token_invalid">
  The bearer token's signature didn't verify. Make sure `getToken` returns the **session token from `/sdk/auth`** (not the raw Clerk JWT), that it hasn't expired, and that you minted it against the same tenant/environment your gateway points at.
</Accordion>

<Accordion title="403 not_a_customer">
  The signed-in user's email isn't a person on any account in your tenant. Add them as a contact, or sign in as a known customer.
</Accordion>

<Accordion title="Components throw 'must be rendered inside <StatisfyProvider>'">
  An SDK component is mounted outside the provider. Move it beneath `<StatisfyProvider>`.
</Accordion>
