Usage
This page takes you from creating the client all the way to writing data, covering reading and error handling along the way. All examples use ESM (import): the @shopimind/sdk-js SDK is ESM only and requires Node.js 18+.
Creating a client
The entry point is SpmClient.getClient(apiVersion, apiKey). It returns a ready-to-use HTTP instance, authenticated with the shop API key (spm-api-key header).
import { SpmClient, SpmCustomers } from '@shopimind/sdk-js';
// Client for the v1 API, authenticated with the shop API key (spm-api-key header)
const client = SpmClient.getClient('v1', process.env.SHOPIMIND_API_KEY!);The default base URL is https://core.shopimind.com/v1. The version ('v1') is appended automatically to the end of the base URL.
All the options
The third argument is an SpmClientOptions object — every key is optional.
const client = SpmClient.getClient('v1', 'your-api-key', {
baseUrl: 'https://core-staging.shopimind.com',
headers: { 'x-trace-id': 'abc-123' },
timeout: 60000,
labelSource: 'web',
retry: { maxRetries: 3 },
});| Option | Type | Default | Role |
|---|---|---|---|
baseUrl | string | https://core.shopimind.com | API base URL |
headers | Record<string, string> | {} | Additional headers |
timeout | number | 30000 | Request timeout (ms) |
labelSource | string | null | 'web' | Injected as label_source on sync POST/PUT requests. null disables it. |
retry | SpmRetryOptions | false | see Resilience | Retry policy |
The base URL can also come from the SHOPIMIND_CORE_API_BASE environment variable. Priority: options.baseUrl > SHOPIMIND_CORE_API_BASE > default. Trailing slashes on the base are stripped before the version segment is appended.
The authentication header
The API key is always sent in the spm-api-key header (lowercase). The SDK does not use any X-… variant. See Scope & versions for the key's scope.
labelSource — where your data comes from
When labelSource is not null, the SDK injects label_source: <value> into the body of POST and PUT requests (on an object, or on each element of an array). This lets ShopiMind know where the synced data originated.
label_source is only added to the writes (POST/PUT) of the entities you sync into ShopiMind:
- Customers:
SpmCustomers,SpmCustomersAddresses,SpmCustomersGroups - Products:
SpmProducts,SpmProductsCategories,SpmProductsImages,SpmProductsManufacturers,SpmProductsVariations - Orders:
SpmOrders,SpmOrdersCarriers,SpmOrdersStatuses - Vouchers:
SpmVouchers - Newsletter subscribers:
SpmNewsletterSubscribers
Configuration and read endpoints never receive it (data sources, custom data, events, lists, contacts, carts, shop connection, stats). Pass labelSource: null to disable it entirely.
The response envelope
Every method returns the same object, on both success and failure. The SDK never throws on an HTTP error: 4xx/5xx codes and network errors are encoded in the envelope.
{
ok: true | false, // true if HTTP 2xx
statusCode: 200 | 4xx | 5xx | 0, // 0 on network failure (no HTTP response)
data: {/* body returned by the API */} | null,
error: null | {
message: string,
code: string, // 'HTTP_503', 'ETIMEDOUT', 'PARTIAL_FAILURE', …
retryable: boolean,
attempts: number,
details?: unknown,
},
}The handling pattern is always the same: you test res.ok, never a try/catch.
const res = await SpmCustomers.list(client, { limit: 50 });
if (!res.ok) {
throw new Error(res.error.message); // nothing is thrown by the SDK: you test res.ok
}
console.log(res.data.data); // ⚠️ business payload double-nested: res.data.dataThe business payload is at res.data.data
The ShopiMind API itself wraps its responses as { statusCode, data }. The SDK places this full body in envelope.data. The business payload is therefore at res.data.data — this is intentional, do not confuse it with res.data.
About SpmClientException
The SDK exports an SpmClientException class, thrown on configuration errors (for example an invalid baseUrl passed to getClient), but never on an HTTP failure (which goes into the envelope).
Reading data
Read methods are static and take the client as the first argument. The query is serialized into a query string.
// Paginated list
const page = await SpmCustomers.list(client, { limit: 50, offset: 0 });
// A single item by identifier (+ optional field projection)
const customer = await SpmCustomers.get(client, 'cust_1', ['email', 'first_name']);
const order = await SpmOrders.get(client, 'ord_123');
const byRef = await SpmOrders.getByReference(client, 'CMD-2024-0001');
// Sub-resources of a parent
const addresses = await SpmCustomersAddresses.list(client, 'cust_1', { limit: 20 });
const groups = await SpmCustomers.listGroups(client, 'cust_1');Writing data
Static — bulk operations (recommended for syncing)
To push large volumes, use the bulk methods. The { chunk: true } option automatically splits an array into successive requests (see Resilience for the batch sizes per resource).
// Bulk write with automatic chunking
const saved = await SpmCustomers.bulkSave(client, customers, { chunk: true });
await SpmCustomers.bulkUpdate(client, customers, { chunk: true });
await SpmCustomers.delete(client, 'cust_1');
await SpmCustomers.bulkDelete(client, ['cust_1', 'cust_2']);bulkDelete is a POST
bulkDelete sends a POST to …/bulk-delete (not a DELETE request).
Instance — one item at a time
Each resource also exposes an "instance" form. You instantiate the object with the client, fill in the fields, then call .save() or .update().
import { SpmCustomers } from '@shopimind/sdk-js';
const customer = new SpmCustomers(client);
customer.customer_id = 'cust_1';
customer.email = 'foo@bar.com';
customer.first_name = 'Foo';
customer.last_name = 'Bar';
customer.lang = 'fr';
customer.is_active = true;
customer.created_at = '2026-01-01T00:00:00Z';
customer.updated_at = '2026-01-01T00:00:00Z';
const res = await customer.save(); // or .update()Shape sent by .save() / .update()
.save() (POST) and .update() (PUT) send the object wrapped in an array[data]. In addition, .update() compacts the payload: fields equal to null, undefined, '', 0, false or [] are silently omitted. If you need to update a field to 0, false or '', use bulkUpdate instead.
Nested resources (addresses, images, variations)
For the sub-resources of a parent, the constructor takes the parent, and the static methods take the parent's identifier as the 2nd argument:
import { SpmCustomersAddresses } from '@shopimind/sdk-js';
await SpmCustomersAddresses.bulkSave(client, 'cust_1', addresses, { chunk: true });
await SpmCustomersAddresses.bulkDelete(client, 'cust_1', ['42', '43']);Flat bulkSaveAll endpoint
Some nested resources expose a bulkSaveAll that accepts a flat payload, with the parent embedded in each element (multi-parent batch):
await SpmCustomersAddresses.bulkSaveAll(client, [
{ customer_id: 'cust_1', address_id: 1, /* … */ },
{ customer_id: 'cust_2', address_id: 2, /* … */ },
], { chunk: true });Associating a data source (id_data_source / source_label)
Syncable entities optionally accept a data source identifier or label. This is useful to distinguish multiple origins (for example a point of sale in addition to the e-commerce site).
customer.id_data_source = 3;
customer.source_label = 'Magasin Paris';
await customer.save();Building an integration?
Within an integration built with the kit, you do not create the client yourself: the kit builds it for you and exposes it as ctx.spm (an SpmHttpClient, the direct SDK client). See the Sync your data guide.
Registering a shop
SpmShopConnection.saveConfiguration sends the shop configuration (POST shop/connection).
import { SpmClient, SpmShopConnection } from '@shopimind/sdk-js';
const client = SpmClient.getClient('v1', process.env.SHOPIMIND_API_KEY!);
await SpmShopConnection.saveConfiguration(client, {
default_currency: 'EUR',
default_lang: 'fr',
langs: ['fr', 'en'],
timezone: 'Europe/Paris',
url_client: 'https://maboutique.example.com',
ecommerce_version: '2.7.0',
module_version: '5.0.0',
});What's next
- Resilience (retries, batch chunking, helpers) — how chunking and retries work.
- Resources — the full table of resources, methods and endpoints.
- TypeScript types — the exact DTOs to type your payloads.