Headless SDK
Use Qafka’s backend without the built-in <Qafka> widget — bring your own chat UI and drive the SDK directly.
Initialize
import { QafkaSDK } from '@qafka/react-native'
const sdk = QafkaSDK.getInstance()
await sdk.initialize({ projectId: 'proj_abc123' })
// End-user identity is set separately, after initialize() — see below
sdk.setEndUser(currentUser?.id ?? 'anonymous')QafkaSDK is a singleton — getInstance() always returns the same instance. getSDK() is a shorthand for the same call:
import { getSDK } from '@qafka/react-native'
const sdk = getSDK()projectId is required in production (keyless) builds — it’s sent with device attestation so the backend can resolve your project. In development, before attestation is set up, authenticate with a TEST key instead by passing apiKey:
await sdk.initialize({
projectId: 'proj_abc123',
apiKey: __DEV__ ? 'your-test-key' : undefined,
})The devConfig prop that resolves a TEST key automatically is specific to the <Qafka> component’s useSDK hook — headless code reads the key itself (e.g. from your own config, or the same .qafka/qafka-runtime.js file the CLI writes) and passes it as apiKey. See API Key Security for how key types and attestation fit together.
SDKConfig
| Field | Type | Description |
|---|---|---|
projectId | string | Target Qafka project. Required in production. |
apiKey | string | null | Development API key (TEST key). Omit or leave null in production. |
subProjectId | string | Sub-project identifier. |
apiUrl | string | Advanced — leave unset for production. |
locale | string | BCP 47 locale (e.g. "tr", "en-US"), forwarded as sdkContext.locale on every chat request. Explicit only — never derived from device language. |
navigationRef | any | React Navigation ref, used when the SDK navigates on your behalf. |
debug | boolean | Enable verbose dev logging. |
timeout | number | Request timeout in ms. |
onStatusChange | (status: SDKStatusType) => void | Fires as the SDK moves through 'uninitialized' | 'initializing' | 'ready' | 'unavailable' | 'error'. This is how a headless caller finds out about an initialization failure or a suspended project — see Status below. |
onNavigationSuggest | (suggestion) => void | Fires whenever the backend suggests navigation. |
onError | (error: Error) => void | Declared on the config type, but initialize() does not currently call it — rely on onStatusChange (status 'error') instead, and wrap initialize() in try/catch since it also rejects. |
SDKConfig doesn’t take endUserId/endUserData — call setEndUser after initialize(), and whenever the identity changes:
sdk.setEndUser(currentUser.id, { email: currentUser.email, plan: currentUser.plan })The first argument groups conversations, action logs, and analytics per user and is never forwarded to the LLM prompt. The second is optional structured profile data, also never forwarded to the LLM — unless a tool’s Description references either with {{endUser.*}} — both are reachable in dashboards/tool templates via {{endUser.id}} / {{endUser.data.<key>}}. See Context for the full context vs. endUserData split.
Sending Messages
// Single response
const response = await sdk.sendMessage('Hello', context, contextDescription)
// Streaming
await sdk.sendMessageStream(
'Hello',
(chunk) => console.log('Chunk:', chunk),
(fullResponse) => console.log('Complete:', fullResponse),
(error) => console.error('Error:', error),
context,
contextDescription,
)sendMessage returns one complete reply but doesn’t run Server, Static Data or Calendar tools — those only execute on the streaming path. If your project uses any of them, send with sendMessageStream.
Both throw if the SDK isn’t in the 'ready' status yet, or if the message is empty. context and contextDescription follow the same rules as the <Qafka> widget props — see Context.
Generating Images
const result = await sdk.generateImage({
description: 'product photo reframed to 16:9 landscape',
aspectRatio: '16:9',
quality: 'standard',
})Requires the SDK to be 'ready'. See the package README for the full parameter and result reference.
Conversation State
const history = await sdk.getConversationHistory()
await sdk.clearConversation()
const newSessionId = await sdk.startNewConversation()All three require the SDK to be 'ready'.
Locale
sdk.setLocale('tr') // or null to clearA no-op if called before initialize() — pass locale in SDKConfig for the initial value instead.
Status
const status = sdk.getStatus() // 'uninitialized' | 'initializing' | 'ready' | 'unavailable' | 'error''unavailable' means the project is suspended — this is treated as an expected, recoverable state rather than an error; onStatusChange fires with 'unavailable'.
Teardown
await sdk.destroy()Tears down the current instance’s services and clears the shared singleton slot if this instance still owns it. Call it when your headless session ends (e.g. user signs out).