Server SDK
The @guidekit/server package provides server-side token generation, LLM/STT/TTS proxy routes, and session key storage. API keys never appear in JWT payloads or client bundles.
Installation
npm install @guidekit/serverQuick setup (Next.js App Router)
// app/api/guidekit/token/route.ts
import { createNextAppRouterRoutes } from '@guidekit/server/next';
const routes = createNextAppRouterRoutes({
signingSecret: process.env.GUIDEKIT_SECRET!,
createTokenOptions: () => ({
llmApiKey: process.env.LLM_API_KEY!,
sttApiKey: process.env.STT_API_KEY,
ttsApiKey: process.env.TTS_API_KEY,
expiresIn: '15m',
}),
siteKnowledge: {
documents: [
{
id: 'pricing',
title: 'Pricing',
content: 'The Pro plan includes guided autonomy and priority support.',
metadata: { url: '/pricing' },
},
],
},
});
export const POST = routes.POST_token;
// app/api/guidekit/llm/route.ts
export const POST = routes.POST_llm;
// app/api/guidekit/health/route.ts
export const GET = routes.GET_health;
// app/api/guidekit/site-search/route.ts
export const POST = routes.POST_siteSearch;Client configuration (zero browser keys)
<GuideKitProvider
tokenEndpoint="/api/guidekit/token"
proxy={{ llm: '/api/guidekit/llm', health: '/api/guidekit/health' }}
llm={{ provider: 'gemini', model: 'gemini-2.5-flash' }}
siteKnowledge={{ endpoint: '/api/guidekit/site-search', topK: 5 }}
/>Session storage
Provider keys are stored server-side keyed by sessionId (not jti). The JWT contains sessionId, permissions, and expiry — never API keys.
import { InMemorySessionStore, getSessionKeys } from '@guidekit/server';
// Default in-memory store (single instance)
const keys = await getSessionKeys('my-session-id');
// Production: Redis adapter
import { RedisSessionStore } from '@guidekit/server/redis';In-memory vs Redis (production guidance)
- InMemorySessionStore: good for local dev and single-instance deployments.
- Not suitable for multi-instance or serverless deployments where requests can land on different processes.
- RedisSessionStore: recommended for production when you run multiple instances or need durability across restarts.
The reference example app (apps/example-nextjs) uses in-memory storage by default. Set REDIS_URL and install ioredis in that app to exercise the Redis path locally.
Example:
import Redis from 'ioredis';
import { RedisSessionStore } from '@guidekit/server/redis';
import { createNextAppRouterRoutes } from '@guidekit/server/next';
const redis = new Redis(process.env.REDIS_URL!);
const sessionStore = new RedisSessionStore({ redis });
const routes = createNextAppRouterRoutes({
signingSecret: process.env.GUIDEKIT_SECRET!,
sessionStore,
createTokenOptions: () => ({
llmApiKey: process.env.LLM_API_KEY!,
expiresIn: '15m',
allowedOrigins: ['https://yourapp.com'],
}),
});Framework-agnostic handler
For non-Next.js runtimes, use createGuideKitHandler:
import { createGuideKitHandler } from '@guidekit/server';
const handler = createGuideKitHandler({
signingSecret: process.env.GUIDEKIT_SECRET!,
createTokenOptions: () => ({
llmApiKey: process.env.LLM_API_KEY!,
expiresIn: '15m',
}),
});
// Route dispatch: 'token' | 'llm' | 'health' | 'stt' | 'tts' | 'site-search'
export async function onRequest(request: Request, route: string) {
return handler(request, route as 'token');
}Proxy routes
| Route | Method | Purpose |
|---|---|---|
/token | POST | Mint session JWT + store provider keys |
/llm | POST | Stream LLM requests with server-side key |
/stt | POST | Mint STT credentials for voice |
/tts | POST | Mint TTS credentials for voice |
/site-search | POST | Search server-backed website knowledge |
/health | GET | Health check |
All proxy routes require Authorization: Bearer <session-token> except /token. The default token permissions are stt, tts, llm, and site:read.
Rate limiting
createGuideKitHandler applies a sliding-window rate limiter keyed by session ID (when authenticated) or client IP.
Defaults
| Setting | Default | Meaning |
|---|---|---|
windowMs | 60_000 | 1-minute window |
maxRequests | 60 | Max requests per window per key |
When exceeded, the handler returns 429 with a Retry-After header (seconds).
Production recommendations
| Deployment | Suggested maxRequests | Notes |
|---|---|---|
| Single-tenant internal app | 60 (default) | Fine for most dashboards |
| Public consumer app | 30–40 | Tighten if you see abuse |
| High-traffic B2B | 80–120 | Monitor 429 rates; add CDN/WAF in front |
Tune via handler options:
const handler = createGuideKitHandler({
signingSecret: process.env.GUIDEKIT_SECRET!,
rateLimit: {
windowMs: 60_000,
maxRequests: 40,
},
createTokenOptions: () => ({ llmApiKey: process.env.LLM_API_KEY!, expiresIn: '15m' }),
});The example app reads GUIDEKIT_RATE_LIMIT_WINDOW_MS and GUIDEKIT_RATE_LIMIT_MAX for local tuning.
Origin allowlist (recommended in production)
Set allowedOrigins when minting tokens. Proxy routes (/llm, /stt, /tts) reject requests when the token includes an aud claim and the Origin header does not match.
createTokenOptions: () => ({
llmApiKey: process.env.LLM_API_KEY!,
expiresIn: '15m',
allowedOrigins: ['https://yourapp.com'],
}),In the example app, set GUIDEKIT_ALLOWED_ORIGINS=https://yourapp.com,https://staging.yourapp.com.
createSessionToken
import { createSessionToken } from '@guidekit/server';
const result = await createSessionToken({
signingSecret: process.env.GUIDEKIT_SECRET!,
sessionId: 'optional-custom-id',
llmApiKey: process.env.LLM_API_KEY!,
expiresIn: '15m',
allowedOrigins: ['https://yourapp.com'],
});
// result.token — send to client
// result.expiresIn — secondsSecret rotation
- Generate:
npx @guidekit/cli generate-secret - Sign with
[newSecret, oldSecret]array during rotation window - Validate accepts either secret until old tokens expire