npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@shedcloud/partner-api

v0.9.0

Published

Official TypeScript/JavaScript client for the ShedCloud Partner API

Readme

@shedcloud/partner-api

Official TypeScript/JavaScript client for the ShedCloud Partner API.

Use this package from Node.js (18+) or any modern runtime with fetch to call company-scoped Partner API endpoints under /partner/v1/*.

Install

npm install @shedcloud/partner-api

Hosts

| Environment | Host | |-------------|------| | production (default) | https://go.shedcloud.com | | sandbox | https://api.shedcloudtest.com |

You usually only pass auth — the production host is used automatically. Set environment: 'sandbox' for test, or baseUrl for a custom/local override.

Quick start

API key (production)

import { ShedCloudPartnerClient } from '@shedcloud/partner-api';

const client = new ShedCloudPartnerClient({
  auth: {
    type: 'apiKey',
    apiKey: process.env.SHEDCLOUD_API_KEY!,
  },
});

const stock = await client.lotStock.list({
  limit: 50,
  purchaseType: 'Lot Stock',
  sort: 'price',
  order: 'asc',
});

console.log(stock.total, stock.data[0]?.title);

Sandbox

const client = new ShedCloudPartnerClient({
  environment: 'sandbox',
  auth: { type: 'apiKey', apiKey: process.env.SHEDCLOUD_API_KEY! },
});

OAuth2 client credentials

import { ShedCloudPartnerClient } from '@shedcloud/partner-api';

const client = new ShedCloudPartnerClient({
  auth: {
    type: 'oauth',
    clientId: process.env.SHEDCLOUD_CLIENT_ID!,
    clientSecret: process.env.SHEDCLOUD_CLIENT_SECRET!,
  },
});

// Access tokens are fetched from POST /oauth/token and cached until near expiry.
const orders = await client.orders.list({ status: 'Unprocessed', limit: 25 });

Create credentials in the ShedCloud portal under Settings → Company → Developer API.

Resources

Each resource maps to a section of the hosted reference.

| Client property | Endpoints | Reference | |-----------------|-----------|-----------| | client.lotStock | GET /partner/v1/lot-stock | #lot-stock | | client.stockTemplates | GET /partner/v1/stock-templates (buildable catalog designs) | #stock-templates | | client.leads | GET/POST/PATCH /partner/v1/leads, POST .../status, GET .../status-historycreate(...) makes a lead with location lead-routing | #leads | | client.quotes | GET/POST/PATCH /partner/v1/quotes, POST .../status, GET .../status-history, GET/POST/DELETE .../line-itemscreate(...) makes an in-stock quote from a serial number, convert(id) places it as a sales order | #quotes | | client.orders | GET/POST/PATCH /partner/v1/orders, POST .../status, GET .../status-history, GET/POST/DELETE .../line-items, GET .../contract, GET/POST .../payments, POST .../payment-linkscreate(...) makes a full order (customer, base product + size, upgrades, configurator); createPayment(...) records manual payments, createPaymentLink(...) returns a Stripe Checkout URL | #orders | | client.workOrders | GET/POST/PATCH /partner/v1/work-orders, POST .../status, GET .../status-historycreate(...) makes a work order with friendly type enums + optional sizeId | #work-orders | | client.locations | GET/POST/PATCH /partner/v1/locations | #locations | | client.customers | GET/POST/PATCH /partner/v1/customers, POST .../{id}/merge | #customers | | client.products | GET/POST/PATCH /partner/v1/products, createSize(id, ...) for POST .../{id}/sizes (finished catalog products with gallery images) | #products | | client.domains | GET /partner/v1/domains, forLocation(id) for GET /partner/v1/locations/{id}/domains (white-label storefront domains, defaultForStore filter) | #domains | | client.users | GET/POST/PATCH /partner/v1/users, roles() for GET /partner/v1/rolescreate(...) makes a company user (role, locations, invite email); update(id, ...) patches profile/role/locations/active | #users | | client.payments | GET /partner/v1/payments[/{id}] (read-only) | #payments | | client.salesLedger | GET /partner/v1/sales-ledger (movement history; saleDate / sticky cancelled filters) | #sales-ledger | | client.documents | GET /partner/v1/documents, GET .../{id}/download (short-lived presigned URL) | #documents | | client.events | GET /partner/v1/events cursor feed, iterate(...) async iterator, redeliver(id), deliveries(...) webhook delivery log | #events | | client.siteEvents | POST /partner/v1/site-events batch ingest (visitor behavioral tracking, snake_case body), list(...) / iterate(...) read-back | #site-events | | client.configuratorSessions | POST /partner/v1/configurator-sessions (single-use 3D configurator launch URLs) | #configurator-sessions |

Examples

const lead = await client.leads.get('665f0a1b2c3d4e5f60718293');

await client.leads.update(lead.id, {
  salespersonName: 'Alex Rep',
  salesLocation: '66c00443c2d8aa83c5757dcf',
});

await client.orders.updateStatus(orderId, {
  status: 'On hold',
  actionDescription: 'Waiting on customer financing',
});

// Create a quote from an in-stock unit: the serial number's work order is
// linked to the new quote, the sales location is assigned, and lead routing
// auto-assigns a salesperson when the location has routing configured.
const quote = await client.quotes.create({
  serialNumber: 'SC-2024-00123',
  customer: { name: 'Jane Doe', email: '[email protected]', phone: '555-0100' },
  deliveryAddress: { address: '42 Oak Ave', city: 'Dallas', state: 'TX', zipCode: '75201' },
});

// Create a lead: when no salesperson is given, the location's lead-routing
// strategy (round-robin, availability, skill-based) auto-assigns one.
const newLead = await client.leads.create({
  locationId: '66c00443c2d8aa83c5757dcf',
  customer: { name: 'Jane Doe', email: '[email protected]' },
});

// Convert a quote to a sales order (requires partner-api.orders.write).
// The new order starts in "Unsubmitted" — submit with updateStatus.
const order = await client.quotes.convert(quote.id);

// Or create a full order directly: customer + location + base product
// (model + size), upgrades, and an optional configurator payload.
const fullOrder = await client.orders.create(
  {
    customer: { name: 'Jane Doe', email: '[email protected]' },
    locationId: '66c00443c2d8aa83c5757dcf',
    productId: '6659f3ab8e5a2c001f9b1c11',
    sizeId: '6659f3ab8e5a2c001f9b1c22',
    basePrice: 8995,
    upgrades: [{ productId: '6659f3ab8e5a2c001f9b1c33', quantity: 2 }],
    configuration: { sidingColor: 'Barn Red', roofMaterial: 'Metal Roof', roofColor: 'Charcoal' },
  },
  { idempotencyKey: crypto.randomUUID() },
);

// Manage upgrade lines afterward (idempotent on lineKey); the base product
// line is protected. Update pricing fields yourself after changing lines.
const line = await client.orders.addLineItem(fullOrder.id, {
  productId: '6659f3ab8e5a2c001f9b1c44',
  lineKey: 'crm-line-42',
});
await client.orders.deleteLineItem(fullOrder.id, line.lineId);

// Create a manufacturing work order (portal building-wizard parity): status
// starts in "Customer Care", the number is allocated automatically, and an
// optional sizeId attaches the product.
const workOrder = await client.workOrders.create(
  {
    locationId: 'dallas-lot',
    purchaseType: 'new-build',
    workOrderType: 'made-to-order',
    deliveryType: 'delivery-from-factory',
    serialNumber: 'SC-2026-00999',
    sizeId: '6659f3ab8e5a2c001f9b1c22',
  },
  { idempotencyKey: crypto.randomUUID() },
);

// Record a manual payment (cash | check | financed | manual) — runs the
// portal's own pipeline: payment record + order-balance recalc + audit.
// Card/ACH are rejected; use payment links for those.
await client.orders.createPayment(
  fullOrder.id,
  { method: 'check', amount: 500, checkNumber: '1042' },
  { idempotencyKey: crypto.randomUUID() },
);

// Or send the customer a Stripe Checkout link (requires the company's
// Stripe integration): the webhook records the payment when they pay.
const link = await client.orders.createPaymentLink(fullOrder.id, {
  amount: 250,
  sendEmail: true, // email the link to the order's customer (default)
});
console.log(link.url, link.expiresAt);

// Create a company user: discover roles first, then create with the role,
// locations, and invite email. The user's login is created at their first
// sign-in via the invite (no Cognito account is made here).
const { data: roles } = await client.users.roles();
const salesRole = roles.find((r) => r.name === 'Salesperson');
const newUser = await client.users.create(
  {
    firstName: 'Alex',
    lastName: 'Rep',
    email: '[email protected]',
    roleId: salesRole!.id,
    locationIds: ['66c00443c2d8aa83c5757dcf'],
  },
  { idempotencyKey: crypto.randomUUID() },
);
// Deactivate later (the company owner is protected).
await client.users.update(newUser.id, { active: false });

// All create/convert calls accept an idempotency key: a retried request with
// the same key replays the stored response instead of creating a duplicate.
await client.quotes.create(
  { serialNumber: 'SC-2024-00123', customer: { email: '[email protected]' } },
  { idempotencyKey: crypto.randomUUID() },
);

// Stamp your own correlation ids on records; filter lists by them later.
await client.orders.update(order.id, {
  externalReferences: { crmDealId: 'deal-42' },
});
const matches = await client.orders.list({ externalRef: 'crmDealId:deal-42' });

// Optimistic concurrency: send the version you read as If-Match — the server
// answers 409 Conflict if someone else wrote in between.
await client.orders.update(order.id, { customerPhone: '555-0100' }, { ifMatch: order.version });

// Consume the change feed losslessly with a stored cursor.
for await (const event of client.events.iterate({ cursor: lastSeenEventId })) {
  console.log(event.type, event.resourceId);
  lastSeenEventId = event.id;
}

// Catalog products: create a model, then give it real pricing via sizes.
const product = await client.products.create(
  { name: '10x16 Lofted Barn', sku: 'LB-1016', lineId: '6529b409e7d0f84e18e2a100' },
  { idempotencyKey: crypto.randomUUID() },
);
await client.products.createSize(product.id, { width: 10, length: 16, price: 8200 });
await client.products.update(product.id, { description: 'Lofted barn with double doors' });

// White-label storefront domains: find each location's primary storefront.
const defaults = await client.domains.list({ defaultForStore: true });
for (const domain of defaults.data) {
  console.log(domain.subdomain, '→', domain.locations.map((l) => l.name));
}
const storeDomains = await client.domains.forLocation('66c00443c2d8aa83c5757dcf');

Webhooks

Verify webhook deliveries with the subscription secret (shown once when the webhook is created in Settings → Developer API). Always verify against the raw request body:

import { verifyWebhookSignature, WebhookVerificationError } from '@shedcloud/partner-api';

app.post('/webhooks/shedcloud', express.raw({ type: 'application/json' }), async (req, res) => {
  try {
    await verifyWebhookSignature(
      req.body,
      req.header('X-ShedCloud-Signature') ?? '',
      process.env.SHEDCLOUD_WEBHOOK_SECRET!,
    );
  } catch (err) {
    if (err instanceof WebhookVerificationError) return res.status(400).send('bad signature');
    throw err;
  }
  const event = JSON.parse(req.body.toString('utf8'));
  res.sendStatus(200); // ack fast; process async and dedupe by event.id
});

Errors

Failed responses throw PartnerApiError (or PartnerApiAuthError for OAuth token failures):

import { PartnerApiError } from '@shedcloud/partner-api';

try {
  await client.orders.get(id);
} catch (err) {
  if (err instanceof PartnerApiError) {
    console.error(err.status, err.message, err.body);
    // err.isUnauthorized / isForbidden / isNotFound / isRateLimited
  }
}

Scopes

Scope constants match the backend catalog:

import { PartnerScopes } from '@shedcloud/partner-api';

PartnerScopes.LotStockRead; // 'partner-api.lot-stock.read'
PartnerScopes.OrdersWrite;  // 'partner-api.orders.write'

Development

npm install
npm run build
npm test
npm run typecheck

Versioning & changelog

The Partner API is additive-only within /partner/v1 — new endpoints and fields may appear, but existing response fields are never renamed, retyped, or removed. This SDK follows semver in lockstep with API additions: new API capabilities arrive as minor releases, fixes as patches. Pinning an older version is always safe; it simply won't surface newer fields.

Related SDKs

Docs

  • Partner API reference: https://go.shedcloud.com/partner/reference
  • API changelog: https://go.shedcloud.com/partner/reference#changelog
  • Backend source of truth: shedcloud-api-go/docs/PARTNER_API.md
  • npm: @shedcloud/partner-api