Practical guide

How to build an API client in PHP

Keep HTTP details inside one small class. The rest of the application can then work with your data instead of a third-party API.

25 minutes · PHP

First, the short version

What should an API client do?

An API client is the part of an application that builds an HTTP request, sends it to a remote server, and translates the response into a shape your code understands.

A good boundary between client and server keeps URLs, headers, authentication, and foreign field names in one place. Callers receive your data object or a meaningful exception.

Get ready

What you need

Start with one safe read operation. Before writing a client for the entire API, verify one real request and response.

  • PHP 8.2 or newer and Composer.
  • Official API documentation: the base URL, endpoint, method, authentication, error statuses, and a response example.
  • A test account or token stored in an environment variable. A secret does not belong in source code or logs.
  • One small operation, such as fetching a product by ID, plus a sample successful and failed response.

Steps 1 to 3

Build a client with a clear boundary

The HTTP library handles transport. Your class must handle the contract, errors, and data mapping.

1. Install an HTTP client and set shared options

  1. Use a maintained library instead of wrapping cURL yourself. Symfony HttpClient also works outside a Symfony project.
  2. Set the base URL, Accept header, authentication, and a short timeout in one place. Do not repeat URLs or tokens in every method.
  3. Distinguish the timeout of one network operation from the maximum duration of the whole request. A remote service must not hold a PHP process indefinitely.
  4. Send the token in the appropriate HTTP header specified by the provider and never write it to logs.
composer require symfony/http-client
Official Symfony HTTP client documentation

2. Create one class for the remote API

  1. Name the client after the service, for example CatalogApiClient. Its public methods should describe operations, not HTTP details.
  2. Build the request inside the client. Escape URL parameters and pass query parameters through the query option instead of concatenating strings.
  3. Read the HTTP status code first. A 404 may mean a missing result, while 401, 429, and 5xx responses require different failures.
  4. Map the JSON response to your own DTO and validate required fields and data types. Do not let a foreign associative array spread through the application.
$http = HttpClient::createForBaseUri($_ENV['CATALOG_API_URL'], [
    'auth_bearer' => $_ENV['CATALOG_API_TOKEN'],
    'headers' => ['Accept' => 'application/json'],
    'timeout' => 5,
    'max_duration' => 10,
]);

$response = $http->request('GET', '/v1/products/42');
$status = $response->getStatusCode();
$data = $response->toArray(false);
Official documentation for making requests

3. Design errors, retries, and logging

  1. Translate a network error, timeout, and invalid response into your own exceptions. Callers then do not need to know a particular library’s exceptions.
  2. Use a retry only for transient failures, with a limited number of attempts and increasing delays. Respect Retry-After for a 429 response.
  3. Do not automatically retry write requests unless the service guarantees idempotency or supports an idempotency key. Otherwise, you may create a duplicate payment or order.
  4. Log the operation, duration, HTTP status, and a safe correlation ID. Leave out bodies with personal data and the authorization header.
Official documentation for retrying requests

Step 4

Verify the client against the contract

A successful response test is not enough. The client must anticipate a slow, failed, and incomplete response.

  1. Run a harmless smoke test

    Call one read endpoint in the test environment. Check the resulting DTO, HTTP status, and that the log does not contain the token.

    php scripts/catalog-api-smoke-test.php
  2. Replace the transport with a test implementation

    Return prepared 200, 404, 429, and 500 responses in a unit test. Verify the exact request and the client’s public behaviour without a real network.

    vendor/bin/phpunit tests/Integration/CatalogApiClientTest.php
  3. Simulate a malformed response

    Return invalid JSON or omit a required field. The client should end with a controlled exception, not a missing array key warning.

If something goes wrong

Common problems

The client waits for too long

Set both a timeout and a maximum request duration, and measure elapsed time. A higher timeout usually only delays the same failure.

An error response looks like normal data

Decide explicitly from the HTTP status before mapping the body. Return a result or your own domain exception for each expected status.

A token appears in a log

Redact Authorization and other secret headers in the central logger. Do not log entire request or response objects.

A changed foreign field breaks several parts of the application

Map the response to your own DTO inside the client. You can then fix and test a renamed field in one place.

Done

Your API client has a firm boundary.

You now call the API through one testable layer. Add operations one at a time and define the request, response, and failure behaviour for each one.

Request a call

I will call you on the next working day between 9:00 and 17:00.

You can also call me directly.

+420 605 181 728

Leave your phone number and send a callback request.

By sending, you agree to processing your data in order to handle your request.