Skip to content

SDK (TypeScript)

@collaterate/partner-api is a typed TypeScript client for this API: typed requests and responses, async-iterator pagination over nextCursor, a typed ApiError, automatic 429/503 retry honoring Retry-After, and a one-line webhook-signature verifier. If you are integrating from Node or TypeScript, prefer it over hand-rolling the calls the rest of this site documents at the fetch/curl level.

It is distributed from a private registry, not the public npm registry, and reuses the same Cognito verification the REST API uses - but for only one of the REST API’s credential kinds. The Cognito client_credentials token you already mint for API calls (clientId/clientSecret, the normal way a partner’s own service integrates) also authenticates npm install: there is no second credential to request, rotate, or revoke, and revoking API access (the existing credential-revocation path) revokes SDK access in the same act. An interactive staff sign-in token (the one a partner’s own person gets from signing in through a browser, rather than a service holding a clientId/clientSecret) does not authenticate the install and is rejected with the same 401 an invalid credential gets - this only matters if you were planning to install using a personal sign-in rather than a service credential, which is not the documented path below.

Add these two lines to your project’s .npmrc:

@collaterate:registry=https://sdk.collaterate.com/
//sdk.collaterate.com/:_authToken=${COLLATERATE_API_TOKEN}

Every URL in this guide is the production one. For the sandbox, substitute collaterail.com for collaterate.com throughout - see Environments.

${COLLATERATE_API_TOKEN} is npm’s own syntax for reading an environment variable at install time - .npmrc never holds the token itself, only this reference.

With .npmrc in place and COLLATERATE_API_TOKEN exported:

Terminal window
npm install @collaterate/partner-api

3. What an expired or wrong token looks like

Section titled “3. What an expired or wrong token looks like”

An expired token, a token from the wrong environment’s user pool, or a partner credential that has been disabled or revoked all fail the same way an npm install failure always looks from npm’s own CLI - not something this SDK controls, since the registry broker’s 401 is what npm itself surfaces. The distinguishing question is the same one Authentication already answers for the REST API: a 401 here never explains which check failed, on purpose, so the fix is always the same regardless of cause - mint a fresh COLLATERATE_API_TOKEN and retry before assuming anything else is wrong.

Captured from a real revoked credential:

npm error code E401
npm error 401 Unauthorized - GET https://sdk.collaterate.com/@collaterate%2fpartner-api - unauthorized

Two details worth knowing when you read that line. The URL npm reports has the scope’s / percent-encoded as %2f, which is npm’s own doing and not a sign of a malformed .npmrc. And the request that fails is the packument fetch - the first call npm makes - so a 401 here means you never got as far as downloading a tarball, and nothing was partially installed.

The SDK is versioned independently of the API’s own v1/v2 scheme - a fix or an ergonomic addition ships as an SDK minor or patch with no API change involved, and the SDK’s version number has no required relationship to which API version it happens to target today. Only v1 exists, so this is not yet a live distinction, but it means an SDK upgrade is never itself evidence that the underlying API contract changed.

Upgrade the same way you would any other npm dependency:

Terminal window
npm install @collaterate/partner-api@latest

Every published version stays installable - publishing a new version merges it into the registry’s version list rather than replacing it, so pinning an older version in your lockfile continues to resolve. Read Versioning for what counts as an additive, in-place change versus one that ships as a new version, which governs the API underneath the SDK exactly as it does the raw REST contract.

Install, construct a client, list products, submit an order, and verify a webhook - the SDK equivalent of the raw calls in Getting started and Submitting orders.

