@shedcloud/partner-api
v0.9.0
Published
Official TypeScript/JavaScript client for the ShedCloud Partner API
Maintainers
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-apiHosts
| 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-history — create(...) 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-items — create(...) 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-links — create(...) 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-history — create(...) 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/roles — create(...) 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 typecheckVersioning & 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.
- SDK changes: CHANGELOG.md
- API changes: hosted changelog
Related SDKs
- Go:
shedcloud-gomod/partnerapi - Python:
shedcloud-partner-api - PHP:
shedcloud/partner-api - Ruby:
shedcloud-partner_api
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
