Sub-Projects
Sub-projects let one parent project serve multiple tenants from the same SDK install — same parent project, but the SDK passes a subProjectId to route each session to the right child. The mobile codebase stays the same; the AI configuration (greeting, documents, theme, conversations) varies by tenant. Tools are always the parent project’s — they don’t vary per sub-project.
This page covers the SDK side — the prop, the runtime behavior, and the cross-feature limits. For dashboard-side configuration (creating sub-projects, inheritance, per-sub-project conversations), see Dashboard › Sub-Projects.
When to Use Sub-Projects
Sub-projects are the right answer when one mobile app serves multiple distinct tenants that each need their own AI personality, knowledge base, or branding — but you don’t want the engineering cost of running multiple builds or multiple integrations.
Concrete examples:
- Multi-location app — same app for every branch, but each branch’s AI knows its own offerings and promotions (works for a restaurant chain, a clinic network, a bank, a gym franchise)
- Multi-brand retail — same shopping app for several brand partners, each with its own tone and product catalog
- White-label deployment — one codebase you ship to multiple customer organizations, each with their own assistant
- Regional variants — same app worldwide, but the AI varies by country’s documents and rules
If your app serves a single tenant, don’t use sub-projects — they add a layer of indirection (the parent + the child) that buys nothing in single-tenant cases. Use one project.
The SDK Contract
Sub-project routing is a top-level prop, not part of the runtime context:
<Qafka
// projectId, endUserId, …
subProjectId="branch-london"
/>This is a deliberate API decision — putting subProjectId inside context would make it look like one more piece of optional metadata, when in reality it’s a routing key that changes which project the SDK is talking to. Mistakes here ripple silently: a misnamed key in context would be ignored; a wrong subProjectId prop produces a clear Sub-project not found error.
Identifying a Sub-Project
The subProjectId value can be either:
- The sub-project’s UUID (immutable, generated by the dashboard) —
e7f2a4b8-3c5d-4f1a-9d8b-2e1f5c4a7b6d - A human-readable identifier you set in the dashboard —
branch-london,branch-nyc,tenant-acme
The backend tries UUID first, then falls back to identifier match within the parent. Prefer human-readable identifiers — they’re easier to debug from the conversations view, easier to thread through your app’s existing branch/tenant ID model, and stable across environments.
Switching at Runtime
When subProjectId changes, the SDK fully tears down the current session and reinitializes against the new sub-project. This means:
- The active conversation ends — the user starts fresh in the new sub-project
- Any in-flight message is dropped
- Theme, greeting, documents, and instructions all reload from the new sub-project (tools stay the same — they’re always the parent project’s)
- The voice session, if active, is closed (see voice limitation below)
Practically, this is what you want — switching tenants mid-conversation almost never makes semantic sense (“show me Branch A’s menu — actually no, Branch B’s”). But be deliberate about when you change the prop:
// User picks a different branch from a location switcher → re-mount with new ID
<Qafka
// projectId, endUserId, …
subProjectId={selectedBranch.identifier}
/>If you wrap <Qafka /> in your own location-switcher UI, don’t pipe a frequently-changing value into subProjectId. Memoize or settle on the chosen tenant before passing it in.
Voice and Sub-Projects
Voice chat currently does not respect subProjectId — the SDK doesn’t forward it to the voice session, so voice always authenticates against whichever project the API key or attestation session resolves to (the parent), regardless of which child the rest of the SDK is configured for. Sub-project-aware voice routing is a known gap.
If sub-project-specific voice configuration is critical for your launch, fall back to text chat for sub-project flows, or hide the voice page on sub-project screens via the voiceEnabled={false} prop until the gap is closed.
Patterns
Location Switcher
User picks a branch from a list, the chat for that branch opens:
function BranchChatScreen({ branch }) {
return (
<Qafka
projectId="proj_abc123"
endUserId={currentUser?.id ?? 'anonymous'}
subProjectId={branch.identifier}
/>
)
}When the user navigates between branch screens, the parent component remounts <Qafka /> with the new identifier and the chat re-initializes against that branch’s sub-project.
Auth-Driven Tenant Routing
User signs in; their tenant is on the user record:
function ChatScreen() {
const { user } = useAuth()
if (!user) return <LoginPrompt />
return (
<Qafka
projectId="proj_abc123"
endUserId={user.id}
subProjectId={user.tenantId}
isAuthenticated
context={{ role: user.role }}
/>
)
}Notice the user’s identity goes in endUserId (required, groups conversations/analytics per user), role is arbitrary runtime metadata so it goes in context, and tenantId is subProjectId because it’s a routing key.
Single-Project Fallback
If subProjectId is undefined (or omitted), the SDK falls through to the parent project — the same behavior as a single-tenant app. This is useful when your app is gradually migrating to multi-tenancy:
<Qafka
// projectId, endUserId, …
subProjectId={tenant?.identifier} /* undefined for users not yet assigned to a tenant */
/>Users on the parent fallback see the parent’s greeting, tools, and documents. Users with a sub-project assigned see their sub-project’s overrides on top.
Errors
The SDK surfaces sub-project resolution errors through onError. The most common ones:
| Error | What it means |
|---|---|
Sub-project not found | The subProjectId value didn’t match any active child of the active parent project. Check the value against the dashboard. |
Project not found for API key (API-key auth) / Project is not a parent (keyless auth) | You passed subProjectId, but the project your key/session resolves to is itself already a sub-project — sub-projects can’t have sub-projects of their own. |
If the subProjectId is missing or empty, the SDK silently uses the parent — that is not an error.