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

@authpi/admin

v1.3.0

Published

Official TypeScript Admin SDK for AuthPI

Readme

@authpi/admin

Official TypeScript Admin SDK for the AuthPI Core API.

Requirements: Node.js 18+, Bun, Deno, or Cloudflare Workers (server-side only — API keys are secrets)

Stability

@authpi/admin follows semantic versioning. Public exports, constructor options, generated request and response types, resource accessors, and documented method behavior are stable across 1.x; incompatible changes will ship in a new major version.

Installation

npm install @authpi/admin
# or
pnpm add @authpi/admin

Quick Start

import { AuthPIAdmin } from "@authpi/admin";

const admin = new AuthPIAdmin({ apiKey: { id: "key_xxx", secret: "your_key_secret" }, accountId: "acc_xxx" });

// List issuers
const page = await admin.issuers.list({ limit: 10 });
console.log(page.data); // Issuer[]

// Scope into an issuer and manage users
const users = await admin.issuer("i_xxx").users.list();

for await (const user of admin.issuer("i_xxx").users.listAll()) {
  console.log(user);
}

// Create a user
const user = await admin.issuer("i_xxx").users.create({
  username_type: "email",
  username: "[email protected]",
  profile: { first_name: "Alice", last_name: "Smith" },
});

// Create a webhook with typed event subscriptions
const webhook = await admin.webhooks.create({
  name: "Lifecycle events",
  url: "https://example.com/webhooks/authpi",
  auth: { type: "signature" },
  events: ["organization.created", "user.created"],
});

Request body types such as CreateWebhookInput are generated from the API schema. You can pass object literals directly to .create(...) and .update(...); TypeScript uses the method signature to validate required keys and enum values. If you build a payload before passing it, use satisfies:

import type { CreateWebhookInput } from "@authpi/admin";

const payload = {
  name: "Lifecycle events",
  url: "https://example.com/webhooks/authpi",
  auth: { type: "signature" },
  events: ["user.created"],
} satisfies CreateWebhookInput;

await admin.webhooks.create(payload);

Authentication

API Key (default)

API keys are issued as an id + secret pair — both parts are shown once when you create the key in the dashboard. The SDK sends them as HTTP Basic credentials (key_id:key_secret):

const admin = new AuthPIAdmin({ apiKey: { id: "key_xxx", secret: "your_key_secret" }, accountId: "acc_xxx" });

Bearer Token

For server-side applications authenticating on behalf of a user session:

const admin = new AuthPIAdmin({
  accessToken: "tok_xxx",
  accountId: "acc_xxx",
});

With optional token refresh callback:

const admin = new AuthPIAdmin({
  accessToken: "tok_xxx",
  accountId: "acc_xxx",
  onTokenExpired: async () => {
    const newTokens = await myRefreshLogic();
    return { accessToken: newTokens.accessToken };
  },
});

When onTokenExpired is provided, the SDK calls it on 401 responses and retries the request with the new token. Concurrent 401s are deduplicated — only one refresh runs at a time.

Account resolution

accountId is optional. When omitted, the SDK resolves it once via GET /v1/me on the first request and caches it — an API key always maps to exactly one account:

const admin = new AuthPIAdmin({ apiKey: { id: "key_xxx", secret: "your_key_secret" } });
const issuers = await admin.issuers.list(); // resolves the account transparently

If the credential can act on zero or multiple accounts (possible with user bearer tokens), the SDK throws a ConfigurationError naming the choices — pass accountId explicitly in that case.

You can also ask directly who the API considers you to be:

const me = await admin.whoami();
// { type: "api_key", key_id: "key_...", issuer_id: "i_...", accounts: [{ account_id, org_id, scopes }] }

Retries

Read-only requests (GET, HEAD, OPTIONS) are automatically retried on 429, 502, 503, and 504 responses with exponential backoff. The Retry-After header is respected when present.

// Default: retries enabled (3 attempts, 1s base delay, exponential backoff)
const admin = new AuthPIAdmin({ apiKey: { id: "key_xxx", secret: "your_key_secret" }, accountId: "acc_xxx" });

