OpenAI gooit de deur open voor spraak in je SaaS
Gisteren lanceerde OpenAI nieuwe voice intelligence features in hun API. Denk aan betere spraakherkenning, realtime transcriptie en slimmere audio-analyse, allemaal via dezelfde API die je al kent. OpenAI noemt customer service als voor de hand liggend gebruik, maar ook educatieplatforms en creator-tools staan expliciet op de lijst.
Voor makers die een SaaS bouwen is dit relevant: je hoeft geen eigen spraakmodel te trainen of een dure derde partij zoals AssemblyAI te betalen. Het zit nu gewoon in je bestaande OpenAI-factuur.
Wat betekent dit voor jouw project?
Als je een SaaS bouwt voor coaches, therapeuten, docenten of klantenservice-teams, dan is het toevoegen van een "spreek je notitie in"-feature opeens veel toegankelijker. Geen complexe integratie meer, gewoon een microfoontje in je UI dat audio doorstuurt naar de API en tekst terugkrijgt. Claude Code kan het hele stuk van component tot API-route voor je schrijven, ook als je normaal niet programmeert.
Hoe pak je het aan met Claude Code?
Hieronder een concrete workflow in vier stappen. Ik ga ervan uit dat je een Next.js-project hebt draaien met een Supabase-backend, maar de aanpak werkt ook voor andere stacks.
Stap 1: Zet je OpenAI API-key klaar
Zorg dat je .env.local de key bevat:
OPENAI_API_KEY=sk-...
Geef Claude Code daarna deze context-prompt, zodat het weet wat je wil bouwen:
Ik bouw een Next.js 14 SaaS met App Router en Supabase. Ik wil een component toevoegen waarmee gebruikers een spraaknotitie kunnen inspreken. De audio wordt via een API-route doorgestuurd naar de OpenAI Whisper API (audio/transcriptions endpoint). De getranscribeerde tekst moet terugkomen in een textarea zodat de gebruiker hem nog kan aanpassen. Gebruik de nieuwste OpenAI node SDK. Maak het simpel en foutbestendig.
Stap 2: Laat Claude Code de API-route schrijven
Na de context-prompt vraag je Claude Code om de server-side route:
Schrijf een Next.js API-route op
app/api/transcribe/route.tsdie een POST-request accepteert met een audio-bestand als FormData. Stuur het door naaropenai.audio.transcriptions.createmet modelwhisper-1. Geef de tekst terug als JSON{ text: string }. Voeg foutafhandeling toe voor missende bestanden en API-fouten.
Claude Code levert dan iets als:
// app/api/transcribe/route.ts
import { NextRequest, NextResponse } from 'next/server';
import OpenAI from 'openai';
const openai = new OpenAI();
export async function POST(req: NextRequest) {
try {
const formData = await req.formData();
const file = formData.get('audio') as File;
if (!file) {
return NextResponse.json(
{ error: 'Geen audiobestand gevonden' },
{ status: 400 }
);
}
const transcription = await openai.audio.transcriptions.create({
file,
model: 'whisper-1',
language: 'nl',
});
return NextResponse.json({ text: transcription.text });
} catch (error) {
console.error('Transcriptie-fout:', error);
return NextResponse.json(
{ error: 'Transcriptie mislukt' },
{ status: 500 }
);
}
}
Let op de language: 'nl' parameter, die geeft betere resultaten voor Nederlandse spraak.
Stap 3: Laat Claude Code het UI-component maken
Schrijf een React client-component
VoiceInput.tsxmet de volgende functionaliteit: een knop om opname te starten en te stoppen via de MediaRecorder API, een loading-state tijdens het transcriberen, een textarea die de teruggekregen tekst toont en een onChange-prop zodat de parent de tekst kan ontvangen. Gebruik Tailwind voor styling. Geen externe libraries.
Een belangrijk stukje uit wat Claude Code teruggeeft:
// components/VoiceInput.tsx
'use client';
import { useState, useRef } from 'react';
interface VoiceInputProps {
onChange: (text: string) => void;
}
export function VoiceInput({ onChange }: VoiceInputProps) {
const [isRecording, setIsRecording] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [transcript, setTranscript] = useState('');
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
const chunksRef = useRef<Blob[]>([]);
const startRecording = async () => {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const recorder = new MediaRecorder(stream);
chunksRef.current = [];
recorder.ondataavailable = (e) => chunksRef.current.push(e.data);
recorder.onstop = handleStop;
mediaRecorderRef.current = recorder;
recorder.start();
setIsRecording(true);
};
const stopRecording = () => {
mediaRecorderRef.current?.stop();
setIsRecording(false);
};
const handleStop = async () => {
setIsLoading(true);
const blob = new Blob(chunksRef.current, { type: 'audio/webm' });
const formData = new FormData();
formData.append('audio', blob, 'opname.webm');
const res = await fetch('/api/transcribe', {
method: 'POST',
body: formData,
});
const data = await res.json();
setTranscript(data.text ?? '');
onChange(data.text ?? '');
setIsLoading(false);
};
return (
<div className="space-y-2">
<button
onClick={isRecording ? stopRecording : startRecording}
className={`px-4 py-2 rounded-lg font-medium ${
isRecording
? 'bg-red-500 text-white'
: 'bg-blue-600 text-white'
}`}
>
{isRecording ? 'Stop opname' : 'Start opname'}
</button>
{isLoading && <p className="text-sm text-gray-500">Transcriberen...</p>}
<textarea
value={transcript}
onChange={(e) => {
setTranscript(e.target.value);
onChange(e.target.value);
}}
rows={4}
className="w-full border rounded-lg p-2 text-sm"
placeholder="Jouw ingesproken tekst verschijnt hier..."
/>
</div>
);
}
Stap 4: Voeg het toe aan een bestaande pagina
Vraag Claude Code ten slotte om de component in te passen:
Voeg
VoiceInputtoe aan mijnapp/notes/new/page.tsx. De getranscribeerde tekst moet in de bestaandebody-state komen. Laat de rest van het formulier ongewijzigd.
Claude Code past de import en de state-koppeling voor je aan, zonder dat je de hele pagina hoeft te herschrijven.
Wat te checken na afloop
- Werkt de microfoontoegang in de browser (popup verschijnt bij eerste klik)?
- Komt er na het stoppen van de opname een laadmelding en daarna tekst in de textarea?
- Check de browser-console op CORS-fouten (zou niet moeten, want zelfde domein via
/api/transcribe). - Kijk in je OpenAI-dashboard of het
whisper-1verbruik zichtbaar is onder "Usage" - dat bevestigt dat de route echt aanroept. - Test ook met een korte Nederlandse zin om te verifiëren dat de
language: 'nl'parameter het verschil maakt ten opzichte van auto-detect.
Bij Eighty leer ik je Claude Code in het Nederlands gebruiken, van installatie tot een werkend SaaS-product. Wekelijks een nieuwe module, persoonlijke begeleiding.
