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

@wocha/sdk

v0.1.0

Published

TypeScript SDK for the Wocha authentication and authorisation platform

Readme

@wocha/sdk

npm version npm downloads TypeScript License

TypeScript SDK for the Wocha authentication and authorisation platform.

Installation

npm install @wocha/sdk

Quick Start

import { WochaClient } from '@wocha/sdk';

const wocha = new WochaClient({
  tenant: 'acme',
  apiKey: process.env.WOCHA_API_KEY!,
});

const { data: users } = await wocha.users.list({ page_size: 25 });
console.log(users);

Configuration

Create a WochaClient with one of the following configuration options.

tenant vs baseUrl

Use tenant when connecting to Wocha Cloud. The SDK resolves the API base URL automatically:

new WochaClient({ tenant: 'acme', apiKey: '...' });
// → https://acme.api.wocha.ai/v1

Use baseUrl for self-hosted installations or custom domains. Point it at wherever the Customer API is reachable — the SDK appends resource paths such as /users and /applications directly to this base. On the platform API, /v1/* is rewritten to /api/v1/customer/*, so a typical self-hosted value is:

new WochaClient({
  baseUrl: 'https://your-domain.com/v1',
  apiKey: '...',
});

Do not include /api/v1/customer in baseUrl unless that is literally how your deployment exposes the Customer API.

You must provide either tenant or baseUrl (not both required, but at least one).

apiKey

Pass a management API key (wocha_mgmt_...) or an OAuth 2.0 access token with the required scopes. Management keys are recommended for server-side integrations.

timeout and maxRetries

| Option | Default | Description | |--------|---------|-------------| | timeout | 10000 | Request timeout in milliseconds | | maxRetries | 3 | Retries on 429 and 5xx responses with exponential backoff |

Debug mode and custom hooks

Enable request/response logging or attach custom hooks:

const wocha = new WochaClient({
  tenant: 'acme',
  apiKey: process.env.WOCHA_API_KEY!,
  debug: true,
  onRequest: (method, url, headers) => {
    console.log(`${method} ${url}`);
  },
  onResponse: (status, headers, body) => {
    console.log(`Response ${status}`, body);
  },
});

Hooks are invoked once per logical request (not on retries).

tracePropagator for OpenTelemetry

Inject trace context into outbound requests:

import { propagation, context } from '@opentelemetry/api';

const wocha = new WochaClient({
  tenant: 'acme',
  apiKey: process.env.WOCHA_API_KEY!,
  tracePropagator: (headers) => {
    propagation.inject(context.active(), headers, {
      set: (carrier, key, value) => { carrier[key] = value; },
    });
    return headers;
  },
});

When @opentelemetry/api is installed, the client also auto-detects OTel and injects traceparent headers without a custom propagator.

Resources

All resources are accessed as properties on the client: wocha.users, wocha.sessions, etc.

Every resource method accepts an optional final requestOptions?: PerRequestOptions argument for per-request overrides (timeout, abort signal, and extra headers). See the PerRequestOptions type exported from the package.

For endpoints not yet covered by a resource helper, use WochaClient.request() (or requestText() for non-JSON responses) as an escape hatch:

const custom = await wocha.request<{ foo: string }>('/custom-endpoint', { method: 'GET' });

Availability markers:

| Marker | Meaning | |--------|---------| | Stable | Supported in the Customer API | | Beta | Available but may change | | Coming soon | Planned SDK support; not yet available |

Users

| Method | Signature | Availability | |--------|-----------|--------------| | list | (options?: ListOptions): Promise<PaginatedResponse<User>> | Stable | | listAll | (options?: ListOptions): AsyncGenerator<User> | Stable | | get | (id: string): Promise<User> | Stable | | create | (data: CreateUserInput): Promise<User> | Stable | | update | (id: string, data: UpdateUserInput): Promise<User> | Stable | | delete | (id: string): Promise<void> | Stable | | sendRecovery | (id: string): Promise<{ recovery_link?: string }> | Stable |

const user = await wocha.users.create({
  email: '[email protected]',
  traits: { name: { first: 'Alice', last: 'Smith' } },
  metadata_admin: { org_id: 'org_abc123' },
});

Sessions

| Method | Signature | Availability | |--------|-----------|--------------| | list | (userId: string, options?: ListOptions): Promise<PaginatedResponse<Session>> | Stable | | listAll | (userId: string, options?: ListOptions): AsyncGenerator<Session> | Stable | | revokeAll | (userId: string): Promise<void> | Stable | | listActive | (options?: ListOptions): Promise<PaginatedResponse<Session>> | Stable | | purgeAll | (): Promise<void> | Stable | | revoke | (userId: string, sessionId: string): Promise<void> | Stable |

await wocha.sessions.revokeAll('usr_abc');

Organisations

| Method | Signature | Availability | |--------|-----------|--------------| | list | (options?: ListOptions): Promise<PaginatedResponse<Organisation>> | Stable | | listAll | (options?: ListOptions): AsyncGenerator<Organisation> | Stable | | get | (id: string): Promise<Organisation> | Stable | | create | (data: { slug, display_name, parent_id?, metadata? }): Promise<Organisation> | Stable | | update | (id: string, data: Partial<...>): Promise<Organisation> | Stable | | delete | (id: string): Promise<void> | Stable | | listMembers | (orgId: string, options?: ListOptions): Promise<PaginatedResponse<OrgMember>> | Stable | | addMember | (orgId: string, data: { identity_id, role }): Promise<OrgMember> | Stable | | updateMember | (orgId: string, memberId: string, data: { role }): Promise<OrgMember> | Stable | | removeMember | (orgId: string, memberId: string): Promise<void> | Stable | | getTree | (orgId: string): Promise<{ tree: Organisation[] }> | Stable |

const org = await wocha.organisations.create({
  slug: 'engineering',
  display_name: 'Engineering',
});

Applications

| Method | Signature | Availability | |--------|-----------|--------------| | list | (options?: ListOptions): Promise<PaginatedResponse<Application>> | Stable | | listAll | (options?: ListOptions): AsyncGenerator<Application> | Stable | | get | (id: string): Promise<Application> | Stable | | create | (data: CreateApplicationInput): Promise<Application & { client_secret?: string }> | Stable | | update | (id: string, data: Partial<CreateApplicationInput>): Promise<Application> | Stable | | delete | (id: string): Promise<void> | Stable | | rotateSecret | (id: string): Promise<{ client_secret: string }> | Stable |

const app = await wocha.applications.create({
  client_name: 'My App',
  grant_types: ['authorization_code'],
  redirect_uris: ['https://app.example.com/callback'],
});

Products

M2M-only product registry — requires the platform:provision scope. Not accessible with customer management API keys.

| Method | Signature | Availability | |--------|-----------|--------------| | list | (options?: { status?, owner_tenant_id? }): Promise<ProductListResponse> | Stable | | listAll | (options?: { status?, owner_tenant_id? }): AsyncGenerator<Product> | Stable | | get | (idOrSlug: string): Promise<{ product: Product }> | Stable | | create | (data: CreateProductRequest): Promise<{ product: Product }> | Stable | | update | (id: string, data: UpdateProductRequest): Promise<{ product: Product }> | Stable | | delete | (id: string): Promise<{ deleted: boolean }> | Stable | | listScopes | (): Promise<ProductScopesResponse> | Stable |

const { products } = await wocha.products.list({ status: 'active' });

const { product } = await wocha.products.create({
  slug: 'docs',
  display_name: 'Documentation',
  namespace: 'docs',
  scopes: ['docs:read'],
});

const { scopes } = await wocha.products.listScopes();

Permissions

| Method | Signature | Availability | |--------|-----------|--------------| | check | (request: PermissionCheckRequest): Promise<PermissionCheckResponse> | Stable | | listRelationships | (filters: ListRelationshipsFilters): Promise<{ data: Relationship[] }> | Stable | | writeRelationship | (relationship: Relationship): Promise<void> | Stable | | deleteRelationship | (relationship: Relationship): Promise<void> | Stable | | lookup | (request: LookupRequest): Promise<LookupResponse> | Stable | | expand | (request: ExpandRequest): Promise<ExpandResponse> | Stable | | getSchema | (): Promise<{ schema: string }> | Stable | | updateSchema | (schema: string): Promise<{ schema: string }> | Stable |

const { allowed } = await wocha.permissions.check({
  resource: { type: 'document', id: 'doc-123' },
  permission: 'edit',
  subject: { type: 'user', id: 'usr_abc' },
});

Webhooks

| Method | Signature | Availability | |--------|-----------|--------------| | list | (options?: ListOptions): Promise<PaginatedResponse<WebhookSubscription>> | Stable | | listAll | (options?: ListOptions): AsyncGenerator<WebhookSubscription> | Stable | | get | (id: string): Promise<WebhookSubscription> | Stable | | create | (data: CreateWebhookInput): Promise<WebhookSubscription> | Stable | | update | (id: string, data: UpdateWebhookInput): Promise<WebhookSubscription> | Stable | | delete | (id: string): Promise<void> | Stable | | listDeliveries | (webhookId: string, options?: ListOptions): Promise<PaginatedResponse<WebhookDelivery>> | Stable |

const webhook = await wocha.webhooks.create({
  url: 'https://app.example.com/webhooks/wocha',
  events: ['user.created', 'user.deleted'],
});

Billing

| Method | Signature | Availability | |--------|-----------|--------------| | getSubscription | (): Promise<Subscription> | Stable | | getUsage | (): Promise<UsageMetrics> | Stable | | createPortalSession | (): Promise<PortalSession> | Stable |

const { url } = await wocha.billing.createPortalSession();
// Redirect the user to url

Branding

Theme methods use GET/PUT /branding. Template and domain helpers use separate paths under /branding/templates and /branding/domain.

| Method | Signature | Endpoint | Availability | |--------|-----------|----------|--------------| | getTheme | (): Promise<Theme> | GET /branding | Beta | | updateTheme | (data: Theme): Promise<Theme> | PUT /branding | Beta | | listTemplates | (): Promise<{ data: EmailTemplate[] }> | GET /branding/templates | Beta | | updateTemplate | (type: string, data: Omit<EmailTemplate, 'type'>): Promise<EmailTemplate> | PUT /branding/templates/:type | Beta | | getDomain | (): Promise<CustomDomain> | GET /branding/domain | Beta | | setDomain | (data: { domain: string }): Promise<CustomDomain> | PUT /branding/domain | Beta | | removeDomain | (): Promise<void> | DELETE /branding/domain | Beta |

await wocha.branding.updateTheme({
  primary_colour: '#0066cc',
  company_name: 'Acme Corp',
});

Logs

Auth and audit logs share a single endpoint: GET /logs?type=auth|audit. The SDK exposes separate helpers that set the type query parameter for you.

| Method | Signature | Endpoint | Availability | |--------|-----------|----------|--------------| | listAuthLogs | (filters?: LogFilter): Promise<PaginatedResponse<AuthLogEntry>> | GET /logs?type=auth | Beta | | listAuditLogs | (filters?: LogFilter): Promise<PaginatedResponse<AuditLogEntry>> | GET /logs?type=audit | Beta | | exportLogs | (options?: LogFilter & { format?: 'csv' \| 'json' }): Promise<string> | GET /logs/export | Beta |

const { data: entries } = await wocha.logs.listAuthLogs({
  since: '2026-01-01T00:00:00Z',
  page_size: 50,
});

Connections

| Method | Signature | Availability | |--------|-----------|--------------| | listSocial | (): Promise<{ data: SocialProvider[] }> | Stable | | configureSocial | (provider: string, config: SocialProviderConfig): Promise<SocialProvider> | Stable | | disableSocial | (provider: string): Promise<void> | Stable | | listEnterprise | (): Promise<{ data: EnterpriseConnection[] }> | Stable | | createEnterprise | (data: CreateEnterpriseInput): Promise<EnterpriseConnection> | Stable | | deleteEnterprise | (id: string): Promise<void> | Stable | | getScimConfig | (): Promise<ScimConfig> | Stable | | configureScim | (data: ScimConfig): Promise<ScimConfig> | Stable |

await wocha.connections.configureSocial('google', {
  client_id: process.env.GOOGLE_CLIENT_ID!,
  client_secret: process.env.GOOGLE_CLIENT_SECRET!,
  enabled: true,
});

Actions

| Method | Signature | Availability | |--------|-----------|--------------| | list | (options?: ListOptions): Promise<PaginatedResponse<Action>> | Stable | | listAll | (options?: ListOptions): AsyncGenerator<Action> | Stable | | get | (id: string): Promise<Action> | Stable | | create | (data: CreateActionInput): Promise<Action> | Stable | | update | (id: string, data: Partial<CreateActionInput>): Promise<Action> | Stable | | delete | (id: string): Promise<void> | Stable | | deploy | (id: string): Promise<ActionDeployment> | Stable | | test | (id: string, payload?: Record<string, unknown>): Promise<ActionTestResult> | Stable |

const action = await wocha.actions.create({
  name: 'Post-registration hook',
  trigger: 'user.created',
  code: 'export default async (event) => { /* ... */ }',
});
await wocha.actions.deploy(action.id);

