Motivation
Almost every modern project requires third-party API integration — weather forecasts, stock data, and countless other use cases. While some APIs offer SDKs, many don't.
Quick access using ZTTP looks like:
$symbols = \Zttp\Zttp::get("https://api.iextrading.com/1.0/ref-data/symbols")->json();
As complexity grows, you need abstraction for errors, validation, and transformation. The goal: access APIs the way you access a database:
$user = User::where('email', $email)->first();
What if APIs worked the same way?
$symbol = IEX::Symbol()->where('symbol', $apiSymbol)->first();
Solution
The approach introduces an API → Endpoint → Shape pattern:
- ApiConsumer — represents one API (e.g., JSONPlaceholder)
- Endpoint — represents one resource (e.g.,
/posts), with chainable query methods - Shape — represents the data structure returned
Endpoints mirror Eloquent's query builder. Shapes mirror models. All results return as collections, enabling methods like filter() when the API itself doesn't support filtering:
$users = User::all();
$users = $users->filter(function ($value, $key) {
return $value->age >= 21;
});
Shapes also support mutator-style custom methods (e.g., combining first_name + last_name), structural validation, and field normalization via $transformations.
Relations between endpoints are supported via hasOne() and hasMany():
$user = JSONPlaceholder::Post()->first()->user();
Results
The package black-bits/laravel-api-consumer provides:
- Artisan generators for ApiConsumers, Endpoints, and Shapes
- Chainable collection methods on Endpoints
- Relation support between resources
- Built-in caching out of the box
Show Case
Two APIs were implemented: IEX Trading and JSONPlaceholder.

ApiConsumers Setup
composer require black-bits/laravel-api-consumer
php artisan vendor:publish --provider="BlackBits\ApiConsumer\ApiConsumerServiceProvider"
php artisan make:api-consumer IEX
php artisan make:api-consumer-endpoint Symbol -c IEX
Add base URL to /config/api-consumers.php:
return [
'IEX' => [
'apiBasePath' => 'https://api.iextrading.com/1.0',
],
];
ApiConsumer class (/app/ApiConsumers/IEX/IEX.php):
namespace App\ApiConsumers\IEX;
use BlackBits\ApiConsumer\ApiConsumer;
class IEX extends ApiConsumer
{
protected function getEndpoint()
{
return config('api-consumers.IEX.apiBasePath');
}
}
Endpoint class with caching (SymbolEndpoint.php):
class SymbolEndpoint extends Endpoint
{
protected $path = '/ref-data/symbols';
protected $shouldCache = true;
protected $cacheDurationInMinutes = 10;
public function all()
{
return $this->get();
}
}
Shape class with transformations and relations (SymbolShape.php):
class SymbolShape extends BaseShape
{
protected $return_shape_data_only = true;
protected $require_shape_structure = true;
protected $transformations = [
'symbol' => 'symbol',
'name' => 'name',
'date' => 'date',
'isEnabled' => 'is_enabled',
'type' => 'type',
'iexId' => 'iex_id'
];
protected $fields = ['symbol', 'name', 'date', 'is_enabled', 'type', 'iex_id'];
public function company()
{
return $this->hasOne(CompanyEndpoint::class, 'symbol');
}
}
Usage
$symbols = IEX::Symbol()->all();
// With relation
$company = IEX::Symbol()->first()->company();
Controller example with filtering:
public function list(Request $request)
{
$symbols = IEX::Symbol();
if (!empty($request->get('symbol'))) {
$symbol = $request->get('symbol');
$symbols->filter(function ($value, $key) use ($symbol) {
return str_contains($value->symbol, $symbol);
});
}
if (!empty($request->get('name'))) {
$symbols->filterName($request->get('name'));
}
$data = ['symbols' => $symbols->take(10)->get()];
return response()->view('symbols.list', $data);
}
JSONPlaceholder example — fetch a post with its comments:

public function listComments($post)
{
$post = JSONPlaceholder::Post()->where('id', $post)->first();
$data = [
'comments' => $post->comments(),
'post' => $post
];
return response()->view('json-placeholder.comments-list', $data);
}
Next Steps
- Authentication support (next priority)
- Additional HTTP methods (POST, PUT, etc.)
- Improved relation functionality
- Testing
- Long-term: auto-generation for standardized endpoints (JSON API / Swagger)
Conclusion
The package aims to fill a gap in the Laravel ecosystem by offering a standardized, Eloquent-style interface for third-party API consumption. Open questions for feedback:
- Thoughts on the Consumer → Endpoint → Shape structure?
- Missing features?
- Which APIs cause the most implementation friction?

