----++ ----++ ---+++
---+++ ---++ ---++
----+--- ----- --------- --------++ ------ ----- ----++-----
---------+ --------++----------++--------+++--------+ --------++---++---++++
---+++---++ ++++---++---+++---++---+++---++---+++---++---++---++------++++
----++ ---++--------++---++----++---+++---++---++ ---+---++ -------++
----+----+---+++---++---++----++---++----++---++---+++--++ --------+---++
---------++--------+++--------+++--------++ -------+++ -------++---++----++
+++++++++ +++++++++- +++---++ ++++++++ ++++++ ++++++ ++++ ++++
--------+++
+++++++
Embeddable AI-powered storage management for your app — streaming chat, pluggable authentication, and multi-tenant operator scoping.
npm install @bagdock/hiveyarn add @bagdock/hivepnpm add @bagdock/hivebun add @bagdock/hiveYour app talks to the Bagdock API through the SDK. The API handles AI responses, tool execution, and data access on your behalf.
graph LR
subgraph Your App
SDK["@bagdock/hive"]
end
SDK -- "ek_* / rk_*" --> API["Bagdock API"]
Bagdock uses a Stripe-style three-tier key model:
| Prefix | Name | Use case | Scopes |
|---|---|---|---|
ek_* |
Embed Key | Client-side widgets, iframes | Origin-locked, read-only by default |
rk_* |
Restricted Key | Server-side integrations, APIs | Granular scopes, no origin lock |
whsec_* |
Webhook Signing Key | Verifying webhook payloads | HMAC-SHA256 signatures |
All keys support live and test environments:
'ek_live_abc123...' // Production embed key
'ek_test_abc123...' // Sandbox embed keyEmbed a chat widget in 10 lines. The SDK handles streaming, tool execution, and session management.
sequenceDiagram
participant Browser
participant SDK as @bagdock/hive
participant API as Bagdock API
Browser->>SDK: sendMessage("Find storage in London")
SDK->>API: POST /hive/chat/stream<br/>Authorization: Bearer ek_live_...
API-->>SDK: SSE stream (text + tool results)
SDK-->>Browser: messages[] updated
import { BagdockHive } from '@bagdock/hive'
const hive = new BagdockHive({
apiKey: 'ek_live_...',
})
const stream = hive.chat.stream({
messages: [{ role: 'user', content: 'Find storage near King\'s Cross' }],
})Attach a user identity so the agent can access their rentals, payments, and profile.
sequenceDiagram
participant User
participant App
participant Auth as Auth Provider<br/>(Clerk/Stytch/Auth0)
participant SDK as @bagdock/hive
participant API as Bagdock API
User->>App: Sign in
App->>Auth: Authenticate
Auth-->>App: JWT token
App->>SDK: new BagdockHive({ auth: { getToken } })
SDK->>API: POST /hive/chat/stream<br/>+ X-Bagdock-User-Token: jwt
API-->>SDK: Personalised response (rentals, loyalty, etc.)
import { BagdockHive } from '@bagdock/hive'
const hive = new BagdockHive({
apiKey: 'ek_live_...',
auth: {
provider: 'clerk',
getToken: () => clerk.session?.getToken() ?? '',
},
})
const stream = hive.chat.stream({
messages: [{ role: 'user', content: 'Show my upcoming payments' }],
})Supported providers: Clerk, Auth0, Stytch (client-side), or any custom JWT.
If your auth provider uses httpOnly cookies (e.g. Stytch session tokens), the JWT is not accessible to client-side JavaScript. Use a thin server-side proxy that authenticates and forwards requests to the Bagdock API.
sequenceDiagram
participant User
participant Browser
participant Proxy as Your API Route<br/>(Next.js / Express)
participant API as Bagdock API
User->>Browser: Chat message
Browser->>Proxy: POST /api/hive/stream<br/>(httpOnly cookie sent automatically)
Proxy->>Proxy: Read cookie, authenticate server-side
Proxy->>API: POST /hive/chat/stream<br/>+ Authorization: Bearer ek_live_...<br/>+ X-Bagdock-User-Token: jwt
API-->>Proxy: SSE stream
Proxy-->>Browser: SSE stream (passthrough)
// app/api/hive/stream/route.ts (Next.js App Router)
import { type NextRequest } from 'next/server'
export async function POST(req: NextRequest) {
// 1. Read httpOnly cookie (never exposed to client JS)
const sessionToken = req.cookies.get('stytch_session')?.value
// 2. Authenticate server-side
const headers: Record<string, string> = {
'Authorization': `Bearer ${process.env.HIVE_EMBED_KEY}`,
'Content-Type': 'application/json',
}
if (sessionToken) {
const session = await stytch.sessions.authenticate({ session_token: sessionToken })
headers['X-Bagdock-User-Token'] = session.session_jwt
headers['X-Bagdock-Contact-Id'] = session.user.user_id
}
// 3. Forward to Bagdock API
const upstream = await fetch(
`${process.env.HIVE_API_URL}/api/v1/hive/chat/stream`,
{ method: 'POST', headers, body: req.body, duplex: 'half' as any },
)
// 4. Stream response back to client
return new Response(upstream.body, {
status: upstream.status,
headers: { 'Content-Type': 'text/event-stream' },
})
}Security: Use this pattern when your auth provider uses httpOnly cookies. The JWT and embed key never leave your server — they are not accessible to client-side JavaScript or
NEXT_PUBLIC_environment variables. This satisfies SOC 2 CC6.1, CC6.3 and GDPR Art. 32 requirements.
| Your auth setup | Recommended pattern | Example |
|---|---|---|
| Client-accessible JWT (Clerk, Auth0, Firebase) | auth adapter directly |
auth: { provider: 'clerk', getToken } |
| httpOnly session cookies (Stytch, custom cookies) | Server-side proxy | Next.js API route → Bagdock API |
| Server-to-server (backend integrations) | Restricted key (rk_*) |
apiKey: 'rk_live_...' |
Scope all requests to a specific operator — their facilities, pricing, and branding.
graph TB
subgraph Your Platform
SDK["@bagdock/hive<br/>rk_live_..."]
end
SDK --> |"forOperator('opreg_acme')"| OpA["Acme Storage"]
SDK --> |"forOperator('opreg_globalvault')"| OpB["Global Vault"]
const hive = new BagdockHive({ apiKey: 'rk_live_...' })
// Scoped to Acme Storage
const acme = hive.forOperator('opreg_acme')
const units = await acme.units.list({ status: 'available' })
const codes = await acme.access.list(unitId)
// Scoped to Global Vault
const vault = hive.forOperator('opreg_globalvault')
const vaultUnits = await vault.units.list()Full API access from your backend — manage units, trigger webhooks, read analytics.
// Server-side only — never expose rk_* keys to the browser
import { BagdockHive } from '@bagdock/hive'
const hive = new BagdockHive({
apiKey: process.env.BAGDOCK_RESTRICTED_KEY!, // rk_live_...
})
const units = await hive.units.list({ status: 'available' })
const rental = await hive.units.book({
unitId: 'unit_abc',
contactId: 'contact_123',
moveInDate: '2025-02-01',
})Use HiveChatTransport with the Vercel AI SDK for seamless useChat integration.
import { useChat } from '@ai-sdk/react'
import { HiveChatTransport } from '@bagdock/hive'
function ChatPage() {
const { messages, sendMessage, status } = useChat({
transport: new HiveChatTransport({
apiKey: 'ek_live_...',
operatorId: 'opreg_acme', // optional
}),
})
return (
<div>
{messages.map(m => <p key={m.id}>{m.content}</p>)}
<button onClick={() => sendMessage({ text: 'Find storage' })}>
Send
</button>
</div>
)
}Override the API URL for local testing against a development marketplace API.
const hive = new BagdockHive({
apiKey: 'ek_test_...',
baseUrl: 'https://abc123.ngrok.io', // or http://localhost:3001
})Or via environment variable (no code change needed):
BAGDOCK_API_URL=https://abc123.ngrok.io bun run devNote: Non-HTTPS URLs are blocked when
NODE_ENV=production.
| Method | Description |
|---|---|
chat.create() |
Start a new chat session |
chat.send(sessionId, { message }) |
Send a message |
chat.stream(params) |
Stream a chat response (SSE) |
chat.history(sessionId) |
Get session history |
| Method | Description |
|---|---|
units.list(params?) |
List units (filter by status, size, location) |
units.get(unitId) |
Get unit details |
units.book(params) |
Create a rental |
| Method | Description |
|---|---|
access.list(unitId) |
List access codes for a unit |
access.create(params) |
Generate a new access code |
access.revoke(codeId) |
Revoke an access code |
| Method | Description |
|---|---|
embedTokens.create(params) |
Create a new embed token |
embedTokens.validate(token) |
Validate a token |
embedTokens.revoke(tokenId) |
Revoke a token |
| Export | Description |
|---|---|
HiveChatTransport |
AI SDK ChatTransport for useChat integration |
BagdockHiveError |
Typed error class with status, code, requestId |
detectKeyType(key) |
Returns 'embed', 'restricted', 'webhook', or 'unknown' |
isTestKey(key) |
true if key contains _test_ |
isLiveKey(key) |
true if key contains _live_ |
scrubPii(text) |
Redact PII from a single string (re-exported from @bagdock/pii-patterns) |
scrubPiiDeep(value) |
Recursively scrub PII from objects and arrays |
scrubMessagesForModel(messages) |
Scrub PII from user role messages before AI inference |
| Option | Type | Default | Description |
|---|---|---|---|
apiKey |
string |
— | Required. Your Bagdock API key (ek_* or rk_*) |
baseUrl |
string |
https://api.bagdock.com |
API base URL |
maxRetries |
number |
3 |
Max retries for failed requests |
timeoutMs |
number |
30000 |
Request timeout in milliseconds |
auth |
AuthAdapterConfig |
— | Pluggable auth provider config |
// Clerk
{ provider: 'clerk', getToken: () => clerk.session?.getToken() ?? '' }
// Auth0
{ provider: 'auth0', domain: 'my-app.us.auth0.com', clientId: '...' }
// Stytch
{ provider: 'stytch' }
// Custom JWT
{ provider: 'custom', getToken: async () => myJwt }- HTTPS enforced in production — the SDK throws if a non-
https://base URL is used whenNODE_ENV=production - Client-side PII scrubbing —
chat.create(),chat.send(),chat.stream(), andHiveChatTransportautomatically scrub PII from user messages before they leave your application (defense-in-depth via@bagdock/pii-patterns). Server-side WASM scrubbing remains authoritative. - Embed keys (
ek_*) are origin-locked and safe for client-side use - Restricted keys (
rk_*) should only be used server-side - Never expose
rk_*orwhsec_*keys in client-side code
MIT