Environments

Manage tenant environments, promote configuration between them, and list environment-scoped API keys.

| Method | Signature | Availability | |--------|-----------|--------------| | list | (): Promise<EnvironmentList> | Stable | | get | (id: string): Promise<Environment> | Stable | | create | (data: CreateEnvironmentInput): Promise<Environment> | Stable | | update | (id: string, data: UpdateEnvironmentInput): Promise<Environment> | Stable | | delete | (id: string): Promise<void> | Stable | | promote | (id: string, data: PromoteEnvironmentInput): Promise<PromoteEnvironmentResult> | Stable | | listApiKeys | (id: string, options?: ListOptions): Promise<PaginatedResponse<ApiKey>> | Stable |

const { data: environments } = await wocha.environments.list();

await wocha.environments.promote('env_production', {
  source_slug: 'staging',
  dry_run: true,
});

Pagination

List endpoints return a PaginatedResponse<T> with data, next_page_token, and has_more.

Manual pagination with list()

let pageToken: string | undefined;

do {
  const page = await wocha.users.list({ page_size: 25, page_token: pageToken });
  for (const user of page.data) {
    console.log(user.email);
  }
  pageToken = page.next_page_token;
} while (pageToken);

Auto-pagination with listAll()

for await (const user of wocha.users.listAll({ page_size: 25 })) {
  console.log(user.email);
}

