Voice input is useful when your application needs information faster than users can type it. Support teams can transcribe calls. Sales teams can capture notes. Mobile users can submit voice messages without opening a keyboard.
Laravel gives you a clean way to expose this capability through a REST endpoint. The Laravel AI SDK handles the provider integration, while Laravel manages routing, validation, storage, queues, and testing.
This guide shows how to build a practical transcription API with Laravel. You will create an endpoint that accepts an audio upload and returns a JSON transcript.
Laravel is a productive PHP web framework for this workflow. Its built-in validation, filesystem, and queue features cover the application layer. The AI SDK supplies a unified transcription API across supported providers.
What We Are Building
The API will expose this endpoint:
POST /api/transcriptions
Content-Type: multipart/form-data
The request will contain an audio file. A successful response will look like this:
{
"text": "Please schedule a call with the customer tomorrow morning."
}
The initial version is synchronous. The request uploads the audio, sends it to the configured AI provider, and returns the transcript.
That pattern works well for short recordings. Longer files should move to a queued workflow, which we will cover later.

Install the Laravel AI SDK
Start with a Laravel application. Then install the SDK with Composer:
composer require laravel/ai
Publish the SDK configuration and migration files:
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
php artisan migrate
The published configuration lives in config/ai.php. Add the credentials for your chosen provider to .env.
For example:
OPENAI_API_KEY=your-api-key
The current AI SDK supports speech-to-text through OpenAI, ElevenLabs, Mistral, and Gemini. You can review the complete provider support matrix before selecting a provider.
The SDK keeps your application code consistent. Provider credentials and default models remain configuration concerns.
Define the Transcription Route
Create an invokable controller:
php artisan make:controller TranscriptionController --invokable
Register the route in routes/api.php:
<?php
use App\Http\Controllers\TranscriptionController;
use Illuminate\Support\Facades\Route;
Route::post('/transcriptions', TranscriptionController::class);
If the endpoint belongs to authenticated users, add your API authentication middleware. Laravel Sanctum is a practical option for first-party SPAs and mobile clients.
Route::middleware('auth:sanctum')->group(function () {
Route::post('/transcriptions', TranscriptionController::class);
});
Keep the route focused. Validation and transcription logic belong in the controller or a dedicated application service.
Validate the Audio Upload
Audio is user-controlled input. Validate it before sending anything to an external provider.
Laravel includes file validation rules for MIME types, extensions, and file sizes. Use MIME validation when possible because the framework checks the file contents instead of trusting only the filename.
Open app/Http/Controllers/TranscriptionController.php:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Laravel\Ai\Transcription;
class TranscriptionController extends Controller
{
public function __invoke(Request $request): JsonResponse
{
$request->validate([
'audio' => [
'required',
'file',
'mimetypes:audio/mpeg,audio/wav,audio/webm,audio/ogg',
'max:25600',
],
]);
$transcript = Transcription::fromUpload(
$request->file('audio')
)->generate();
return response()->json([
'text' => (string) $transcript,
]);
}
}
The max:25600 rule limits the upload to 25 MB because file sizes are expressed in kilobytes.
Adjust the accepted MIME types and size for your provider. Some providers support additional formats. Your application should also define a duration limit if long recordings create unacceptable processing costs.
For larger applications, move these rules into a form request:
php artisan make:request TranscriptionRequest
A form request keeps authorization and validation outside the controller. Laravel automatically returns a 422 JSON response when validation fails for an API request.
Transcribe Audio with the AI SDK
The key operation is concise:
$transcript = Transcription::fromUpload(
$request->file('audio')
)->generate();
The SDK also accepts files from a local path or Laravel storage:
use Laravel\Ai\Transcription;
$fromPath = Transcription::fromPath(
storage_path('app/private/audio.mp3')
)->generate();
$fromStorage = Transcription::fromStorage(
'audio/audio.mp3'
)->generate();
Cast the response to a string when you need the raw transcript:
$text = (string) $transcript;
The SDK handles provider-specific request details. Your controller does not need to construct multipart HTTP requests or parse provider response formats.
This is where Laravel’s ecosystem helps. The framework handles the HTTP boundary, while the AI SDK provides one expressive API for the provider call.
Store Audio Before Processing
The direct upload example is useful for short recordings. A production API often stores the source file first.
Laravel’s filesystem abstraction supports local storage, Amazon S3, and other S3-compatible services through the same API. Store the upload on a private disk:
$path = $request->file('audio')->store('transcriptions');
Then transcribe the stored file:
$transcript = Transcription::fromStorage($path)->generate();
You can persist the transcript with an Eloquent model:
$recording = Recording::create([
'audio_path' => $path,
'status' => 'processing',
]);
$transcript = Transcription::fromStorage($path)->generate();
$recording->update([
'text' => (string) $transcript,
'status' => 'completed',
]);
Keep recordings private unless users need public access. For downloads, generate short-lived URLs with Laravel’s temporary URL support.