// Disable retries
const admin = new AuthPIAdmin({ apiKey: { id: "key_xxx", secret: "your_key_secret" }, accountId: "acc_xxx", retries: false });

// Custom retry config
const admin = new AuthPIAdmin({
  apiKey: { id: "key_xxx", secret: "your_key_secret" },
  accountId: "acc_xxx",
  retries: { limit: 5, delay: 500, backoff: "linear" },
});

Mutations (POST, PATCH, DELETE) are never retried automatically. Use idempotency keys and handle retries explicitly for writes.

Scoped Client Pattern

The SDK mirrors the API's resource hierarchy. Navigate with chained accessors — singular for scoping into a specific entity, plural for collections:

// Account-level resources
admin.issuers.list()
admin.webhooks.create({ name: "Lifecycle events", url: "https://...", auth: { type: "signature" }, events: ["user.created"] })
admin.events.list({ limit: 50 })

// Issuer scope
const iss = admin.issuer("i_xxx");
iss.users.list()
iss.agents.create({ name: "bot" })
iss.clients.list()
iss.organizations.list()

// Organization scope (nested under issuer)
const org = admin.issuer("i_xxx").organization("org_xxx");
org.members.list()
org.invitations.list()
org.sso.addDomain({ domain: "acme.com" })

// User scope (nested under issuer)
const usr = admin.issuer("i_xxx").user("usr_xxx");
usr.get()
usr.sessions.list()
usr.tokens.list()
usr.trustedDevices.list()
usr.verifiers.list()

// Webhook scope
const wh = admin.webhook("wh_xxx");
wh.get()
wh.deliveries.list()

All scope and resource accessors are lazily cached — repeated access returns the same instance.

Pagination

List endpoints return a Page<T> with cursor-based pagination:

// Manual pagination
const page = await admin.issuer("i_xxx").users.list({ limit: 25 });
console.log(page.data);       // User[]
console.log(page.hasMore);    // boolean
console.log(page.nextCursor); // string | undefined

// Fetch next page
if (page.hasMore) {
  const next = await admin.issuer("i_xxx").users.list({
    limit: 25,
    cursor: page.nextCursor,
  });
}

// Auto-pagination (yields individual items across all pages)
for await (const user of admin.issuer("i_xxx").users.listAll()) {
  console.log(user);
}

ETags & Optimistic Concurrency

GET responses include an _etag field. Pass it back on updates to prevent overwriting concurrent changes:

const user = await admin.issuer("i_xxx").users.get("usr_xxx");
console.log(user._etag); // "W/\"abc123\""

// Conditional update — fails with PreconditionFailedError if modified since
await admin.issuer("i_xxx").users.update("usr_xxx",
  { profile: { display_name: "Bob" } },
  { ifMatch: user._etag },
);

The SDK automatically strips _etag (and all _-prefixed keys) from outbound request bodies, so passing a fetched object back as a body is safe.

Idempotency

Mutating operations accept an optional idempotency key to guarantee at-most-once execution:

await admin.issuer("i_xxx").users.create(
  { username_type: "email", username: "[email protected]" },
  { idempotencyKey: "req_abc123" },
);

Custom Actions

Some resources expose additional action endpoints beyond CRUD:

// Block/unblock API keys (account-level)
await admin.apiKeys.block("key_xxx");
await admin.apiKeys.unblock("key_xxx");

// Rotate an API key
await admin.apiKeys.rotate("key_xxx");

// Rotate a client secret
await admin.issuer("i_xxx").clients.rotateSecret("c_xxx");

// Revoke all trusted devices for a user
await admin.issuer("i_xxx").user("usr_xxx").trustedDevices.revokeAll();

Error Handling

The SDK maps HTTP status codes to specific error classes:

import {
  ApiError,
  NotFoundError,
  ValidationError,
  AuthenticationError,
  RateLimitError,
  PreconditionFailedError,
} from "@authpi/admin";

