AdminClient
Your ERP, warehouse, CRM or app on a shop's products, stock, orders, customers, marketing, reviews, quotations, keys and webhooks, through the Admin API, from your PHP server.
Get started
-
Make a key. In the Store Hugs panel, open Shop Settings → Integration API and add an Admin key (
sa_live_…). Choose its scopes and, optionally, the IP addresses your servers call from. The key is shown once; keep it in your server's secrets. -
Install the SDK:
composer require storehugs/integration -
Make a client and a first call:
use StoreHugs\Integration\AdminClient; $admin = new AdminClient(getenv('STOREHUGS_ADMIN_KEY')); $shop = $admin->shop(); echo $shop['name'], ' ', $shop['plan'], ' ', $shop['currency']; -
Sync. Read every record changed since your last run, oldest change first, and keep the last
updatedAtyou saw:foreach ($admin->products->all(['updatedSince' => $lastSyncedAt]) as $product) { $erp->save($product); $lastSyncedAt = $product['updatedAt'] ?? $lastSyncedAt; } -
Write safely. Every write is retried with the same
Idempotency-Key(see retries). Pass theupdatedAtyou read with a change, so a newer edit is never overwritten (409edit_conflict):$product = $admin->products->get(12); $admin->products->update(12, ['price' => 19.5], unmodifiedSince: $product['updatedAt']);
The key names the shop; there's no shop id to pass.
Constructor
| Parameter | Type | |
|---|---|---|
apiKey |
string |
Required. An Admin key, made in the panel or issued to your app. |
baseUrl |
string |
The panel's origin. Default https://my.storehugs.com (AdminClient::DEFAULT_BASE_URL). |
http |
HttpOptions |
Timeouts, retries and your own HTTP client (see HTTP options). |
Scopes
Each method needs the scope it names. A key without it gets 403 insufficient_scope.
| Scope | Allows |
|---|---|
shop:read |
The shop's details. |
keys:read, keys:write |
Storefront keys. |
products:read, products:write |
Products. |
inventory:write |
Stock levels. |
orders:read, orders:write |
Orders, their status and shipments. |
customers:read, customers:write |
Customers and wholesale memberships. |
marketing:read, marketing:write |
Coupons and offers. |
reviews:read, reviews:write |
Reviews and the shop's replies. |
quotations:read, quotations:write |
B2B quotations. |
webhooks:read, webhooks:write |
Webhook endpoints. |
Lists and sync
Every resource with a list has two methods:
list($query = [])returns one cursor page:data, andmeta['nextCursor']for the next page (nullon the last).all($query = [])is a generator over every record, fetching the next page as you read.
They take these query fields, and the resource's filters below:
| Query | Type | |
|---|---|---|
limit |
int |
Records per page, up to 100. |
cursor |
string |
The page to read, from meta['nextCursor']. |
updatedSince |
string |
ISO 8601. Only records changed since then, oldest change first. |
$query = ['status' => 'processing', 'limit' => 50];
do {
$page = $admin->orders->list($query);
// … $page['data']
$query['cursor'] = $page['meta']['nextCursor'];
} while ($query['cursor'] !== null);
Changes and $unmodifiedSince
Methods that change a record (update, setMembership) take $unmodifiedSince: the record's updatedAt as you read it, as a string or a DateTimeInterface. If the record has changed since, the change is refused with 409 edit_conflict.
Every write takes $idempotencyKey last (see retries). Use named arguments to pass either:
$admin->coupons->update(5, ['value' => 15], unmodifiedSince: $coupon['updatedAt'], idempotencyKey: 'coupon-5-v2');
Models and methods
Each page describes every method in full: its parameters, the fields it sends and gets back, its refusals and an example.