Muhammad Yasir Amin

Loading websiteโ€ฆ

More from Muhammad Yasir Amin

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Subscribe on YouTube

How to Integrate the OpenAI API (ChatGPT) with Laravel 13

๐Ÿ› ๏ธ Laravel Tutorials4 min read

How to Integrate the OpenAI API (ChatGPT) with Laravel 13

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 12, 2026
How to Integrate the OpenAI API (ChatGPT) with Laravel 13

You can integrate the OpenAI API (the API behind ChatGPT) with Laravel 13 using the built-in HTTP client. Store the API key in .env, wrap the call in a small service class, and send a POST request to the Responses endpoint from a controller on your server. The key must never reach the browser. This guide builds the whole flow, including validation, timeouts and a test that does not call OpenAI.

Targets: Laravel 13 (PHP 8.3 to 8.5) and the OpenAI Responses API. Model names and prices change often, so the model is a config value, and you should pick a current one from the OpenAI documentation.

Step 1: Get an API key

Create a key in your OpenAI platform account and add billing if the API requires it. Treat the key like a password. Put it in .env only:

Code
OPENAI_API_KEY=your-secret-key
OPENAI_MODEL=put-a-current-model-name-here

Step 2: Register the config

Add an entry to config/services.php so the app reads settings through config(), which also works when the configuration is cached:

JavaScript
'openai' => [
    'key' => env('OPENAI_API_KEY'),
    'model' => env('OPENAI_MODEL'),
    'base_url' => env('OPENAI_BASE_URL', 'https://api.openai.com/v1'),
],

Step 3: Write a service class

Keeping the API call in one class makes it easy to reuse and to fake in tests.

JavaScript
<?php

namespace App\Services;

use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
use RuntimeException;

class OpenAiService
{
    public function ask(string $prompt): string
    {
        try {
            $response = Http::withToken(config('services.openai.key'))
                ->baseUrl(config('services.openai.base_url'))
                ->timeout(60)
                ->retry(2, 500, throw: false)
                ->post('/responses', [
                    'model' => config('services.openai.model'),
                    'input' => $prompt,
                ])
                ->throw();
        } catch (RequestException $e) {
            report($e);

            throw new RuntimeException('The AI service is not available right now.');
        }

        return $this->extractText($response->json());
    }

    private function extractText(array $payload): string
    {
        $text = '';

        foreach ($payload['output'] ?? [] as $item) {
            foreach ($item['content'] ?? [] as $part) {
                if (($part['type'] ?? null) === 'output_text') {
                    $text .= $part['text'];
                }
            }
        }

        return trim($text);
    }
}

The Responses API returns an output list. Each message item contains content parts, and the generated text is in parts whose type is output_text. The helper joins them. Check the current response shape in the OpenAI reference if you extend this code.

retry(2, 500, throw: false) retries failed requests twice with a 500 ms pause, and the following throw() raises an exception if the final answer is still an error.

Step 4: Create the controller and route

JavaScript
<?php

namespace App\Http\Controllers;

use App\Services\OpenAiService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use RuntimeException;

class ChatController extends Controller
{
    public function __invoke(Request $request, OpenAiService $ai): JsonResponse
    {
        $data = $request->validate([
            'message' => ['required', 'string', 'max:2000'],
        ]);

        try {
            $answer = $ai->ask($data['message']);
        } catch (RuntimeException $e) {
            return response()->json(['message' => $e->getMessage()], 503);
        }

        return response()->json(['answer' => $answer]);
    }
}
CSS
use App\Http\Controllers\ChatController;

Route::post('/chat', ChatController::class)->middleware('throttle:20,1');

Limiting the message length and adding a throttle protect your bill. Each request costs money, so anonymous public endpoints should also require login.

Step 5: Try it

Shell
curl -X POST http://localhost:8000/api/chat \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{"message":"Explain Laravel service providers in two sentences."}'

Put the route in routes/api.php (run php artisan install:api first) or use a web route with CSRF protection for a form.

How do I test it without calling OpenAI?

Laravel can fake outgoing HTTP calls, so tests are fast and free:

JavaScript
use Illuminate\Support\Facades\Http;

public function test_chat_returns_the_answer(): void
{
    config(['services.openai.key' => 'test', 'services.openai.model' => 'test-model']);

    Http::fake([
        '*/responses' => Http::response([
            'output' => [[
                'type' => 'message',
                'content' => [['type' => 'output_text', 'text' => 'Hello from the fake']],
            ]],
        ]),
    ]);

    $this->postJson('/api/chat', ['message' => 'Hi'])
        ->assertOk()
        ->assertJson(['answer' => 'Hello from the fake']);
}

Common mistakes

  • Calling OpenAI from JavaScript: this exposes the key. Always go through your Laravel backend.
  • Hard-coding the model name: models are renamed and retired. Keep it in config.
  • No timeout: model calls can be slow. Set a timeout so PHP workers are not blocked forever.
  • Running long calls inside the web request at scale: for heavy use, dispatch a queued job and show the result later.
  • Logging prompts that contain private data: be careful what you store.

FAQ

Should I use the HTTP client or an OpenAI package?

The HTTP client has no extra dependency and is enough for simple calls. A package adds typed helpers if you need streaming or many endpoints.

Why do I get a 401 error?

The key is missing, wrong or revoked. Run php artisan config:clear after changing .env.

Why a 429 error?

You hit a rate or usage limit. Retry with a delay, reduce requests or check your plan's limits.

References

Article Index & Topic Tags
LaravelApiHttp-ClientOpenai

YouTube Channel

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Muhammad Yasir Amin
Available for Contracts
Author & Solutions Architect

Senior Full Stack Engineer

Independent Senior Full Stack Engineer working on Laravel, React and Next.js applications, APIs and delivery workflows.

Laravel & PHP ApplicationsReact & Next.js InterfacesREST APIs & IntegrationsDatabase Queries & DebuggingDocker Development WorkflowsClear Scope & Handover
Discuss Your Next Step

What Does Your Application Need Next?

Share your current system, the problem you want to solve and what a useful result looks like. We can identify the right starting point and agree on a practical scope.

Technical Audit

Find the Next Useful Fix

Application Development

Scoped, Reviewable Milestones

Ongoing Support

Agreed Capacity and Priorities

Share your goals, current stack and requirements before the call.