Skip to main content

Documentation Index

Fetch the complete documentation index at: https://kawax.biz/llms.txt

Use this file to discover all available pages before exploring further.

What is the HTTP client?

Laravel’s HTTP client wraps Guzzle with a concise, expressive API. Access it through the Http facade to send requests to external web services.
use Illuminate\Support\Facades\Http;

$response = Http::get('https://api.example.com/users');
Guzzle is included with Laravel, so no extra installation is required.

Making requests

GET

use Illuminate\Support\Facades\Http;

$response = Http::get('https://api.example.com/users');

// Pass query parameters as an array
$response = Http::get('https://api.example.com/users', [
    'page'     => 1,
    'per_page' => 20,
]);

POST

Data is sent as application/json by default:
$response = Http::post('https://api.example.com/users', [
    'name'  => 'Taylor Otwell',
    'email' => '[email protected]',
]);

PUT, PATCH, DELETE

$response = Http::put('https://api.example.com/users/1', [
    'name' => 'Taylor Otwell',
]);

$response = Http::patch('https://api.example.com/users/1', [
    'email' => '[email protected]',
]);

$response = Http::delete('https://api.example.com/users/1');

Handling responses

Every request method returns an Illuminate\Http\Client\Response instance:
$response = Http::get('https://api.example.com/users/1');

// Body
$response->body();        // Raw string
$response->json();        // Decoded array
$response->json('name');  // Single key from JSON
$response->object();      // stdClass object
$response->collect();     // Laravel Collection

// Status
$response->status();      // HTTP status code
$response->successful();  // True for 2xx
$response->failed();      // True for 4xx and 5xx
$response->clientError(); // True for 4xx
$response->serverError(); // True for 5xx

// Common status helpers
$response->ok();                   // 200
$response->created();              // 201
$response->noContent();            // 204
$response->notFound();             // 404
$response->unauthorized();         // 401
$response->forbidden();            // 403
$response->unprocessableEntity();  // 422
$response->tooManyRequests();      // 429
JSON responses support array access:
$name = Http::get('https://api.example.com/users/1')['name'];

Request options

Headers

$response = Http::withHeaders([
    'X-Api-Version'  => '2',
    'Accept-Language' => 'en',
])->get('https://api.example.com/users');

// Shorthand to accept JSON
$response = Http::acceptJson()->get('https://api.example.com/users');
For browser-side AJAX requests to your Laravel app (not outbound server-to-server calls), see CSRF protection for X-CSRF-TOKEN and X-XSRF-TOKEN usage.

Authentication

// Bearer token (most common)
$response = Http::withToken($token)->get('https://api.example.com/me');

// Basic auth
$response = Http::withBasicAuth('[email protected]', 'password')
    ->get('https://api.example.com/private');

Base URL

When you make multiple requests to the same host, set a base URL once:
$response = Http::baseUrl('https://api.example.com')
    ->withToken($token)
    ->get('/users/1');

Form data

Send data as application/x-www-form-urlencoded:
$response = Http::asForm()->post('https://api.example.com/login', [
    'username' => 'taylor',
    'password' => 'secret',
]);

Timeouts

// Response timeout (default: 30 seconds)
$response = Http::timeout(10)->get('https://api.example.com/slow-endpoint');

// Connection timeout (default: 10 seconds)
$response = Http::connectTimeout(5)->get('https://api.example.com/endpoint');
Exceeding the timeout throws Illuminate\Http\Client\ConnectionException. Always set a timeout when calling external APIs.

Retries

Automatically retry failed requests:
// Retry up to 3 times, waiting 100 ms between attempts
$response = Http::retry(3, 100)->post('https://api.example.com/orders', $data);

// Retry only on connection errors
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\ConnectionException;

$response = Http::retry(3, 100, function (Throwable $exception, PendingRequest $request) {
    return $exception instanceof ConnectionException;
})->post('https://api.example.com/orders', $data);

Error handling

The HTTP client does not throw an exception for 4xx or 5xx responses by default. Check the status manually:
$response = Http::get('https://api.example.com/users/999');

if ($response->notFound()) {
    // Handle 404
}

if ($response->failed()) {
    logger()->error('API request failed', ['status' => $response->status()]);
}

Throwing on error

Call throw() to turn an error response into an Illuminate\Http\Client\RequestException:
// Throws for any 4xx / 5xx response
$response = Http::post('https://api.example.com/users', $data)->throw();

// Throw only when a condition is true
$response->throwIf($response->status() === 422);

// Throw unless the response matches the expected status
$response->throwUnlessStatus(201);
Because throw() returns the response instance, you can chain further calls:
$user = Http::post('https://api.example.com/users', $data)
    ->throw()
    ->json();
Catch the exceptions separately:
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;

try {
    $response = Http::timeout(5)
        ->post('https://api.example.com/users', $data)
        ->throw();
} catch (ConnectionException $e) {
    // Timeout or network failure
} catch (RequestException $e) {
    // 4xx / 5xx error — $e->response holds the response
    logger()->error('API error', ['status' => $e->response->status()]);
}

Concurrent requests

Send multiple requests simultaneously with pool():
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;

$responses = Http::pool(fn (Pool $pool) => [
    $pool->as('users')->get('https://api.example.com/users'),
    $pool->as('posts')->get('https://api.example.com/posts'),
    $pool->as('comments')->get('https://api.example.com/comments'),
]);

$users    = $responses['users']->json();
$posts    = $responses['posts']->json();
$comments = $responses['comments']->json();
Concurrent requests are ideal for dashboards that aggregate data from multiple endpoints. See the Concurrency guide for running other types of concurrent tasks beyond HTTP requests.

Testing

Faking responses

