Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Tests

on:
push:
branches: [main]
pull_request:

jobs:
test:
name: PHP ${{ matrix.php }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ["8.1", "8.2", "8.3"]
steps:
- uses: actions/checkout@v4

- name: Setup PHP ${{ matrix.php }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: curl, json
coverage: none

- name: Validate composer.json
run: composer validate --strict

- name: Install dependencies
run: composer update --no-interaction --prefer-dist --no-progress

- name: Run unit tests
run: vendor/bin/phpunit

# Optional live smoke test. IMPORTANT: do NOT gate this step with
# `if: ${{ secrets.* }}` - the secrets context is unavailable in
# step-level `if` expressions and invalidates the whole workflow file.
# Guard inside the shell instead so it skips gracefully when the
# secret is not configured.
- name: Live smoke test (skips when no secret)
env:
OILPRICEAPI_TEST_KEY: ${{ secrets.OILPRICEAPI_TEST_KEY }}
run: |
if [ -z "$OILPRICEAPI_TEST_KEY" ]; then
echo "OILPRICEAPI_TEST_KEY not configured - skipping live smoke test."
exit 0
fi
OILPRICEAPI_KEY="$OILPRICEAPI_TEST_KEY" php examples/smoke.php
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/vendor/
composer.lock
.phpunit.result.cache
.phpunit.cache/
.DS_Store
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Changelog

## 2.0.0 (2026-07-03)

Ground-up rewrite of the PHP SDK.

### Added

- `OilPriceAPI\Client` with `latest()`, `pastDay()`, `pastWeek()`, `pastMonth()`, `pastYear()`, `demoPrices()`, and the `raw()` escape hatch for any endpoint.
- Keyless demo mode via `/v1/demo/prices`; helpful `AuthenticationException` (with signup URL) when keyed endpoints are called without a key.
- `OILPRICEAPI_KEY` environment variable fallback.
- Immutable `Price` DTO (`code`, `price`, `currency`, `updatedAt` as `DateTimeImmutable`, `change24h`, plus `name`/`unit`/`type`/`formatted`) with `toArray()`.
- Automatic retries with exponential backoff + jitter on 429/5xx, honoring `Retry-After`.
- Typed exceptions: `ApiException`, `AuthenticationException`, `RateLimitException`, `TransportException`.
- Zero runtime dependencies (`ext-curl` + `ext-json` only); PHP >= 8.1; strict types throughout.
- `HttpTransport` interface for dependency-free testing and custom HTTP stacks.
- PHPUnit suite (offline, mocked transport) and GitHub Actions CI across PHP 8.1/8.2/8.3.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 OilPriceAPI (Metiri Labs LLC)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
169 changes: 167 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,168 @@
# oilpriceapi-php
# OilPriceAPI - PHP SDK

Official PHP SDK for [OilPriceAPI](https://oilpriceapi.com).
> **Real-time oil, gas, LNG, carbon and fuel prices for PHP** — one class, zero dependencies, works everywhere PHP does (including shared hosting and WordPress).

[![Tests](https://github.com/OilpriceAPI/oilpriceapi-php/actions/workflows/test.yml/badge.svg)](https://github.com/OilpriceAPI/oilpriceapi-php/actions/workflows/test.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

**[Get a Free API Key](https://oilpriceapi.com/auth/signup?utm_source=php-sdk)** | **[Documentation](https://docs.oilpriceapi.com)** | **[Pricing](https://oilpriceapi.com/pricing?utm_source=php-sdk-limit)**

The official PHP SDK for [OilPriceAPI](https://oilpriceapi.com) — real-time and historical prices for Brent, WTI, Natural Gas, Diesel, EU Carbon (ETS), TTF Gas and 100+ commodities.

- **Zero dependencies** — only `ext-curl` and `ext-json` (bundled with virtually every PHP install). No Guzzle, no framework, no conflicts with your host's packages.
- **PHP 8.1+**, strict types, immutable `Price` DTOs.
- **Resilient** — automatic retries with exponential backoff + jitter on 429/5xx, honors `Retry-After`.
- **Typed errors** — `AuthenticationException`, `RateLimitException`, `ApiException`.
- **Demo mode** — try it without an API key.
- **Escape hatch** — `$client->raw()->get(...)` reaches any endpoint, present or future.

## Install

```bash
composer require oilpriceapi/oilpriceapi
```

## Quick start

```php
use OilPriceAPI\Client;

$client = new Client('your_api_key'); // or set OILPRICEAPI_KEY env var
$brent = $client->latest('BRENT_CRUDE_USD');
echo $brent->price; // e.g. XX.XX (USD per barrel)
```

`latest()` without a code returns every commodity on your plan as a list of `Price` objects.

## No composer? Plain PHP

No SDK, no packages — this is the whole integration with nothing but PHP's built-in cURL:

```php
<?php
// Latest Brent price from OilPriceAPI - plain PHP, no libraries needed.
$apiKey = getenv('OILPRICEAPI_KEY') ?: 'your_api_key_here';

$ch = curl_init('https://api.oilpriceapi.com/v1/prices/latest?by_code=BRENT_CRUDE_USD');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Authorization: Token ' . $apiKey,
'Accept: application/json',
],
]);
$response = curl_exec($ch);

$json = json_decode($response, true);
echo $json['data']['price'] ?? 'No price returned'; // e.g. XX.XX
```

## Try it without an API key (demo mode)

The client works out of the box — no signup required — via the demo endpoint (rate limited per IP, free-tier commodities only):

```php
$client = new \OilPriceAPI\Client(); // no key

foreach ($client->demoPrices() as $price) {
printf("%s: %s %.2f\n", $price->code, $price->currency, $price->price);
}
```

Calling a keyed endpoint without a key throws an `AuthenticationException` that tells you exactly where to [get a free key](https://oilpriceapi.com/auth/signup?utm_source=php-sdk).

## Historical prices

```php
$day = $client->pastDay('BRENT_CRUDE_USD'); // last 24 hours
$week = $client->pastWeek('BRENT_CRUDE_USD');
$month = $client->pastMonth('BRENT_CRUDE_USD');
$year = $client->pastYear('BRENT_CRUDE_USD');

foreach ($week as $price) {
echo $price->updatedAt?->format('Y-m-d H:i'), ' -> ', $price->price, PHP_EOL;
}
```

Each method returns a `list<OilPriceAPI\Price>` — an immutable DTO with `code`, `price` (float), `currency`, `updatedAt` (`DateTimeImmutable|null`), `change24h` (`float|null`), plus `name`, `unit`, `type`, `formatted` where the API provides them, and a `toArray()` helper.

## Beyond oil — gas, LNG, carbon & fuels

OilPriceAPI is not just crude. The same client covers the energy complex that maritime compliance, fleet & logistics, LNG analytics and CBAM reporting teams need:

```php
// EU ETS carbon allowances (EUR/tonne) - CBAM & maritime compliance
$eua = $client->latest('EU_CARBON_EUR');
echo $eua->price; // e.g. XX.XX EUR/tonne

// Diesel - fleet & logistics fuel-surcharge calculations
$diesel = $client->latest('DIESEL_USD');

// Dutch TTF natural gas futures curve - LNG & gas analytics
$ttf = $client->raw()->get('/v1/futures/ttf-gas/curve');

// ICE Brent futures curve via the same escape hatch
$curve = $client->raw()->get('/v1/futures/ice-brent/curve');
```

> Futures endpoints require a plan with futures access — see [pricing](https://oilpriceapi.com/pricing?utm_source=php-sdk-limit).

## Any endpoint: the `raw()` escape hatch

New endpoints ship in the API before they ship in the SDK. `raw()` gives you the full decoded JSON envelope for any path:

```php
$response = $client->raw()->get('/v1/futures/ice-brent/curve', ['unit' => 'usd']);
// ['status' => 'success', 'data' => [...]]
```

## Error handling

```php
use OilPriceAPI\Exception\ApiException;
use OilPriceAPI\Exception\AuthenticationException;
use OilPriceAPI\Exception\RateLimitException;

try {
$price = $client->latest('BRENT_CRUDE_USD');
} catch (AuthenticationException $e) {
// 401 or missing key - message includes the signup URL
} catch (RateLimitException $e) {
// 429 after retries - $e->retryAfter (seconds), $e->limit,
// message includes the upgrade URL
} catch (ApiException $e) {
// everything else - $e->statusCode, $e->responseBody
}
```

All exceptions extend `ApiException`, so a single `catch` handles everything.

## Retries & timeouts

Requests that hit `429` or `5xx` are retried automatically (default: 3 retries) with exponential backoff plus jitter. If the API sends a `Retry-After` header, it is honored exactly. Everything is configurable:

```php
$client = new \OilPriceAPI\Client(
apiKey: 'your_api_key',
timeout: 10.0, // seconds per request (default 10)
maxRetries: 3, // retries on 429/5xx (default 3)
);
```

## WordPress

The SDK has no dependencies to collide with other plugins, so it drops straight into themes and plugins — `composer require` it, or copy `src/` and load it with any PSR-4 autoloader. Prefer no code at all? Use the official [OilPriceAPI WordPress plugin](https://github.com/OilpriceAPI/oilpriceapi-wordpress-plugin) for ready-made price widgets and shortcodes.

## Testing

```bash
composer install
composer test
```

The test suite is fully offline — HTTP is mocked through the `OilPriceAPI\Http\HttpTransport` interface, which you can also implement to route the SDK through your own HTTP stack.

## License

MIT — see [LICENSE](LICENSE).
58 changes: 58 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"name": "oilpriceapi/oilpriceapi",
"description": "Official PHP SDK for OilPriceAPI - real-time and historical oil, gas, LNG, carbon and fuel prices. Zero dependencies, works on shared hosting and WordPress.",
"type": "library",
"license": "MIT",
"keywords": [
"oil",
"oil-price",
"brent",
"wti",
"commodities",
"natural-gas",
"lng",
"carbon",
"energy",
"api",
"sdk",
"prices"
],
"homepage": "https://oilpriceapi.com",
"authors": [
{
"name": "OilPriceAPI",
"email": "support@oilpriceapi.com",
"homepage": "https://oilpriceapi.com"
}
],
"support": {
"email": "support@oilpriceapi.com",
"docs": "https://docs.oilpriceapi.com",
"issues": "https://github.com/OilpriceAPI/oilpriceapi-php/issues"
},
"require": {
"php": ">=8.1",
"ext-curl": "*",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^10.5 || ^11.0 || ^12.0"
},
"autoload": {
"psr-4": {
"OilPriceAPI\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"OilPriceAPI\\Tests\\": "tests/"
}
},
"scripts": {
"test": "phpunit"
},
"config": {
"sort-packages": true
},
"minimum-stability": "stable"
}
27 changes: 27 additions & 0 deletions examples/quickstart.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

require __DIR__ . '/../vendor/autoload.php';

use OilPriceAPI\Client;

// Reads the OILPRICEAPI_KEY environment variable automatically.
// Get a free key: https://oilpriceapi.com/auth/signup?utm_source=php-sdk
$client = new Client();

if ($client->hasApiKey()) {
$brent = $client->latest('BRENT_CRUDE_USD');
printf(
"Brent: %s %.2f (as of %s)\n",
$brent->currency,
$brent->price,
$brent->updatedAt?->format(DATE_ATOM) ?? 'n/a',
);
} else {
// No key? Demo mode still works (rate limited per IP).
echo "No API key set - showing demo prices instead.\n";
foreach ($client->demoPrices() as $price) {
printf("%-20s %s %.2f\n", $price->code, $price->currency, $price->price);
}
}
35 changes: 35 additions & 0 deletions examples/smoke.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

/**
* Live smoke test used by CI when OILPRICEAPI_TEST_KEY is configured.
* Exits non-zero on failure.
*/

require __DIR__ . '/../vendor/autoload.php';

use OilPriceAPI\Client;

$client = new Client();

if (!$client->hasApiKey()) {
fwrite(STDERR, "smoke: no API key available\n");
exit(1);
}

$brent = $client->latest('BRENT_CRUDE_USD');

if ($brent->code !== 'BRENT_CRUDE_USD' || $brent->price <= 0.0) {
fwrite(STDERR, "smoke: unexpected latest price payload\n");
exit(1);
}

$week = $client->pastWeek('BRENT_CRUDE_USD');

if ($week === []) {
fwrite(STDERR, "smoke: past_week returned no prices\n");
exit(1);
}

echo "smoke: OK (latest + past_week)\n";
Loading
Loading