try {
  await admin.issuer("i_xxx").users.get("usr_xxx");
} catch (err) {
  if (err instanceof NotFoundError) {
    console.log("User not found");
  } else if (err instanceof ValidationError) {
    console.log("Validation failed:", err.fields);
  } else if (err instanceof RateLimitError) {
    console.log(`Retry after ${err.retryAfter} seconds`);
  } else if (err instanceof PreconditionFailedError) {
    console.log(`Stale ETag, current: ${err.currentETag}`);
  } else if (err instanceof AuthenticationError) {
    console.log("Invalid API key");
  }
}

Error Hierarchy

| Error | Status | Extra Fields | Retryable | |-------|--------|--------------|-----------| | ApiError | — | error, errorDescription, statusCode, retryable, reference, rawBody | — | | ValidationError | 400, 422 | fields | No | | AuthenticationError | 401 | — | No | | ForbiddenError | 403 | — | No | | NotFoundError | 404 | — | No | | ConflictError | 409 | — | No | | PreconditionFailedError | 412 | currentETag | No | | RateLimitError | 429 | retryAfter | Yes | | InternalServerError | 500 | — | No | | BadGatewayError | 502 | — | Yes | | ServiceUnavailableError | 503 | — | Yes | | GatewayTimeoutError | 504 | — | Yes | | UnexpectedError | other | — | No |

Configuration

import { AuthPIAdmin } from "@authpi/admin";

const admin = new AuthPIAdmin({
  // Authentication (apiKey or accessToken; accountId is always required)
  apiKey: { id: "key_xxx", secret: "your_key_secret" },  // API key authentication
  accessToken: "tok_xxx",                     // Bearer token authentication
  accountId: "acc_xxx",                       // Required — the API has no "me" alias

  // Token refresh (only with accessToken)
  onTokenExpired: async () => {               // Called on 401; concurrent calls are deduplicated
    return { accessToken: "new_tok_xxx" };
  },

  // Connection
  baseUrl: "https://api.authpi.com",          // Default
  fetch: customFetch,                         // Optional: custom fetch for alternative runtimes

  // Retries (read-only requests only)
  retries: true,                              // Default: enabled (3 attempts, 1s delay, exponential)
  // retries: false,                          // Disable retries
  // retries: { limit: 5, delay: 500, backoff: "linear" },  // Custom config

  // Headers
  defaultHeaders: { "X-Custom": "value" },   // Extra headers sent on every request
});

Custom Fetch

Inject a custom fetch for environments that need it, or to add middleware (logging, retries):

import { AuthPIAdmin } from "@authpi/admin";

const admin = new AuthPIAdmin({
  apiKey: { id: "key_xxx", secret: "your_key_secret" },
  accountId: "acc_xxx",
  fetch: async (url, init) => {
    console.log(`${init?.method} ${url}`);
    return globalThis.fetch(url, init);
  },
});

API Reference

AuthPIAdmin

| Property | Type | Description | |----------|------|-------------| | issuers | IssuersResource | Account-level issuers | | webhooks | WebhooksResource | Account-level webhooks | | events | EventsResource | Account-level events | | notes | NotesResource | Account-level notes | | accounts | AccountsResource | Account management | | apiKeys | ApiKeysResource | Account-level API keys | | domains | DomainsResource | Account-level domains | | tokens | TokensResource | Account-level tokens |

| Method | Returns | Description | |--------|---------|-------------| | issuer(id) | IssuerScope | Scope into a specific issuer | | webhook(id) | WebhookScope | Scope into a specific webhook |

Resource Methods

All resources expose a subset of these standard methods:

| Method | Description | |--------|-------------| | list(options?) | List resources (returns Page<T>) | | listAll(options?) | Auto-paginating iterator (yields individual items) | | get(id, options?) | Get a single resource | | create(body, options?) | Create a resource | | update(id, body, options?) | Update a resource | | delete(id, options?) | Delete a resource |

Request Options

All methods accept an options object with:

| Option | Type | Description | |--------|------|-------------| | timeout | number | Per-request timeout in milliseconds | | headers | Record<string, string> | Additional HTTP headers | | ifMatch | string | ETag for conditional update/delete | | idempotencyKey | string | Idempotency key for create/update |

License

MIT