Use Http::fake() to intercept requests in tests without hitting real URLs:
use Illuminate\Support\Facades\Http;

// Return 200 for every request
Http::fake();

// Return specific responses per URL pattern
Http::fake([
    'api.example.com/users/*' => Http::response(['id' => 1, 'name' => 'Taylor'], 200),
    'api.example.com/posts/*' => Http::response(['error' => 'Not Found'], 404),
    '*'                        => Http::response('OK', 200),
]);
Return responses in sequence:
Http::fake([
    'api.example.com/*' => Http::sequence()
        ->push(['id' => 1], 200)
        ->push(['id' => 2], 200)
        ->pushStatus(429),
]);

Asserting requests

Verify that your code sent the expected requests:
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;

Http::fake();

Http::withToken('my-token')->post('https://api.example.com/users', [
    'name' => 'Taylor',
]);

Http::assertSent(function (Request $request) {
    return $request->url() === 'https://api.example.com/users'
        && $request->hasHeader('Authorization', 'Bearer my-token')
        && $request['name'] === 'Taylor';
});

Http::assertNotSent(fn (Request $request) => str_contains($request->url(), '/admin'));

Http::assertSentCount(1);
Add Http::preventStrayRequests() to your test setup. Any request that does not match a fake URL will throw an exception, catching accidental real HTTP calls.

Service class example

Encapsulate HTTP client logic in a dedicated service class for better testability and reuse.
1

Create a service class

<?php

namespace App\Services;

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

class GitHubService
{
    private string $baseUrl = 'https://api.github.com';

    public function __construct(
        private readonly string $token,
    ) {}

    /**
     * @throws ConnectionException
     * @throws RequestException
     */
    public function getUser(string $username): array
    {
        return Http::baseUrl($this->baseUrl)
            ->withToken($this->token)
            ->acceptJson()
            ->timeout(10)
            ->get("/users/{$username}")
            ->throw()
            ->json();
    }

    /**
     * @throws ConnectionException
     * @throws RequestException
     */
    public function getRepositories(string $username, int $page = 1): array
    {
        return Http::baseUrl($this->baseUrl)
            ->withToken($this->token)
            ->acceptJson()
            ->timeout(10)
            ->retry(2, 500)
            ->get("/users/{$username}/repos", [
                'page'     => $page,
                'per_page' => 30,
                'sort'     => 'updated',
            ])
            ->throw()
            ->json();
    }
}
2

Register in a service provider

// app/Providers/AppServiceProvider.php
use App\Services\GitHubService;

public function register(): void
{
    $this->app->singleton(GitHubService::class, function () {
        return new GitHubService(
            token: config('services.github.token'),
        );
    });
}
// config/services.php
'github' => [
    'token' => env('GITHUB_TOKEN'),
],
3

Use in a controller

<?php

namespace App\Http\Controllers;

use App\Services\GitHubService;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\JsonResponse;

class GitHubController extends Controller
{
    public function __construct(
        private readonly GitHubService $github,
    ) {}

    public function show(string $username): JsonResponse
    {
        try {
            $user = $this->github->getUser($username);
            return response()->json($user);
        } catch (ConnectionException) {
            return response()->json(['error' => 'Could not connect to GitHub API.'], 503);
        } catch (RequestException $e) {
            if ($e->response->status() === 404) {
                return response()->json(['error' => 'User not found.'], 404);
            }
            return response()->json(['error' => 'GitHub API error.'], 502);
        }
    }
}
4

Write a test

<?php

namespace Tests\Unit\Services;

use App\Services\GitHubService;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;

class GitHubServiceTest extends TestCase
{
    private GitHubService $service;

    protected function setUp(): void
    {
        parent::setUp();

        Http::fake([
            'api.github.com/users/octocat' => Http::response([
                'login'        => 'octocat',
                'name'         => 'The Octocat',
                'public_repos' => 8,
            ], 200),
            'api.github.com/users/notfound' => Http::response(
                ['message' => 'Not Found'],
                404
            ),
        ]);

        $this->service = new GitHubService(token: 'test-token');
    }

    public function test_it_fetches_a_user(): void
    {
        $user = $this->service->getUser('octocat');

        $this->assertEquals('octocat', $user['login']);
        $this->assertEquals('The Octocat', $user['name']);

        Http::assertSent(function ($request) {
            return $request->url() === 'https://api.github.com/users/octocat'
                && $request->hasHeader('Authorization', 'Bearer test-token');
        });
    }
}

Quick reference

MethodPurpose
Http::get($url, $query)GET request
Http::post($url, $data)POST request (JSON)
Http::put($url, $data)PUT request
Http::patch($url, $data)PATCH request
Http::delete($url)DELETE request
->withToken($token)Bearer authentication
->withHeaders($headers)Custom headers
->timeout($seconds)Response timeout
->connectTimeout($seconds)Connection timeout
->retry($times, $sleep)Automatic retries
->throw()Throw on error response
Http::fake()Mock responses in tests
Http::pool($callback)Concurrent requests
MethodStatus code
successful()2xx
failed()4xx or 5xx
clientError()4xx
serverError()5xx
ok()200
created()201
notFound()404
unauthorized()401
forbidden()403
unprocessableEntity()422
tooManyRequests()429
  • Wrap HTTP client logic in a service class, not directly in controllers.
  • Always set a timeout. External APIs can hang indefinitely without one.
  • Use retry() for transient errors such as network blips or rate limiting.
  • Call Http::fake() at the start of every test that involves HTTP calls.
  • Add Http::preventStrayRequests() to your test setup to catch accidental real requests.
  • Store API tokens in environment variables and reference them through config/services.php.
Last modified on May 26, 2026