Terminal window
npm install @collaterate/partner-api
integration.ts
import {
ApiError, PartnerApiClient, verifyWebhookSignature, type WebhookEvent,
} from '@collaterate/partner-api';
const client = new PartnerApiClient({
clientId: process.env.CLIENT_ID!,
clientSecret: process.env.CLIENT_SECRET!,
baseUrl: 'https://api.partners.collaterate.com',
});
async function listProducts(): Promise<void> {
// Async-iterates every page, walking `nextCursor` automatically -- no cursor loop to write.
for await (const product of client.products.list({ siteId: 7 })) {
console.log(product.productId, product.name);
}
}
async function submitOrder(): Promise<void> {
try {
// `partnerOrderId` is the idempotency key: resubmitting the identical body under an
// already-used id returns the original submission rather than creating a second order.
const submission = await client.orders.create({
siteId: 7,
partnerOrderId: 'acme-po-4471',
shipTo: {
name: 'Jane Doe',
address1: '123 Main St',
city: 'Springfield',
state: 'IL',
postalCode: '62701',
country: 'US',
},
lines: [{ productId: 'SLO_918273', partnerLineId: 'acme-line-1', quantity: 500, variantId: 55012 }],
});
// `POST /v1/orders` waits up to 20 seconds for a terminal state, so `submission.status` is
// `completed`/`failed` on roughly 19 of 20 calls. If it is still `queued`/`processing`,
// poll `client.submissions.get(submission.submissionId)` -- see Submitting orders.
if (submission.status === 'completed') {
console.log('order number', submission.order?.orderNumber);
} else if (submission.status === 'failed') {
console.error('submission rejected', submission.errors);
}
} catch (error) {
if (error instanceof ApiError) {
// error.code is the field to branch on -- see the Errors guide's code catalog. Retryable
// 429/503 responses never reach this catch: the client already retried them internally.
console.error(`${error.status} ${error.code}: ${error.detail}`);
} else {
throw error;
}
}
}
// In your webhook endpoint handler, before you parse or act on the body:
function handleWebhook(rawBody: string, signatureHeader: string): void {
const valid = verifyWebhookSignature(process.env.WEBHOOK_SECRET!, rawBody, signatureHeader);
if (!valid) {
throw new Error('invalid webhook signature');
}
// Only parse a body whose signature checked out. `WebhookEvent` types the result, so
// `eventId`/`type`/`orderNumber` are checked rather than hand-declared.
const event = JSON.parse(rawBody) as WebhookEvent;
// The event names what changed and carries no order data of its own -- re-read the
// resource for what is actually true. Dedupe on `event.eventId` first: best-effort
// delivery does not mean at-most-once, and the same event can arrive twice.
if (event.type === 'order.status_changed') {
void client.orders.get(event.orderNumber);
}
// Do not fail on a `type` you do not recognize. `WebhookEvent['type']` is deliberately an
// open union: an operator can add a type to your endpoint's subscription, effective
// immediately, before your next deploy. Compare against `KnownWebhookEventType` if you want
// the known set by name -- see the Webhooks guide.
}

Three things worth noting against the raw-fetch walkthroughs elsewhere on this site:

  • No token handling of your own. PartnerApiClient mints and caches the Cognito token internally and refetches on expiry or on a 401; the token is never exposed on the client’s public interface. This is the API’s own OAuth2 client_credentials credential (clientId/clientSecret) - a different credential from COLLATERATE_API_TOKEN above, which exists only to authenticate the package install.
  • No manual retry loop. 429 and 503 are retried automatically, honoring Retry-After. Pass retry: false to PartnerApiClient’s constructor if you want manual control instead.
  • ApiError carries status, code, detail and requestId - the same application/problem+json fields Errors documents, just parsed for you rather than something you parse yourself.

6. Flat filters and nested methods, side by side

Section titled “6. Flat filters and nested methods, side by side”

The REST API publishes both a flat collection with a query filter and a nested path that reads the same collection scoped to one parent - see Users and divisions for the REST-level detail. The SDK mirrors this rather than picking one: client.users.list({ siteId: 222 }) and client.users.listBySite(222) are the same request, same result, and neither is deprecated in favor of the other.

// Walking one site's tree - the nested form reads naturally in that context.
for await (const user of client.users.listBySite(222)) {
console.log(user.userId);
}
// Reconciling everything you can see - page the flat, unfiltered form instead. With 419,979
// users across 475 sites, one paged sweep beats one call per site.
for await (const user of client.users.list()) {
console.log(user.userId);
}

The same pair exists on client.divisions, client.credit, client.segments, client.products and client.orders for their own GET /v1/sites/{siteId}/... paths, and client.users.listByDivision(divisionId) mirrors GET /v1/divisions/{divisionId}/users. Each nested method takes the parent id as its first argument and whatever further filters that nested path itself accepts (see docs/openapi/v1.yaml for the exact set per path) - the flat method’s own filter parameter is unchanged and still there.

client.sites.getSite(siteId) is new too: GET /v1/sites/{siteId} had no SDK method before this release.

  • Getting started - the raw-fetch walkthrough this SDK replaces, if you need to see a call at the wire level.
  • Submitting orders - the full submission lifecycle client.orders.create() wraps, including the fast path and the poll.
  • Webhooks - everything verifyWebhookSignature checks for you, and what to do with a delivery once it verifies.
  • Errors - the full code catalog ApiError.code surfaces.