bigcommerce/api
A PHP client for the BigCommerce API with OAuth authentication and ready-made resources for catalog, orders, customers, carts, and store data. Simplifies building integrations and apps against BigCommerce REST endpoints.
The package is a low-level SDK: it gives you authenticated Guzzle-backed clients for BigCommerce's REST APIs, and you write the Laravel glue yourself. There is no service provider, facade, or config file shipped with it.
Install and collect credentials
composer require bigcommerce/api:^3.3.12
Pin at least 3.3.12 — that release widens the firebase/php-jwt constraint to ^7.0 so you can stay on a patched JWT library (see Gotchas).
You need three values from the BigCommerce control panel (API accounts) or from your OAuth app install flow:
store_hash — appears in the API URLclient_id — sent as X-Auth-Clientaccess_token — sent as X-Auth-Token// config/services.php
'bigcommerce' => [
'store_hash' => env('BC_STORE_HASH'),
'client_id' => env('BC_CLIENT_ID'),
'access_token' => env('BC_AUTH_TOKEN'),
'client_secret'=> env('BC_CLIENT_SECRET'), // only needed for webhooks/OAuth
],
Bind the SDK once
The v3 client is built around Bigcommerce\Api\v3\Configuration (host + credentials) and generated API classes such as CatalogApi, OrdersApi, CustomersApi, CartsApi, WebhooksApi, ScriptsApi. Register the configuration as a singleton and bind the API classes you use:
// app/Providers/BigCommerceServiceProvider.php
use Bigcommerce\Api\v3\Api\CatalogApi;
use Bigcommerce\Api\v3\Configuration;
use GuzzleHttp\Client;
public function register(): void
{
$this->app->singleton(Configuration::class, function () {
return (new Configuration())
->setHost('https://api.bigcommerce.com/stores/'.config('services.bigcommerce.store_hash'))
->setClientId(config('services.bigcommerce.client_id'))
->setAccessToken(config('services.bigcommerce.access_token'));
});
$this->app->bind(CatalogApi::class, fn ($app) => new CatalogApi(
new Client(['timeout' => 15, 'connect_timeout' => 5]),
$app->make(Configuration::class)
));
}
Two version-sensitive details: the constructor order (new CatalogApi($guzzle, $config) in most releases) and whether the store hash is baked into setHost() or passed as the first path argument to each method. Open src/Bigcommerce/Api/v3/Api/CatalogApi.php after install and confirm both before writing code.
First use case: read the catalog
use Bigcommerce\Api\v3\Api\CatalogApi;
$response = app(CatalogApi::class)->getProducts([
'limit' => 50,
'page' => 1,
'include' => 'variants,images',
]);
foreach ($response->getData() as $product) {
logger()->info($product->getId().' — '.$product->getName());
}
Where to look first: src/Bigcommerce/Api/v3/Api/ for the endpoint methods, src/Bigcommerce/Api/v3/Model/ for payload shapes, and the BigCommerce API reference for the same endpoint (method names map closely to operation IDs). The legacy Bigcommerce\Api\Client static class in src/Bigcommerce/Api/Client.php is the v2-era interface — avoid it in new Laravel code.
Keep the SDK behind your own services. Controllers and jobs should call CatalogService, OrderService, CheckoutService, not CatalogApi directly. This is the only practical way to fake BigCommerce in tests and to swap in raw HTTP calls for endpoints the generated client doesn't cover yet.
class CatalogService
{
public function __construct(private CatalogApi $api) {}
public function allProducts(int $limit = 250): Generator
{
$page = 1;
do {
$response = $this->api->getProducts(['page' => $page, 'limit' => $limit]);
yield from $response->getData();
$pagination = $response->getMeta()?->getPagination();
$page++;
} while ($pagination && $page <= $pagination->getTotalPages());
}
}
Push writes and syncs onto queues. Product upserts, order imports, and theme/script updates are slow and rate-limited. Jobs give you retries and isolation:
class SyncProductToBigCommerce implements ShouldQueue
{
use InteractsWithQueue, Queueable;
public function handle(CatalogService $catalog): void
{
$catalog->upsert($this->productId);
}
}
Use webhooks for inbound changes rather than polling. Register once:
$webhooks->createWebhook([
'scope' => 'store/product/updated',
'destination' => route('bc.webhooks.product'),
'is_active' => true,
]);
Then verify every delivery against the raw body — parsing first will break the signature:
public function __invoke(Request $request)
{
$expected = hash_hmac('sha256', $request->getContent(), config('services.bigcommerce.client_secret'));
abort_unless(hash_equals($expected, $request->header('X-Bc-Webhook-Signature', '')), 401);
ProcessWebhook::dispatch($request->all()); // respond 200 immediately
return response()->noContent();
}
Add Guzzle middleware for rate limiting and retries. BigCommerce returns 429 plus X-Rate-Limit-Requests-Left and X-Rate-Limit-Time-Reset-Ms; retry with backoff instead of letting the job fail:
$stack = HandlerStack::create();
$stack->push(Middleware::retry(
fn ($retries, $req, $res, $e) => $retries < 3 && (
$e instanceof ConnectException ||
($res && in_array($res->getStatusCode(), [429, 500, 502, 503, 504]))
),
fn ($retries) => 1000 * 2 ** $retries
));
$client = new Client(['handler' => $stack, 'timeout' => 15]);
Multi-store setups need a factory, not a singleton. For agencies or multi-storefront apps, build the Configuration per store at call time (BigCommerceClientFactory::forStore($hash)) and never rely on global state.
Cache reads, invalidate on webhook. Catalog endpoints are the most-hit and the least volatile. Cache::remember("bc.product.{$id}", 300, ...) plus invalidation from store/product/updated keeps you well under rate limits.
Fall back to Laravel's HTTP client when the SDK lags. The generated classes don't expose every endpoint. For those:
Http::withToken(config('services.bigcommerce.access_token'))
->withHeaders(['X-Auth-Client' => config('services.bigcommerce.client_id'), 'Accept' => 'application/json'])
->baseUrl('https://api.bigcommerce.com/stores/'.config('services.bigcommerce.store_hash'))
->post('/v3/catalog/products', $payload)
->throw()
->json();
Test with a mocked Guzzle handler. Inject a MockHandler/HandlerStack into the API class, or bind your service interface to a fake. Never let the test suite hit a real store — rate limits are per store, and CI will trip them.
Bigcommerce\Api\Client (static, v2-era, global mutable state) and Bigcommerce\Api\v3\* (instance-based, generated) coexist. In Octane/queue workers, the static client leaks config between stores and requests — use only the v3 classes.composer.json and verify the signature in vendor/ before upgrading.3.3.12 loosened the firebase/php-jwt constraint from ^6.0 to ^7.0 (PHPMNT-347) so consumers can pick up the patched major. If your app uses the SDK's signed-JWT surfaces — customer login tokens via CustomerLoginApi, or any place you generate/verify a JWT — run composer update bigcommerce/api firebase/php-jwt and confirm composer show firebase/php-jwt reports 7.x. If you had pinned the library back to 6.x to satisfy this SDK, that pin can now be lifted. No API surface changed, so no code edits are expected; a composer why firebase/php-jwt is a good first check when a composer update suddenly resolves differently after bumping the SDK.ApiException, not HTTP exceptions. Catch Bigcommerce\Api\v3\ApiException for 4xx/5xx and inspect getCode() and getResponseBody(). 403 usually means a missing OAuth scope, 401 usually means a revoked token — handle them differently (log/alert vs. fail the job).null. Don't chain ->getData() on delete/update calls without a null check.limit=250 on reads, and inspect the rate-limit headers rather than guessing.include everywhere. include=variants,images,options,modifiers multiplies payload size and latency; request only what the current job needs.scope, data.id, hash, and a timestamp — not the full resource. Verify, enqueue, then fetch. Use hash for dedupe and created_at to discard out-of-order deliveries.limit is 50, max is usually 250, and page is 1-based. Always read meta.pagination.total_pages rather than assuming one page.Product, Order), not arrays. For complex payloads it is often faster to POST raw JSON via Laravel's HTTP client than to construct nested models.Carbon::parse(...)->setTimezone(config('app.timezone')) at the boundary, not inside your domain code.vendor/bigcommerce/api/src/Bigcommerce/Api/v3/ — the generated code is the documentation — and cross-reference the official API reference for the same operation.ApiClient/Configuration classes are the seams: swap the Guzzle handler for logging/retries, subclass Configuration for per-store credentials, and wrap API classes in your own interfaces for fakes.How can I help you explore Laravel packages today?