> ## 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.

# Statisfy SDK API Reference

> Endpoints, authentication headers, error codes, and TypeScript types for the Statisfy SDK.

This page is the low-level reference behind the SDK components and clients. Most apps never call these endpoints directly — the SDK does — but you'll need them when minting tokens or debugging.

## Base URL & headers

All requests go to your Statisfy gateway (e.g. `https://api.statisfy.app`). Every SDK data request carries:

| Header                       | Value                     | Notes                                                                                               |
| ---------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------- |
| `Authorization`              | `Bearer <session-token>`  | The HS256 session token from `/sdk/auth`.                                                           |
| `X-Statisfy-Publishable-Key` | `pk_live_…` / `pk_test_…` | Identifies your tenant. Required by the chat routes; project routes read the tenant from the token. |

## Authentication

### `POST /sdk/auth` — mint a session token

Exchanges a signed-in user's identity for a short-lived Statisfy session token.

**Request**

```http theme={null}
POST /sdk/auth
Authorization: Bearer <tenant Clerk/IdP JWT>
X-Statisfy-Publishable-Key: pk_live_…
Content-Type: application/json

{ "portal_id": "<uuid>" }
```

**Response** `200`

```json theme={null}
{
  "token": "eyJhbGc…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "portal": { "portal_id": "<uuid>", "commit_sha": "<sha>", "url": "https://…" }
}
```

The minted token is an HS256 JWT signed with your tenant's shared secret, carrying `tenant_id`, `customer_id`, `portal_id`, `email`, `iss` (`statisfy-sdk`), `iat`, and `exp`. The gateway scopes every subsequent request to that `customer_id`.

**Error codes**

| Status | `code`                     | Meaning                                              |
| ------ | -------------------------- | ---------------------------------------------------- |
| 401    | `auth_token_invalid`       | The user JWT is missing, expired, or invalid.        |
| 401    | `auth_token_missing_email` | The user JWT has no email claim.                     |
| 401    | `publishable_key_missing`  | The `X-Statisfy-Publishable-Key` header wasn't sent. |
| 401    | `publishable_key_invalid`  | Unknown or revoked publishable key.                  |
| 403    | `not_a_customer`           | The user's email isn't a customer of this tenant.    |
| 403    | `customer_unresolved`      | The matched person has no customer id.               |
| 404    | `portal_not_found`         | The `portal_id` is unknown or not published.         |
| 500    | `sdk_secret_unconfigured`  | The tenant's signing secret isn't configured.        |

## Project endpoints (`/sdk/v1`)

Authenticated by the session token's own `tenant_id` + `customer_id` claims (token-only — the publishable key is not required here). All errors return `{ "detail": { "code": "...", "message": "..." } }`.

| Method  | Path                                   | Body                    | Response                                    |
| ------- | -------------------------------------- | ----------------------- | ------------------------------------------- |
| `GET`   | `/sdk/v1/config/{module}`              | —                       | `{ data: {...}, meta: { module } }`         |
| `GET`   | `/sdk/v1/projects/{id}`                | —                       | `{ data: ProjectData, meta: { can_edit } }` |
| `PATCH` | `/sdk/v1/projects/{id}`                | `{ properties: {...} }` | `{ data: ProjectData, meta }`               |
| `PATCH` | `/sdk/v1/projects/{id}/tasks/{taskId}` | `{ properties: {...} }` | `{ data: ProjectData, meta }`               |

For `module=portal`, the config response exposes the account's onboarding project under the `onboarding_project__c_` key.

**Error codes**

| Status | `code`                 | Meaning                                             |
| ------ | ---------------------- | --------------------------------------------------- |
| 401    | `user_token_invalid`   | Token signature invalid / claims missing.           |
| 401    | `user_token_expired`   | Token `exp` is in the past — re-mint.               |
| 401    | `not_a_portal_session` | Token has no `customer_id` (not a portal session).  |
| 404    | `module_not_found`     | Unknown config module.                              |
| 404    | `project_not_found`    | Project not found for this customer.                |
| 404    | `task_not_found`       | Task not found on this project.                     |
| 400    | `not_editable`         | A supplied field isn't editable / doesn't exist.    |
| 400    | `invalid_option`       | Value isn't an allowed option for a `select` field. |
| 400    | `invalid_type`         | Value doesn't match the field's type.               |

## Chat endpoints (`/sdk/dw/v1`)

Authenticated by the publishable key (resolves the tenant) plus the session/worker token.

