parisco-sdk
v0.3.0
Published
Official Parisco NodeJS SDK — typed client for the Parisco public REST API (/v1): catalog, my products, orders, credit, and webhook verification.
Maintainers
Readme
parisco-sdk
Typed Node.js client for the Parisco public REST API (/v1) — catalog,
imported products, orders, credit, shipping, stores, and webhooks.
Install
npm install parisco-sdkRequires Node.js 18+.
Quick start
import { Parisco } from "parisco-sdk";
const parisco = new Parisco({
baseUrl: process.env.PARISCO_API_ORIGIN!, // e.g. https://api.parisco.io — never guessed
token: process.env.PARISCO_TOKEN!, // prsk_live_… / prsk_test_…
});
const { data: products, nextCursor } = await parisco.catalog.products.list({
limit: 50,
});
const order = await parisco.orders.create(
{
externalOrderRef: cart.id,
paymentMode: "PREPAID",
shipping: { mode: "ON_BEHALF", recipient, address },
lines: cart.items.map((i) => ({ cloneVariantId: i.variantId, qty: i.qty })),
},
{ idempotencyKey: cart.id },
);Client options
| Option | Required | Description |
| ------------ | -------- | --------------------------------------------------------------------------------------------------------- |
| baseUrl | yes | API origin. Never defaulted, so credentials can't be sent to an implicit host. |
| token | yes | Integration token (prsk_live_… / prsk_test_…). |
| maxRetries | no | Automatic retries on 429/5xx for idempotent requests. Default 3, full-jitter backoff, honors Retry-After. |
Resources
catalog — read-only canonical catalog
| Method | Description |
| ------------------------------------------------------------------- | ----------------------------------------- |
| brands.list() | List all brands. |
| products.list({ brand?, q?, tag?, updatedAfter?, cursor?, limit? }) | Paginated product search. |
| products.iter({ ...same params }) | Async-iterate every matching product. |
| products.get(id) | Fetch one product with its variants. |
myProducts — your imported clones
| Method | Description |
| ---------------------------------------------------------------------- | ---------------------------------------------------------------- |
| list({ q?, status?, updatedAfter?, cursor?, limit? }) / iter({ updatedAfter?, ... }) | List/iterate clones; updatedAfter is an incremental change feed that includes archived clones. |
| get(cloneId) | Fetch one clone. |
| import({ productIds }, { idempotencyKey }) | Import canonical products as clones. |
| update(cloneId, { title?, description?, variants? }, { idempotencyKey }) | Edit title, description, per-variant pricing/inclusion. |
| resync(cloneId, { idempotencyKey }) | Pull latest canonical data into the clone. |
| archive(cloneId, { idempotencyKey }) | Archive a clone. |
| restore(cloneId, { idempotencyKey }) | Restore an archived clone. |
| images.list(cloneId) | List clone images. |
| images.add(cloneId, { contentType, data }, { idempotencyKey }) | Upload a custom image (data is a Buffer/Uint8Array or base64 string). |
| images.reorder(cloneId, imageIds, { idempotencyKey }) | Replace the image set/order — omitted ids are removed. |
orders
| Method | Description |
| ------------------------------------------------------------------------------- | -------------------------------------------------------- |
| create(input, { idempotencyKey }) | Create an order — use your own order id as the idempotency key. |
| list({ status?, store?, from?, to?, cursor?, limit? }) / iter({ ... }) | List/iterate orders. |
| get(orderId) | Fetch one order. |
| approve(orderId, { idempotencyKey }) | Approve an order held for manual approval. |
| cancel(orderId, { idempotencyKey }) | Cancel — allowed pre-shipment only. |
credit
| Method | Description |
| --------------------------------------------------------- | ------------------------------------- |
| balance() | Current credit balance. |
| ledger.list({ type?, from?, to?, cursor?, limit? }) | Ledger entries. |
| settlements.list({ cursor?, limit? }) | Settlement history. |
| paydown.create({ amountCents }, { idempotencyKey }) | Start a paydown intent. |
| paydown.status(settlementId) | Check a paydown intent's status. |
| paydown.cancel(settlementId, { idempotencyKey }) | Cancel a paydown intent. |
shipping
| Method | Description |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| track(orderId) | Live courier tracking for one order — a single latest-waybill block plus per-parcel shipments. |
| getProfile() / updateProfile(input, { idempotencyKey }) | Your sender/return identity (used for TO_DROPSHIPPER and label sender). |
| getSettings() / updateSettings(input, { idempotencyKey }) | Default courier, parcel weight, ship-to-me address, per-store defaults. |
| listCredentials() | List registered courier accounts (secrets are never returned). |
| saveCredentials(input, { idempotencyKey }) | Register/replace a courier login — validated against the courier before saving; lands in PENDING_VERIFICATION. |
| deleteCredentials(credentialId) | Remove a courier account. |
stores
| Method | Description |
| ------------------------------------------------------------------------------ | ---------------------------------------------------------------- |
| list() | Connected stores. |
| connectWooCommerce({ siteUrl, ... }, { idempotencyKey }) | Start the WooCommerce plugin connection flow. |
| saveWooCommerceCredentials(storeId, { consumerKey, consumerSecret }, { idempotencyKey }) | Finish the WooCommerce connection. |
| healthCheckWooCommerce(storeId, { ... }, { idempotencyKey }) | Report plugin/WooCommerce/WordPress versions. |
| uninstallWooCommerce(storeId, { idempotencyKey }) | Disconnect a WooCommerce store. |
| push(storeId, { cloneIds }, { idempotencyKey }) | Publish clones to a connected Shopify/WooCommerce store. |
| listMappings(storeId, { status? }) | Listings currently pushed to a store. |
| removeMapping(storeId, mappingId) | Unlist — the external product is set to draft. |
Custom storefronts don't push: they read myProducts.list/.iter directly as
their catalog.
webhooks — manage subscriptions
| Method | Description |
| ----------------------------------------------------------- | -------------------------------------------- |
| subscriptions.list() | List subscriptions. |
| subscriptions.create({ url, events }, { idempotencyKey }) | Subscribe a URL to one or more event types. |
| subscriptions.enable(subscriptionId, { idempotencyKey }) | Re-enable a disabled subscription. |
| subscriptions.delete(subscriptionId) | Delete a subscription. |
Event types: order.shipped, order.status_changed, product.stock_changed,
product.updated, my_product.updated. See Verifying webhooks to receive them.
Pagination
Every list method returns a cursor page; every list resource also has an
iter that walks all pages for you:
const { data, nextCursor } = await parisco.orders.list({ limit: 50 });
for await (const order of parisco.orders.iter({ status: "PENDING" })) {
// ...
}Idempotency
Every mutation takes an idempotencyKey: a retried call with the same key
replays the first outcome instead of applying twice.
await parisco.orders.cancel(orderId, { idempotencyKey: `cancel-${orderId}` });Retries & cancellation
GET requests, and any mutation with an idempotency key, are retried
automatically on 429/5xx (full-jitter backoff, honors Retry-After,
maxRetries default 3). Pass retryDelete: true to opt a DELETE into
retries too. Every method also accepts { signal } for AbortController
cancellation:
const controller = new AbortController();
await parisco.orders.get(orderId, { signal: controller.signal });
controller.abort();Errors
Every non-2xx response throws PariscoError — switch on .code, never
.message:
import { PariscoError } from "parisco-sdk";
try {
await parisco.orders.create(input, { idempotencyKey: cart.id });
} catch (error) {
if (error instanceof PariscoError) {
console.error(error.code, error.status, error.requestId);
}
}Verifying webhooks
Verify the Parisco-Signature header (t=…,v1=hmac_sha256(secret, t + "." +
body)) against the raw body before processing an event. This lives in a
separate entry point, parisco-sdk/webhooks, so verification doesn't need a
client instance:
import { verifyWebhook } from "parisco-sdk/webhooks";
export async function POST(request: Request) {
const event = await verifyWebhook(request, process.env.PARISCO_WEBHOOK_SECRET);
if (event.type === "order.shipped") {
console.log(event.data.order_id, event.data.tracking);
}
if (event.type === "my_product.updated") {
// event.id / data.event_id is stable across retries: use it as your
// database idempotency key, then fetch only the affected private clone.
const clone = await parisco.myProducts.get(event.data.clone_id);
await lituel.products.upsert(clone);
}
return new Response("ok");
}my_product.updated includes event_id, timestamp, clone_id,
canonical_product_id, action, and changed_fields. It is delivered only
to subscriptions owned by the clone's account. Deliveries are at least once,
so the same event ID may arrive more than once after a timeout or retry.
For missed-webhook recovery, persist a high-water timestamp and reconcile in small pages:
for await (const clone of parisco.myProducts.iter({ updatedAfter: checkpoint })) {
await lituel.products.upsert(await parisco.myProducts.get(clone.id));
checkpoint = clone.updated_at;
}The change feed is ordered by updated_at, then clone ID, and includes
archived products. myProducts.get(cloneId) also returns archived products so
their status can be applied locally. Permanent clone deletion is not
supported; archive is the durable tombstone (status: "ARCHIVED"). Keep the
SDK-provided cursor while paging—do not manufacture a cursor from the timestamp.
verifyWebhookPayload(body, header, secret, options?) does the same from a
raw string body, for frameworks without a fetch-API Request. Both reject a
signature older than toleranceSeconds (default 300; pass 0 to disable)
and throw PariscoWebhookVerificationError on any mismatch.
Types & OpenAPI
Fully typed end to end — wire types are generated from Parisco's OpenAPI 3.1
document and re-exported under ergonomic camelCase names. The package ships
ESM and CommonJS builds with TypeScript declarations, the OpenAPI document at
parisco-sdk/openapi.json, and generated API reference docs.
License
MIT
