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

sob-connect-sdk

v0.1.0

Published

Node.js/TypeScript SDK for the Connect API (partner-facing REST API).

Readme

sob-connect-sdk

Lightweight Node.js / TypeScript SDK for seamless SOB Connect API integration — a partner-facing REST API for managing a company's workspace users, access levels, and user types.

  • Node >= 22 (uses native fetch, zero runtime dependencies)
  • ESM only, written in strict TypeScript
  • Automatic retries on rate limiting, automatic idempotency keys on writes

Installation

npm install sob-connect-sdk

Quick start

Partners receive a bootstrap config file from the platform admin UI, shaped:

{
  "slug": "acme-co",
  "base_url": "https://api.example.com",
  "token": "sob_xxxxx"
}

Point the client at that file's path:

import { SobConnectClient } from 'sob-connect-sdk';

const client = new SobConnectClient('./connect-bootstrap.json');

const { workspace, access_levels, user_types } = await client.config.get();
const { data: users, meta } = await client.users.list({ filter: { status: 'active' } });

Manual config

You can also construct the client from an in-memory object instead of a file path — useful when the bootstrap values come from your own secret store:

const client = new SobConnectClient({
  slug: 'acme-co',
  base_url: 'https://api.example.com',
  token: process.env.CONNECT_API_TOKEN!,
});

Both forms accept an optional second argument:

const client = new SobConnectClient(source, {
  timeoutMs: 15_000, // default 10_000
  maxRetries: 3, // default 2
  fetch: myCustomFetch, // default globalThis.fetch — useful for tests
});

Resource reference

client.config

  • get(): Promise<WorkspaceConfig> — workspace metadata plus the full list of access levels and user types.

client.accessLevels

  • list(): Promise<AccessLevel[]>

client.userTypes

  • list(): Promise<UserType[]>

client.users

  • list(params?: ListUsersParams): Promise<PaginatedResponse<ConnectUser>> — filter by status, access_level_id, user_type_id, email; sort and paginate with sort, perPage, page.
  • upsert(payload, opts?): Promise<{ user: ConnectUser; created: boolean }> — creates or updates a user by email. created is derived from the HTTP status (201 vs 200).
  • get(email): Promise<ConnectUser>
  • update(email, payload, opts?): Promise<ConnectUser>
  • changeRole(email, accessLevelId, opts?): Promise<ConnectUser>
  • changeStatus(email, status?, opts?): Promise<ConnectUser> — omit status to toggle active/inactive.
  • recordLogin(email, loggedInAt?, opts?): Promise<ConnectUser>
  • delete(email): Promise<void>

All mutating methods accept an optional { idempotencyKey?: string } as their last argument — see Retries & idempotency.

Error handling

Every non-2xx response is mapped to a typed error, all extending SobConnectError (message, status, type):

| Class | HTTP status | type | | ------------------------ | ----------- | ---------------- | | AuthenticationError | 401 | authentication | | AuthorizationError | 403 | authorization | | NotFoundError | 404 | not_found | | ValidationError | 422 | validation | | RateLimitError | 429 | rate_limited | | SobConnectNetworkError | — | network_error |

ValidationError additionally exposes errors: Record<string, string[]>. RateLimitError additionally exposes retryAfterSeconds: number.

An unrecognized error.type from the API falls back to the base SobConnectError rather than throwing an unrelated error, so future server error types degrade gracefully instead of crashing the SDK.

Note: config/usage mistakes (a missing or malformed bootstrap file/object) throw a plain Error, not a SobConnectError — these are local mistakes, not API failures.

import { NotFoundError, ValidationError, SobConnectError } from 'sob-connect-sdk';

try {
  await client.users.get('[email protected]');
} catch (error) {
  if (error instanceof NotFoundError) {
    // handle 404
  } else if (error instanceof ValidationError) {
    console.error(error.errors);
  } else if (error instanceof SobConnectError) {
    console.error(error.type, error.status, error.message);
  } else {
    throw error;
  }
}

Retries & idempotency

Retries are automatic by default. The SDK retries only on HTTP 429 (rate limited) responses:

  1. If the response has a Retry-After header, it waits that many seconds.
  2. Otherwise it backs off exponentially (500ms * 2^attempt, capped at 8s, ±20% jitter).
  3. It retries up to maxRetries times (default 2, so 3 attempts total) before throwing RateLimitError.
  4. Any other status (including 5xx) is never retried.
  5. Set maxRetries: 0 in the client options to disable retries entirely.

Idempotency keys are automatic by default. Every POST/PUT/PATCH request automatically attaches a fresh Idempotency-Key header (a crypto.randomUUID()), which the server caches per-token for 24h. GET and DELETE never send this header. Pass your own key via opts.idempotencyKey on any mutating resource method to control retries/dedupe across process restarts:

await client.users.upsert(payload, { idempotencyKey: 'my-own-key-for-this-op' });

Rate limits

The Connect API allows 60 requests/min per bearer token. Every response includes X-RateLimit-Limit and X-RateLimit-Remaining headers; the SDK tracks the most recently observed values on the client:

await client.users.list();
console.log(client.lastRateLimit); // { limit: 60, remaining: 59 }

License

MIT