Skip to content

sellersmith/merchize-sdk

Repository files navigation

@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-sdk

Quick 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: "jane@example.com",
    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: "john@example.com",
    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-api

Tokens 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

About

TypeScript SDK for Merchize fulfillment API

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors