Skip to content

Recipe: Keep API Key Server-Side

Create an API route on your server that holds the key and forwards requests.

TanStack Start server function:

app/server/intent.ts
import { createServerFn } from '@tanstack/start'
import { createIntentForm } from '@intentform/core'
import { openaiProvider } from '@intentform/provider-openai'
import { accidentReportModel } from '../models/accident-report'
const engine = createIntentForm({
provider: openaiProvider({ apiKey: process.env.OPENAI_API_KEY! }),
models: [accidentReportModel],
})
export const parseIntent = createServerFn('POST', async ({ intent, modelId }) => {
return engine.parse(intent, modelId)
})

Next.js API route:

app/api/intent/route.ts
import { createIntentForm } from '@intentform/core'
import { openaiProvider } from '@intentform/provider-openai'
import { accidentReportModel } from '@/models/accident-report'
const engine = createIntentForm({
provider: openaiProvider({ apiKey: process.env.OPENAI_API_KEY! }),
models: [accidentReportModel],
})
export async function POST(request: Request) {
const { intent, modelId } = await request.json()
const result = await engine.parse(intent, modelId)
return Response.json(result)
}

Pass a custom provider to the client-side engine. The provider calls your server endpoint instead of the AI provider directly.

providers/server-provider.ts
import type { AiProvider, GenerateInput } from '@intentform/core'
export function serverProvider(endpoint = '/api/intent'): AiProvider {
return {
async generateStructured(input: GenerateInput) {
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(input),
})
if (!response.ok) {
throw new Error(`Intent API error: ${response.status}`)
}
return response.json()
},
}
}
// client engine — no API key here
import { createIntentForm } from '@intentform/core'
import { serverProvider } from './providers/server-provider'
export const engine = createIntentForm({
provider: serverProvider('/api/intent'),
models: [accidentReportModel],
})

Your server endpoint (Option A) then uses the real provider with the key from the environment.