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. Use it on your server only.
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:
npm install @storehugs/integration -
Make a client and a first call:
import { AdminClient } from '@storehugs/integration'; const admin = new AdminClient({ apiKey: process.env.STOREHUGS_ADMIN_KEY! }); const shop = await admin.shop(); console.log(shop.name, shop.plan, shop.currency); -
Sync. Read every record changed since your last run, oldest change first, and keep the last
updatedAtyou saw:for await (const product of admin.products.all({ updatedSince: lastSyncedAt, })) { await saveToErp(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):const product = await admin.products.get(12); await admin.products.update( 12, { price: 19.5 }, { unmodifiedSince: product.updatedAt ?? undefined }, );
The key names the shop; there's no shop id to pass. A request from a browser (with an Origin header) is refused.
Options
| Option | 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 (ADMIN_API_URL). |
fetch |
FetchLike |
Your own fetch (see custom fetch). |
maxRetries |
number |
Retries after 429, 503 and network errors. Default 2. |
inProgressTimeoutMs |
number |
How long to wait while a retried write still runs. Default 30000. |
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?)resolves to oneCursorPage<T>:data, andmeta.nextCursorfor the next page (nullon the last).all(query?)is an async iterator over every record, fetching the next page as you read.
They take these query fields, and the resource's filters below:
| Query | Type | |
|---|---|---|
limit |
number |
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. |
const page = await admin.orders.list({ status: 'processing', limit: 50 });
const next = page.meta.nextCursor
? await admin.orders.list({
status: 'processing',
limit: 50,
cursor: page.meta.nextCursor,
})
: null;
Changes with UpdateOptions
Methods that change a record (update, setMembership) take UpdateOptions last:
| Option | Type | |
|---|---|---|
unmodifiedSince |
string |
The record's updatedAt as you read it. If it has changed since, the change is refused with 409 edit_conflict. |
idempotencyKey |
string |
Your own Idempotency-Key. |
Other writes take RequestOptions (idempotencyKey, signal, headers).
Models and methods
Each page describes every method in full: its parameters, the fields it sends and gets back, its refusals and an example.