| Method | Path                                            | Body / Query                                               | Response                                                                                   |
| ------ | ----------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `POST` | `/sdk/dw/v1/workers/{workerId}/messages`        | `{ content, thread_id?, structured_input?, form_prompt? }` | `{ reply, thread_id, conversation_id, agent_message_id, pending_approval?, next_action? }` |
| `POST` | `/sdk/dw/v1/workers/{workerId}/messages/stream` | same as above                                              | Server-Sent Events (see below)                                                             |
| `GET`  | `/sdk/dw/v1/workers/{workerId}/conversation`    | `?thread_id=<id>&source=in_app_sdk`                        | `{ thread_id, conversation_id, messages[], next_action? }`                                 |
| `GET`  | `/sdk/dw/v1/workers/{workerId}/conversations`   | `?source=in_app_sdk&limit=<n>`                             | `{ conversations[], total }`                                                               |

<Note>
  The chat endpoints moved under the `/sdk` namespace (from `/dw/v1/...` to `/sdk/dw/v1/...`). The old `/dw/v1/workers/...` paths still work as a deprecated alias, but new integrations should use `/sdk/dw/v1`. The official SDK already calls the new paths — upgrade to pick them up automatically.
</Note>

### Streaming frames

`/messages/stream` emits these SSE frame types:

| Frame                 | Payload                                                          | Meaning                                   |
| --------------------- | ---------------------------------------------------------------- | ----------------------------------------- |
| `text_delta`          | `{ content }`                                                    | Incremental reply text.                   |
| `tool_call_started`   | `{ tool_name, tool_input? }`                                     | A tool was invoked (e.g. `present_form`). |
| `tool_call_completed` | `{ tool_name }`                                                  | Tool succeeded.                           |
| `tool_call_error`     | `{ tool_name, tool_error? }`                                     | Tool failed.                              |
| `done`                | `{ thread_id, conversation_id, agent_message_id, next_action? }` | Turn finished.                            |
| `error`               | `{ message, code? }`                                             | Streaming error.                          |

The headless client (`createDigitalWorkerClient`) maps these frames to the `streamMessage` handler callbacks — see [Digital Worker Chat](/sdk/digital_worker_chat#headless-client).

## TypeScript types

The package is fully typed. Public exports:

**Provider & chat**

```ts theme={null}
type GetToken = () => Promise<string | null>
interface StatisfyConfig { baseUrl: string; getToken: GetToken; publishableKey?: string }
interface StatisfyProviderProps extends StatisfyConfig { children: ReactNode }
interface DigitalWorkerChatProps { workerId: string; workerName?: string; greeting?: string; /* …see Chat page */ }
interface OnboardingModuleProps { /* DigitalWorkerChatProps minus initialThreadId & autoLoadConversation */ }
interface DigitalWorkerClientConfig { baseUrl: string; workerId: string; getToken: GetToken; publishableKey?: string }
class DigitalWorkerError extends Error { code?: string; status?: number }
```

**Forms & messages**

```ts theme={null}
interface FormField { key: string; label: string; type: string; options?: unknown[]; placeholder?: string; mandatory?: boolean }
interface FormSpec { prompt: string; fields: FormField[] }
type FormRenderer = (spec: FormSpec, ctx: FormRendererContext) => ReactNode
interface FormRendererContext { onSubmit: (values: Record<string, unknown>) => void; disabled: boolean }
interface ChatMessage { id: string; role: 'user' | 'assistant' | 'system'; content: string; /* …toolCalls, formSpec, senderKind, senderName, … */ }
interface ToolCall { toolName: string; status: 'running' | 'completed' | 'error'; error?: string }
```

**Project**

```ts theme={null}
type ProjectFieldType = 'text' | 'date' | 'number' | 'boolean' | 'select' | (string & {})
type ProjectStepStatus = 'done' | 'current' | 'upcoming' | (string & {})
interface ProjectSelectOption { value: string; label: string; color?: string }
interface ProjectFieldSchema { key: string; label: string; type: ProjectFieldType; editable: boolean; options?: ProjectSelectOption[] }
interface ProjectTask { id: string; parent_id: string | null; is_milestone: boolean; status: string | null; step_status: ProjectStepStatus; properties: Record<string, unknown>; display: Record<string, string> }
interface ProjectProgress { percent: number | null; completed: number; total: number }   // exported as ProjectProgressData
interface ProjectData { id: string; properties: Record<string, unknown>; display: Record<string, string>; schema: ProjectFieldSchema[]; progress: ProjectProgress | null; tasks: ProjectTask[]; tasks_schema: ProjectFieldSchema[] }
interface ProjectResponse { data: ProjectData; meta: { can_edit?: boolean } & Record<string, unknown> }
interface ProjectConfigResponse { data: Record<string, unknown>; meta: Record<string, unknown> }
```

## Security notes

* The **publishable key** is safe in client-side code; it only identifies the tenant and can't authenticate on its own.
* The **session token** is short-lived (default 1 hour) and scoped to a single customer. Re-mint it via `/sdk/auth` before expiry inside your `getToken` callback.
* Cross-origin requests are scoped per publishable key — the gateway only accepts SDK requests from origins configured for your key.
* The gateway derives `customer_id` from the signed token, never from client-supplied input, so a customer can only ever read or write their own project and conversations.