Add Speaker Diarization
Meetings, interviews, and support calls often include multiple speakers. The AI SDK can request a diarized transcript:
$transcript = Transcription::fromStorage($path)
->diarize()
->generate();
The response includes the regular text transcript and speaker-segmented data. Store both when your application needs to display who said what.
A simple response can still expose the raw text:
return response()->json([
'text' => (string) $transcript,
]);
For richer output, inspect the response object and map its diarized segments into your own API schema. Keep your public response format stable even if you change providers later.
Queue Longer Transcriptions
Speech processing can take longer than a normal HTTP request should remain open. Laravel’s queue system lets the API respond quickly while a worker processes the audio in the background.
The AI SDK supports queued transcription generation:
use Laravel\Ai\Responses\TranscriptionResponse;
Transcription::fromStorage($path)
->queue()
->then(function (TranscriptionResponse $transcript) use ($recording) {
$recording->update([
'text' => (string) $transcript,
'status' => 'completed',
]);
});
Your controller can return a 202 Accepted response:
return response()->json([
'id' => $recording->id,
'status' => $recording->status,
], 202);
The client can poll a status endpoint:
Route::get('/transcriptions/{recording}', function (Recording $recording) {
return response()->json([
'id' => $recording->id,
'status' => $recording->status,
'text' => $recording->text,
]);
});
Run a worker locally with:
php artisan queue:work
In production, use a process monitor or a managed platform such as Laravel Cloud. Monitor queue health and provider failures with appropriate application logging and Laravel Nightwatch.
Queueing also gives you a clean place to retry transient provider failures. Configure job attempts and timeouts based on the maximum recording length and provider response time.
Turn Transcripts into Actionable Data
A transcript is useful. Structured data is easier to query.
Once the audio becomes text, you can pass it to an AI SDK agent for tasks such as:
- Extracting action items.
- Identifying customer sentiment.
- Finding names, dates, and order numbers.
- Classifying support requests.
- Creating a summary for a CRM record.
For example, store the raw transcript first. Then dispatch a second job that extracts structured fields. This separation makes each stage easier to retry and test.
A useful database shape might include:
recordings
- id
- audio_path
- status
- text
- language
- processed_at
- created_at
- updated_at
You can add a JSON column for extracted data:
- metadata
Keep the original transcript. It provides an audit trail and lets you reprocess the text when your extraction rules change.
Test Without Calling an AI Provider
The AI SDK includes a fake transcription implementation. Use it in feature tests:
use Illuminate\Http\UploadedFile;
use Laravel\Ai\Transcription;
test('audio can be transcribed', function () {
Transcription::fake([
'Please schedule a call tomorrow morning.',
]);
$response = $this->postJson('/api/transcriptions', [
'audio' => UploadedFile::fake()->create(
'message.mp3',
100,
'audio/mpeg'
),
]);
$response
->assertOk()
->assertJson([
'text' => 'Please schedule a call tomorrow morning.',
]);
});
You can also assert that the transcription request used diarization:
Transcription::fake();
$this->postJson('/api/transcriptions', [
'audio' => UploadedFile::fake()->create(
'meeting.mp3',
100,
'audio/mpeg'
),
]);
Transcription::assertGenerated(function ($prompt) {
return $prompt->isDiarized();
});
Use Storage::fake() when your application stores audio before processing. This keeps tests fast and prevents test files from reaching real storage.
These testing features are among the most useful PHP developer tools for building reliable AI workflows. External providers should be tested separately from your request validation and persistence logic.
Production Checklist
Before exposing the endpoint publicly, review these areas:
- Require authentication for private recordings.
- Validate MIME types and file size.
- Store uploads on a private disk.
- Limit recording duration where possible.
- Add rate limiting to the upload route.
- Queue long-running transcriptions.
- Record provider errors and processing status.
- Retry transient failures carefully.
- Keep raw transcripts separate from extracted data.
- Delete audio files when retention policies require it.
- Test validation, storage, queueing, and provider calls independently.
You now have the core pieces needed to build REST API with PHP: a validated multipart endpoint, provider-independent transcription, private file storage, asynchronous processing, and testable application code.
Laravel keeps the application boundary familiar. The AI SDK keeps speech-to-text integration compact. Together, they give you a practical foundation for turning voice recordings into searchable, structured, and actionable data.
