# Everfur Chat Public Contract (Consumer Guide) Audience: frontend teams integrating Everfur chat. Canonical docs page: /docs Canonical public contract: /docs/reference/contract AI-only prompt pack: /ai-prompts/everfur-widget-sdk.md ## 1) Supported surfaces - Hosted widget via `/widget/v1.js` - Headless core SDK via `@everfur/chat-sdk` - Web React helpers via `@everfur/chat-sdk/react` - Native-safe headless usage via `@everfur/chat-sdk/react-native` Support boundaries: - The hosted widget is the only prebuilt UI surface. - There is no public `EverfurChat`-style drop-in React component in this phase. - React Native / Expo support is headless-only and requires an app-managed UI. ## 2) Identity model - `partnerKey` is publishable partner or app identity. - `userRef` is the durable continuity key. - `sessionId` is transient session or device context. - When `userRef` is present, continuity resolves by `partnerKey + userRef`. - When `userRef` is omitted, continuity falls back to `partnerKey + sessionId`. ## 3) Quickstarts ### Hosted widget ```html ``` ### Web SDK ```ts import {createWidgetClient} from '@everfur/chat-sdk'; const client = createWidgetClient({ partnerKey: 'pk_live_your_key', userRef: 'stable-user-id', baseUrl: 'https://YOUR-EVERFUR-DOMAIN/api/widget/v1/chat', }); const {conversation_id} = await client.createConversation(); const history = await client.getMessages({ conversationId: conversation_id, limit: 20, }); const stream = await client.sendMessage({ conversationId: conversation_id, messages: [{role: 'user', content: 'How is Luna doing today?'}], }); console.log(history.messages.length, stream.ok); ``` ### React web helpers ```tsx import {useEffect, useState} from 'react'; import { useChatSessionController, useChatStreamAdapter, } from '@everfur/chat-sdk/react'; import { bootstrapChatSession, createChatSdkClient, } from '@everfur/chat-sdk'; export function EverfurReportChat() { const client = createChatSdkClient(); const [petId, setPetId] = useState(''); const [conversationId, setConversationId] = useState(''); const chat = useChatStreamAdapter({conversationId}); const controller = useChatSessionController({ petId, chat, initialConversationId: conversationId || null, client, }); useEffect(() => { let cancelled = false; void (async () => { const bootstrap = await bootstrapChatSession( { email: 'owner@example.com', petName: 'Buddy', }, {client}, ); if (cancelled) return; if (bootstrap.petId) setPetId(bootstrap.petId); if (bootstrap.conversationId) { setConversationId(bootstrap.conversationId); } })(); return () => { cancelled = true; }; }, [client]); useEffect(() => { if ( controller.activeConversationId && controller.activeConversationId !== conversationId ) { setConversationId(controller.activeConversationId); } }, [controller.activeConversationId, conversationId]); return (
); } ``` ### React Native / Expo ```ts import {createWidgetClient} from '@everfur/chat-sdk/react-native'; const client = createWidgetClient({ partnerKey: 'pk_live_your_key', userRef: stableUserId, sessionId: stableSessionId, baseUrl: 'https://YOUR-EVERFUR-DOMAIN/api/widget/v1/chat', }); const {conversation_id} = await client.createConversation(); const conversations = await client.listConversations(); const history = await client.getMessages({ conversationId: conversation_id, limit: 20, }); console.log(conversations.conversations.length, history.messages.length); ``` ## 4) API essentials Canonical widget base path: `/api/widget/v1/chat` Canonical widget headers: - `x-everfur-partner-key` - `x-everfur-session-id` - `x-everfur-user-ref` - `Content-Type: application/json` for JSON request bodies Public widget routes: - `POST /api/widget/v1/chat` - `POST /api/widget/v1/chat/conversations` - `GET /api/widget/v1/chat/conversations` - `GET /api/widget/v1/chat/conversations/{conversationId}` - `PATCH /api/widget/v1/chat/conversations/{conversationId}` - `DELETE /api/widget/v1/chat/conversations/{conversationId}` - `GET /api/widget/v1/chat/conversations/{conversationId}/messages` - `POST /api/widget/v1/chat/conversations/{conversationId}/classify` - `GET /api/widget/v1/chat/suggested-prompts` ## 5) Rate limits The Hydrogen widget layer treats backend responses as the source of truth and passes backend rate-limit metadata through unchanged. Public client contract: - limit-hit signal: `429 Too Many Requests` - passthrough headers when present: - `Retry-After` - `x-everfur-rate-limit-limit` - `x-everfur-rate-limit-remaining` - `x-everfur-rate-limit-reset-at` - `x-everfur-rate-limit-scope` - `x-everfur-rate-limit-policy` - current documented scope: partner-level - current documented policy value: `daily` when daily quota enforcement applies - JSON routes may also include backend-provided fields such as `detail` and `resets_at` ## 6) Retention and deletion - continuity follows `partnerKey + userRef` when `userRef` is present - continuity falls back to `partnerKey + sessionId` when `userRef` is omitted - the Hydrogen widget layer does not apply automatic history expiry - explicit conversation deletion is supported through `DELETE /api/widget/v1/chat/conversations/{conversationId}` - no additional bulk-history redaction API is documented in this phase ## 7) Troubleshooting priorities 1. CORS or preflight failures 2. Invalid partner key or environment mismatch 3. History resets caused by unstable `userRef` 4. Frequent `429` responses 5. Streaming responses that appear all at once ## 8) Docs navigation - `/docs` - choose the right integration path - `/docs/widget` - hosted widget - `/docs/headless-sdk` - headless SDK - `/docs/react` - React SDK - `/docs/react-native` - React Native SDK - `/docs/reference/contract` - HTTP contract - `/docs/reference/launch` - launch checklist - `/docs/reference/troubleshooting` - troubleshooting