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

@thumbmarkjs/management

v0.1.3

Published

TypeScript SDK for the Thumbmark Management API — programmatic access to allowed-hostnames and other account resources.

Readme

@thumbmarkjs/management

TypeScript SDK for the Thumbmark Management API. Provides programmatic access to the Thumbmark Admin API from Node.js or any runtime that supports the native fetch API.

Install

npm install @thumbmarkjs/management

Requirements

  • Node.js ≥ 18 (uses native fetch — no polyfill needed)
  • Zero runtime dependencies

Authentication

Create a management secret key in the Thumbmark Admin Console at admin.thumbmarkjs.com/management-apiCreate Management Secret. The key looks like tmsec_live_… and is shown only once, at creation — copy it immediately, as it cannot be retrieved later. Keep it secure: it grants write access to your account's allowed-hostnames list.

Secrets are created in the console only. A management secret cannot create or manage other secrets, so there is no way to mint one programmatically with this SDK — you must be signed in to the console.

import { createManagementClient } from '@thumbmarkjs/management';

const client = createManagementClient({
  apiKey: process.env.THUMBMARK_MGMT_SECRET!, // tmsec_live_…
});

The key is sent as Authorization: Bearer <apiKey> on every request. No client-side hashing is applied.

Options

const client = createManagementClient({
  apiKey: 'tmsec_live_…', // required
  timeoutMs: 10_000, // optional, default 10 s
  retry: { maxRetries: 2 }, // optional, default 2 retries
});
  • Retries are applied automatically to transient failures — network errors, timeouts, and 5xx — with exponential backoff. Every operation here is idempotent (GET / PUT / DELETE), so a retry never causes a double mutation. 4xx responses are never retried.

Operations

Hostname semantics

  • Account-wide. The allowed list applies to all API keys on your account, not a single key.
  • Subdomains are included. Adding example.com also allows app.example.com, api.example.com, and any other subdomain. Wildcards are not supported (and aren't needed).
  • Bare hostnames only. Scheme, port, and path are stripped and the value is lowercased server-side (https://Example.com:443/pathexample.com).
  • Up to 1000 hostnames per account.

client.allowedHostnames.list(options?)

Lists the allowed hostnames for the authenticated account, one page at a time.

// First page
const { items } = await client.allowedHostnames.list();
console.log(items); // ['example.com', 'app.example.com', ...]

// Walk every page (follow nextCursor until it's absent)
const all: string[] = [];
let cursor: string | undefined;
do {
  const page = await client.allowedHostnames.list({ cursor });
  all.push(...page.items);
  cursor = page.nextCursor;
} while (cursor);

Returns { items: string[]; nextCursor?: string }. Each page's items are sorted alphabetically, but ordering is not guaranteed across pages — when paginating, do not rely on a globally sorted sequence (collect all pages and sort yourself if you need that). When nextCursor is absent this is the final page.

client.allowedHostnames.add(hostname)

Adds a hostname to the account's allowed list. This is an idempotent upsert — adding a hostname that is already present resolves without error.

Resolves { status }, the HTTP status the API returned, so you can tell the two cases apart:

const { status } = await client.allowedHostnames.add('example.com');
// status === 201  -> hostname newly added
// status === 204  -> hostname was already present (no-op)

Throws ValidationError (HTTP 400) if the hostname is invalid (e.g., wildcard format) or would exceed the 1 000-hostname cap.

client.allowedHostnames.remove(hostname)

Removes a hostname from the account's allowed list. Resolves { status }.

try {
  const { status } = await client.allowedHostnames.remove('old.example.com');
  // status === 204 -> hostname existed and was removed
} catch (err) {
  if (err instanceof NotFoundError) {
    // err.status === 404 -> hostname was not in the allowed list
  }
}

Throws NotFoundError (HTTP 404) if the hostname was not present. This differs from add() — by default remove() is not idempotent on a missing hostname.

Pass { idempotent: true } to make it idempotent: a hostname that is already absent resolves { status: 204 } instead of throwing.

// Ensure the hostname is gone; don't care whether it was there.
const { status } = await client.allowedHostnames.remove('old.example.com', {
  idempotent: true,
});
// status === 204 — whether it existed and was removed, or was already absent

Status codes

The HTTP status (on { status }, or error.status for the thrown case) lets you confirm exactly which outcome occurred:

| Operation | Outcome | Status | | ----------------------------------------------- | -------------------------- | ------ | | add() a hostname not yet present | newly added | 201 | | add() a hostname already present | already present (no-op) | 204 | | remove() a hostname that existed | removed | 204 | | remove() a hostname not present | NotFoundError (rejected) | 404 | | remove(…, { idempotent: true }) a missing one | no-op (not thrown) | 204 |

Error classes

All errors are exported and can be caught with instanceof.

| Class | HTTP status | When thrown | | --------------------- | ----------- | ------------------------------------------------------------------ | | NotFoundError | 404 | remove() of a hostname not in the allowed list | | ValidationError | 400 | Invalid hostname, malformed cursor, or 1 000-hostname cap exceeded | | AuthenticationError | 401 / 403 | Missing, invalid, or revoked API key | | ServerError | 5xx | Internal server error | | NetworkError | — | DNS failure, connection refused, etc. (before any HTTP response) | | TimeoutError | — | Request exceeded timeoutMs | | ConfigurationError | — | Thrown synchronously by createManagementClient for bad options | | ManagementApiError | any | Base class for all HTTP error classes above |

All HTTP error classes extend ManagementApiError and expose:

  • .status — HTTP status code
  • .code — machine-readable error code string
  • .body — raw parsed response body

Example: catching specific errors

import {
  createManagementClient,
  NotFoundError,
  AuthenticationError,
} from '@thumbmarkjs/management';

const client = createManagementClient({
  apiKey: process.env.THUMBMARK_MGMT_SECRET!,
});

try {
  await client.allowedHostnames.remove('maybe-missing.example.com');
} catch (err) {
  if (err instanceof NotFoundError) {
    console.log('Hostname was not in the list');
  } else if (err instanceof AuthenticationError) {
    console.log('Check your API key');
  } else {
    throw err;
  }
}

License

MIT