@wocha/sdk
v0.1.0
Published
TypeScript SDK for the Wocha authentication and authorisation platform
Maintainers
Readme
@wocha/sdk
TypeScript SDK for the Wocha authentication and authorisation platform.
Installation
npm install @wocha/sdkQuick 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/v1Use 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 urlBranding
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