You can also use the client-level paginate() helper for custom list functions:

for await (const user of wocha.paginate(wocha.users.list.bind(wocha.users), { page_size: 25 })) {
  console.log(user.email);
}

Error Handling

All API errors throw typed subclasses of GreetError. Use instanceof checks or the isGreetError() type guard to handle specific cases:

import {
  isGreetError,
  GreetBadRequestError,
  WochaConflictError,
  WochaNotFoundError,
  WochaRateLimitError,
  WochaValidationError,
  WochaAuthenticationError,
  GreetPermissionError,
  GreetServerError,
} from '@wocha/sdk';

try {
  await wocha.users.get('nonexistent');
} catch (err) {
  if (isGreetError(err)) {
    console.log(`Error ${err.errorCode} (${err.statusCode}) — request ${err.requestId}`);
  }
  if (err instanceof GreetBadRequestError) {
    console.log('Invalid request');
  }
  if (err instanceof WochaConflictError) {
    console.log('Resource conflict');
  }
  if (err instanceof WochaNotFoundError) {
    console.log('User not found');
  }
  if (err instanceof WochaRateLimitError) {
    console.log(`Rate limited — retry after ${err.retryAfter}s`);
  }
  if (err instanceof WochaValidationError) {
    console.log(err.fieldErrors);
  }
}

