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

Laravel 13 HTTP Client Tutorial: GET, POST, Headers, Retries and Error Handling

๐Ÿ› ๏ธ Laravel Tutorials4 min read

Laravel 13 HTTP Client Tutorial: GET, POST, Headers, Retries and Error Handling

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 9, 2026
Laravel 13 HTTP Client Tutorial: GET, POST, Headers, Retries and Error Handling

To call an external API in Laravel 13, use the Http facade: Http::get($url) or Http::post($url, $data). It wraps Guzzle, returns a Response object, and lets you add headers, tokens, timeouts, retries and error handling in a fluent chain.

You do not need to install Guzzle yourself. This guide shows GET and POST calls, authentication, retries, error handling and how to fake responses in tests.

What do I need first?

  • Laravel 13 (current stable at the time of writing) on a supported PHP version.
  • Any JSON API to call. The examples use a placeholder host, api.example.com.

How do I make a GET request?

JavaScript
use Illuminate\Support\Facades\Http;

$response = Http::get('https://api.example.com/products', [
    'page'     => 1,
    'per_page' => 20,
]);

if ($response->successful()) {
    $products = $response->json('data');
}

The array becomes the query string. The json() method decodes the body, and json('data') reads one key using dot notation. Other useful checks are failed(), clientError() and serverError(), plus status() for the exact code.

How do I make a POST request with JSON?

JavaScript
$response = Http::acceptJson()->post('https://api.example.com/orders', [
    'sku'      => 'BK-100',
    'quantity' => 2,
]);

$order = $response->json();

By default post() sends the array as JSON. If the API expects a classic form body, add asForm():

JavaScript
Http::asForm()->post('https://api.example.com/login', [
    'username' => 'demo',
    'password' => 'secret',
]);

How do I send headers and tokens?

JavaScript
$response = Http::withToken(config('services.shop.token'))
    ->withHeaders(['X-Client' => 'my-laravel-app'])
    ->get('https://api.example.com/me');

Keep tokens in .env and read them through config/services.php. Never hard-code a secret in a controller.

JavaScript
// config/services.php
'shop' => [
    'url'   => env('SHOP_API_URL'),
    'token' => env('SHOP_API_TOKEN'),
],

How do I avoid repeating the base URL?

Shell
$shop = Http::baseUrl(config('services.shop.url'))
    ->withToken(config('services.shop.token'))
    ->acceptJson()
    ->timeout(10);

$products = $shop->get('/products')->json('data');

For a client you use in many places, put this chain in a small service class and inject it into controllers.

How do I set timeouts and retries?

Ruby
$response = Http::timeout(10)
    ->retry(3, 200)
    ->get('https://api.example.com/reports');

timeout(10) gives up after ten seconds. retry(3, 200) tries up to three times, waiting 200 milliseconds between attempts. Retry only requests that are safe to repeat. A POST that creates an order may create it twice if the first attempt succeeded but the response was lost.

How do I handle errors?

HTTP errors such as 404 or 500 do not throw by default. Either check the response or call throw().

PHP
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Client\ConnectionException;

try {
    $data = Http::timeout(10)
        ->get('https://api.example.com/products/99')
        ->throw()
        ->json();
} catch (RequestException $e) {
    report($e);
    $status = $e->response->status();
} catch (ConnectionException $e) {
    report($e);
}

A connection failure, such as a DNS error or timeout, raises ConnectionException. A 4xx or 5xx response raises RequestException after throw().

How do I test code that calls an API?

Never hit the real API in tests. Fake it.

JavaScript
use Illuminate\Support\Facades\Http;

public function test_products_are_loaded(): void
{
    Http::preventStrayRequests();

    Http::fake([
        'api.example.com/products*' => Http::response(
            ['data' => [['id' => 1, 'name' => 'Book']]],
            200
        ),
    ]);

    $this->get('/shop')->assertOk()->assertSee('Book');

    Http::assertSent(fn ($request) => str_contains($request->url(), '/products'));
}

preventStrayRequests() makes any unfaked request throw, so a forgotten call cannot reach the internet.

What are the common mistakes?

  • Assuming errors throw. Without throw(), a 500 response returns normally and json() may be empty.
  • No timeout. A slow API then holds your PHP worker open. Always set one.
  • Retrying non-idempotent calls. Add an idempotency key if the API supports it.
  • Calling APIs in the request cycle. For slow work, dispatch a queued job instead.
  • Leaking secrets in logs. Do not log full request headers.

How do I check that it works?

Run a quick call in php artisan tinker against a public JSON endpoint and inspect status() and json(). For your own code, rely on the faked tests above.

FAQ

Do I still need to install Guzzle?

No. Laravel ships the HTTP client and Guzzle is already a dependency of it.

How do I upload a file?

Use Http::attach('file', file_get_contents($path), 'report.pdf')->post($url).

Can I send requests in parallel?

Yes. Http::pool() runs several requests concurrently. See the official docs for the syntax.

References

Article Index & Topic Tags
LaravelApiHttp-ClientGuzzle

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.