@sellersmith/merchize-sdk
v0.1.0
Published
TypeScript SDK for Merchize fulfillment API
Readme
@sellersmith/merchize-sdk
TypeScript SDK for the Merchize print-on-demand fulfillment API.
[!WARNING] Monthly Token Renewal Required
Merchize access tokens expire every month. Your integration must handle token rotation or all API calls will fail with 401 errors. See Token Rotation below.
Installation
npm install @sellersmith/merchize-sdkQuick Start
import { Merchize } from "@sellersmith/merchize-sdk";
const client = new Merchize({
baseUrl: "https://bo-group-1-2.merchize.com/{store_id}/bo-api",
accessToken: "your-monthly-access-token",
});
// Import an order for fulfillment
const result = await client.orders.import({
order_id: "shopify-1001",
shipping_info: {
full_name: "Jane Doe",
address_1: "123 Main St",
city: "Austin",
state: "TX",
postcode: "78701",
country: "US",
email: "[email protected]",
phone: "+15125550100",
},
items: [
{
name: "Classic Tee",
product_id: 42,
sku: "TEE-WHT-M",
merchize_sku: "MCZ-TEE-001",
quantity: 1,
price: 15.99,
currency: "USD",
image: "https://cdn.example.com/tee.png",
design_front: "https://cdn.example.com/design-front.png",
attributes: [
{ name: "Color", option: "White" },
{ name: "Size", option: "M" },
],
},
],
});
console.log(result.data.id);All request/response fields use snake_case.
Configuration
const client = new Merchize({
baseUrl: "https://bo-group-1-2.merchize.com/{store_id}/bo-api",
accessToken: "your-access-token",
timeout: 30000,
enableLogging: false,
onTokenExpired: () => {
console.error("Merchize token expired — renewal required");
},
});| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| baseUrl | string | Yes | — | API base URL from Integrations > API in your dashboard |
| accessToken | string | Yes | — | Monthly access token from your dashboard |
| timeout | number | No | 30000 | Request timeout in milliseconds |
| onTokenExpired | () => void | No | — | Callback fired automatically on 401 responses |
| enableLogging | boolean | No | false | Log requests and responses to console |
Token Rotation
Tokens have a monthly expiry. When expired, all calls return 401.
import { Merchize } from "@sellersmith/merchize-sdk";
const client = new Merchize({
baseUrl: process.env.MERCHIZE_BASE_URL!,
accessToken: await loadTokenFromDb(),
onTokenExpired: async () => {
// Alert your team or trigger an automated renewal flow
console.error("Merchize token expired — renew at dashboard: Integrations > API");
// After obtaining the new token, hot-swap without reinstantiating:
// const newToken = await renewToken();
// client.setAccessToken(newToken);
},
});
// Update the token at any time without creating a new client
client.setAccessToken("new-monthly-token");Store your token in a secrets manager (AWS Secrets Manager, Vault, etc.) and rotate it on a schedule before expiry.
API Reference
Orders
All 13 methods are on client.orders.
orders.import(order)
Import an order for fulfillment.
import(order: CreateOrderRequest): Promise<MerchizeResponse<ImportOrderResponse>>const res = await client.orders.import({
order_id: "my-order-123", // your external order ID
identifier: "shopify", // optional — adds tag "api_shopify"
shipping_info: {
full_name: "John Smith",
address_1: "456 Oak Ave",
address_2: "Apt 2B", // optional
city: "New York",
state: "NY",
postcode: "10001",
country: "US", // ISO 3166-1 alpha-2
email: "[email protected]",
phone: "+12125551234",
},
items: [
{
name: "Custom Hoodie",
product_id: 7,
sku: "HOODIE-BLK-L",
merchize_sku: "MCZ-HOOD-007",
quantity: 2,
price: 29.99,
currency: "USD",
image: "https://cdn.example.com/hoodie.png",
design_front: "https://cdn.example.com/front.png",
design_back: "https://cdn.example.com/back.png", // optional
attributes: [
{ name: "Color", option: "Black" },
{ name: "Size", option: "L" },
],
},
],
tags: ["vip", "rush"], // optional
});orders.getDetail(params)
Get order detail by Merchize code, external number, or identifier.
getDetail(params: OrderLookupParams): Promise<MerchizeResponse<OrderDetail>>
// params: { code?, external_number?, identifier? }const order = await client.orders.getDetail({ code: "RX-0001-ABCD" });
const order = await client.orders.getDetail({ external_number: "shopify-1001" });
const order = await client.orders.getDetail({ external_number: "1001", identifier: "shopify" });orders.getInvoice(params)
Get invoice/cost breakdown for an order.
getInvoice(params: OrderLookupParams): Promise<MerchizeResponse<OrderInvoice>>const invoice = await client.orders.getInvoice({ code: "RX-0001-ABCD" });
console.log(invoice.data.fulfillment_cost.total);orders.getProgress(params)
Get fulfillment progress for an order.
getProgress(params: OrderLookupParams): Promise<MerchizeResponse<OrderDetail>>const progress = await client.orders.getProgress({ code: "RX-0001-ABCD" });orders.getTracking(params)
Get shipment tracking info for an order.
getTracking(params: OrderLookupParams): Promise<MerchizeResponse<OrderTracking>>const tracking = await client.orders.getTracking({ code: "RX-0001-ABCD" });
console.log(tracking.data.status, tracking.data.service);orders.updateStatus(request)
Pause (hold) or resume an order.
updateStatus(request: OrderLinkRequest): Promise<MerchizeResponse<boolean>>
// OrderLinkRequest: { order: { code, external_number, identifier, action: "hold" | "resume" } }// Pause an order
await client.orders.updateStatus({
order: { code: "RX-0001-ABCD", external_number: "shopify-1001", identifier: "shopify", action: "hold" },
});
// Resume a paused order
await client.orders.updateStatus({
order: { code: "RX-0001-ABCD", external_number: "shopify-1001", identifier: "shopify", action: "resume" },
});orders.push(request)
Push an order to fulfillment.
push(request: OrderLinkRequest): Promise<MerchizeResponse<boolean>>await client.orders.push({
order: { code: "RX-0001-ABCD", external_number: "shopify-1001", identifier: "shopify" },
});orders.cancel(request)
Cancel an order.
cancel(request: OrderLinkRequest): Promise<MerchizeResponse<boolean>>await client.orders.cancel({
order: { code: "RX-0001-ABCD", external_number: "shopify-1001", identifier: "shopify" },
});Batch methods
All batch methods accept an OrderListRequest — an object with an order array.
// Common type for all batch methods
interface OrderListRequest {
order: Array<{ code: string; external_number: string; identifier: string }>;
}
const req = {
order: [
{ code: "RX-0001-ABCD", external_number: "1001", identifier: "shopify" },
{ code: "RX-0002-EFGH", external_number: "1002", identifier: "shopify" },
],
};| Method | Return type | Description |
|---|---|---|
| orders.listDetails(request) | MerchizeResponse<OrderDetail[]> | Get details for multiple orders |
| orders.listInvoices(request) | MerchizeResponse<OrderInvoice[]> | Get invoices for multiple orders |
| orders.listProgress(request) | MerchizeResponse<OrderProgressDetail[]> | Get progress for multiple orders |
| orders.listTracking(request) | MerchizeResponse<OrderTracking[]> | Get tracking for multiple orders |
| orders.listTickets(request) | MerchizeResponse<OrderTicket[]> | Get tickets for multiple orders |
const details = await client.orders.listDetails(req);
const invoices = await client.orders.listInvoices(req);
const progresses = await client.orders.listProgress(req);
const trackings = await client.orders.listTracking(req);
const tickets = await client.orders.listTickets(req);Products
products.getCatalog(params?)
Get the product catalog. Maximum 50 products per page.
getCatalog(params?: { page?: number; limit?: number }): Promise<MerchizeResponse<ProductCatalogPage>>
// ProductCatalogPage: { products: Product[]; total: number; limit: number; page: number }const page1 = await client.products.getCatalog({ page: 1, limit: 50 });
page1.data.products.forEach((p) => console.log(p._id, p.title));products.searchCollections(params?)
Search product collections by name.
searchCollections(params?: { limit?: number; name?: string; page?: number }): Promise<MerchizeResponse<Paging<Collection[]>>>const results = await client.products.searchCollections({ name: "t-shirt", page: 1, limit: 20 });
results.data.data.forEach((c) => console.log(c._id, c.name));products.getAllVariants(productId)
Get all variants for a specific product.
getAllVariants(productId: string): Promise<MerchizeResponse<Variant[]>>const variants = await client.products.getAllVariants("product-id-here");
variants.data.forEach((v) => {
console.log(v.sku, v.title);
v.options.forEach((o) => console.log(o.attribute.name, o.value));
});Tickets
tickets.create(request)
Open a support ticket for one or more orders.
create(request: CreateTicketRequest): Promise<MerchizeResponse<Ticket>>const ticket = await client.tickets.create({
order_codes: ["RX-0001-ABCD"], // Merchize order codes
category: "Wrong item",
subcategory: "Wrong size",
product_type: ["T-Shirt"], // optional
preferred: "Reprint", // optional preferred resolution
description: "Customer received size M instead of L.", // optional
images: [ // optional
{ original_name: "photo.jpg", image_url: "https://cdn.example.com/photo.jpg" },
],
});
console.log(ticket.data._id);tickets.update(ticketId, request)
Add a comment/message to an existing ticket.
update(ticketId: number, request: { message: string; images?: TicketImage[] }): Promise<MerchizeResponse<UpdateTicket>>await client.tickets.update(12345, {
message: "Replacement has been dispatched.",
images: [
{ original_name: "label.png", image_url: "https://cdn.example.com/label.png" },
],
});tickets.reopen(ticketId, request)
Reopen a resolved ticket.
reopen(ticketId: number, request: { message: string; is_escalate: boolean; cf_satisfied?: string }): Promise<MerchizeResponse<UpdateTicket>>await client.tickets.reopen(12345, {
message: "Issue persists — replacement also arrived damaged.",
is_escalate: true,
});tickets.resolve(ticketId)
Mark a ticket as resolved.
resolve(ticketId: number): Promise<MerchizeResponse<UpdateTicket>>await client.tickets.resolve(12345);Error Handling
All failed API calls throw MerchizeError.
import { Merchize, MerchizeError } from "@sellersmith/merchize-sdk";
try {
const order = await client.orders.getDetail({ code: "RX-NONEXISTENT" });
} catch (err) {
if (err instanceof MerchizeError) {
console.error(err.status); // HTTP status code, e.g. 404
console.error(err.message); // human-readable message
console.error(err.responseBody); // raw response body string
} else {
// Network error, timeout, etc.
throw err;
}
}| Property | Type | Description |
|---|---|---|
| status | number | HTTP status code |
| message | string | Error message |
| responseBody | string | Raw response body for debugging |
Base URL
Get your baseUrl from the Merchize Dashboard > Integrations > API.
The URL follows the format:
https://bo-group-X-Y.merchize.com/{store_id}/bo-apiTokens are store-scoped. Each store has its own base URL and access token.
Requirements
- Node.js 18+ (uses native
fetch) - Zero runtime dependencies
License
MIT — see LICENSE