Every error includes errorCode, statusCode, requestId, and optionally docsUrl for support debugging.

Webhook Verification

Webhook helpers are split across two entry points:

| Export | From | Purpose | |--------|------|---------| | parseWebhookEvent, constructWebhookEvent | @wocha/sdk | Parse (and optionally verify + parse) event payloads | | verifyWebhookSignature, parseWebhookEvent, constructWebhookEvent | @wocha/sdk/webhooks | Full webhook module including signature verification |

Signature verification (verifyWebhookSignature) is only exported from the /webhooks subpath. The main package exports parsing helpers and all webhook event types, but not verifyWebhookSignature.

import { verifyWebhookSignature, parseWebhookEvent } from '@wocha/sdk/webhooks';

export async function POST(req: Request) {
  const payload = await req.text();
  const signature = req.headers.get('X-Wocha-Signature') ?? '';

  if (!verifyWebhookSignature(payload, signature, process.env.WOCHA_WEBHOOK_SECRET!)) {
    return new Response('Invalid signature', { status: 401 });
  }

  const event = parseWebhookEvent(payload);
  if (event.event_type === 'user.created') {
    console.log('New user:', event.data.email);
  }

  return new Response('OK');
}

Typed event payloads are exported for all supported event types (UserCreatedEvent, SessionRevokedEvent, etc.).

Framework Adapters

The core SDK is framework-agnostic. Use these packages for frontend and full-stack integrations:

| Package | Description | |---------|-------------| | @wocha/nextjs | API route handler, middleware, server helpers | | @wocha/react | Provider, hooks, and headless UI components |

Self-Hosted

For self-hosted Wocha installations, set baseUrl to the Customer API root on your deployment (typically https://your-domain.com/v1 when using the platform API rewrite):

const wocha = new WochaClient({
  baseUrl: 'https://your-domain.com/v1',
  apiKey: process.env.WOCHA_API_KEY!,
});

The URL should be the prefix the SDK appends resource paths to (e.g. /users, /applications). For Wocha Cloud, prefer tenant instead — it resolves this automatically.

License

Apache-2.0