A TypeScript client library for the Pax8 API with full type safety, automatic token management, and zero runtime dependencies.
- Requirements
- Installation
- Quick Start
- Configuration
- Authentication
- API Reference
- Pagination
- Error Handling
- TypeScript
- Rate Limiting
- Links
- License
Node.js 22 or higher is required. This package uses native fetch API available in Node.js 22+ and has zero runtime dependencies.
Install the package using your preferred package manager:
# npm
npm install @imperian-systems/pax8-api
# yarn
yarn add @imperian-systems/pax8-api
# pnpm
pnpm add @imperian-systems/pax8-apiGet started with your first API call in minutes:
import { Pax8Client } from '@imperian-systems/pax8-api';
const client = new Pax8Client({
clientId: 'your-client-id',
clientSecret: 'your-client-secret'
});
// List companies with cursor-based pagination
const result = await client.companies.list({ limit: 50, status: 'active' });
console.log(result.items); // Company[]
console.log(result.page.nextPageToken); // Cursor for next page
// Get a specific company
const company = await client.companies.get('comp-123');
console.log(company.displayName);The Pax8Client constructor accepts a configuration object with the following options:
| Option | Type | Default | Description |
|---|---|---|---|
clientId |
string |
required | OAuth client ID from Pax8 Integrations Hub |
clientSecret |
string |
required | OAuth client secret from Pax8 Integrations Hub |
baseUrl |
string |
https://api.pax8.com/v1 |
API base URL |
tokenUrl |
string |
https://token-manager.pax8.com/oauth/token |
OAuth token endpoint |
audience |
string |
https://api.pax8.com |
OAuth audience |
retryAttempts |
number |
3 |
Maximum number of retry attempts for failed requests |
retryDelay |
number |
1000 |
Initial retry delay in milliseconds (uses exponential backoff) |
timeout |
number |
30000 |
Request timeout in milliseconds |
autoRefresh |
boolean |
true |
Automatically refresh tokens before expiry |
const client = new Pax8Client({
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
retryAttempts: 5,
retryDelay: 2000,
timeout: 60000
});This package uses OAuth 2.0 Client Credentials flow for authentication with the Pax8 API.
- Log in to the Pax8 Integrations Hub
- Navigate to your application or create a new one
- Copy your Client ID and Client Secret
- Pass these credentials directly to the
Pax8Clientconstructor
- The client automatically obtains an access token when you make your first API call
- Tokens are valid for 24 hours (86400 seconds)
- With
autoRefresh: true(default), tokens are automatically refreshed before expiry - You don't need to manually manage token acquisition or refresh
If you need more control over token management:
const client = new Pax8Client({
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
autoRefresh: false // Disable automatic refresh
});
// Manually refresh token when needed
await client.refreshToken();This section documents all available API resources and their methods.
Manage customer organizations served through Pax8. This API uses page-based pagination with page (0-based) and size (default 10, max 200).
// List companies with page-based pagination
const result = await client.companies.list({
page: 0, // Optional: page number (default 0)
size: 25, // Optional: page size (default 10, max 200)
status: 'Active', // Optional: filter by status
country: 'US', // Optional: filter by country
city: 'Denver', // Optional: filter by city
sort: 'name,desc' // Optional: field,direction (default asc)
});
console.log(result.content); // Company[]
console.log(result.page.size); // e.g., 25
console.log(result.page.totalPages); // Total pages available
// Paginate through results
let pageNumber = result.page.number + 1;
while (pageNumber < result.page.totalPages) {
const nextPage = await client.companies.list({ page: pageNumber, size: result.page.size });
console.log(nextPage.content);
pageNumber = nextPage.page.number + 1;
}
// Get a specific company by ID
const company = await client.companies.get('comp-123');
console.log(company.id);
console.log(company.name);
console.log(company.status); // 'Active' | 'Inactive' | 'Deleted'
console.log(company.address);
// Create a new company
const created = await client.companies.create({
name: 'VFC Network, Inc',
address: {
street: '48918 Liberty Lane Ave',
city: 'Denver',
stateOrProvince: 'CO',
postalCode: '80210',
country: 'US'
},
phone: '123-456-5555',
website: 'https://vfc-network-inc-liberty.com',
externalId: 'A123',
selfServiceAllowed: false,
billOnBehalfOfEnabled: false,
orderApprovalRequired: false
});
// Update an existing company (partial)
const updated = await client.companies.update('comp-123', {
phone: '555-000-1234',
selfServiceAllowed: true,
address: { postalCode: '80211' }
});
console.log(updated.phone);
// Search companies by name or domain
const searchResults = await client.companies.search({
query: 'acme', // Required: 2-256 characters
page: 0, // Optional: page number
size: 25 // Optional: page size
});
for (const company of searchResults.content) {
console.log(`${company.name} - ${company.status}`);
}interface ListCompaniesParams {
page?: number; // 0-based page number
size?: number; // 1-200, default 10
sort?: 'name' | 'name,desc' | 'city' | 'city,desc' | 'country' | 'country,desc' | 'stateOrProvince' | 'stateOrProvince,desc' | 'postalCode' | 'postalCode,desc';
city?: string;
country?: string;
stateOrProvince?: string;
postalCode?: string;
selfServiceAllowed?: boolean;
billOnBehalfOfEnabled?: boolean;
orderApprovalRequired?: boolean;
status?: 'Active' | 'Inactive' | 'Deleted';
}
interface SearchCompaniesParams {
query: string; // 2-256 characters
page?: number; // 0-based page number
size?: number; // 1-200, default 10
}
interface CreateCompanyRequest {
name: string;
address: {
street?: string;
street2?: string;
city?: string;
stateOrProvince?: string;
postalCode?: string;
country?: string;
};
phone?: string;
website?: string;
externalId?: string;
billOnBehalfOfEnabled: boolean;
selfServiceAllowed: boolean;
orderApprovalRequired: boolean;
}
interface UpdateCompanyRequest {
name?: string;
address?: {
street?: string;
street2?: string;
city?: string;
stateOrProvince?: string;
postalCode?: string;
country?: string;
};
phone?: string;
website?: string;
externalId?: string;
billOnBehalfOfEnabled?: boolean;
selfServiceAllowed?: boolean;
orderApprovalRequired?: boolean;
}
interface Company {
id: string;
name: string;
address: {
street?: string;
street2?: string;
city?: string;
stateOrProvince?: string;
postalCode?: string;
country?: string;
};
phone?: string;
website?: string;
externalId?: string;
billOnBehalfOfEnabled: boolean;
selfServiceAllowed: boolean;
orderApprovalRequired: boolean;
status: 'Active' | 'Inactive' | 'Deleted';
updatedDate?: string; // ISO 8601
}
interface CompanyListResponse {
content: Company[];
page: {
size: number;
totalElements: number;
totalPages: number;
number: number;
};
}Manage people associated with companies. This API uses page-based pagination (default size: 10, max: 200).
// List contacts for a company with page-based pagination
const result = await client.contacts.list({
companyId: 'comp-123',
page: 0, // Optional: page number (default 0)
size: 50 // Optional: page size (default 10, max 200)
});
console.log(result.content); // Contact[]
console.log(result.page.size); // 50
console.log(result.page.totalElements); // Total contacts
console.log(result.page.totalPages); // Total pages
console.log(result.page.number); // Current page (0-indexed)
// Paginate through all contacts
let currentPage = 0;
const allContacts: Contact[] = [];
while (currentPage < result.page.totalPages) {
const page = await client.contacts.list({
companyId: 'comp-123',
page: currentPage,
size: 100
});
allContacts.push(...page.content);
currentPage++;
}
// Get a specific contact by ID
const contact = await client.contacts.get('comp-123', 'contact-456');
console.log(contact.firstName, contact.lastName);
console.log(contact.email, contact.phone);
// Check contact types (Admin, Billing, Technical)
for (const type of contact.types ?? []) {
console.log(`${type.type}: primary=${type.primary}`);
}
// Create a new contact
const newContact = await client.contacts.create('comp-123', {
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com',
phone: '+1-555-0123',
types: [
{ type: 'Admin', primary: true },
{ type: 'Billing', primary: false }
]
});
console.log(`Created contact: ${newContact.id}`);
// Update an existing contact (partial update)
const updated = await client.contacts.update('comp-123', 'contact-456', {
phone: '+1-555-9999',
types: [
{ type: 'Admin', primary: true },
{ type: 'Technical', primary: true }
]
});
// Delete a contact
await client.contacts.delete('comp-123', 'contact-456');
console.log('Contact deleted');interface ListContactsParams {
companyId: string; // Required: company to list contacts for
page?: number; // Optional: 0-indexed page number (default 0)
size?: number; // Optional: page size (default 10, max 200)
}
interface CreateContactRequest {
firstName: string; // Required
lastName: string; // Required
email: string; // Required: valid email format
phone?: string; // Optional
types?: ContactType[]; // Optional: contact type assignments
}
interface UpdateContactRequest {
firstName?: string; // Optional: all fields are optional for partial updates
lastName?: string;
email?: string; // Must be valid email format if provided
phone?: string;
types?: ContactType[]; // Replaces entire types array if provided
}
interface ContactType {
type: 'Admin' | 'Billing' | 'Technical';
primary: boolean; // Whether this is primary contact for this type
}
interface Contact {
id: string;
firstName: string;
lastName: string;
email: string;
phone?: string;
types?: ContactType[];
createdDate: string; // ISO 8601
}
interface ContactListResponse {
content: Contact[];
page: {
size: number; // Page size used
totalElements: number; // Total contacts across all pages
totalPages: number; // Total pages
number: number; // Current page (0-indexed)
};
}Contacts support a type-based primary system for Admin, Billing, and Technical roles:
- A contact can have multiple types (e.g., both Admin and Billing)
- Each type can be marked as primary or non-primary
- A contact can be primary for multiple types simultaneously
- When setting a contact as primary for a type, the previous primary for that type is automatically demoted
// Create a contact that is primary for both Admin and Billing
const contact = await client.contacts.create('comp-123', {
firstName: 'Jane',
lastName: 'Smith',
email: 'jane@example.com',
types: [
{ type: 'Admin', primary: true },
{ type: 'Billing', primary: true },
{ type: 'Technical', primary: false }
]
});
// Setting another contact as primary Admin will auto-demote Jane's Admin primary status
const newAdmin = await client.contacts.create('comp-123', {
firstName: 'Bob',
lastName: 'Johnson',
email: 'bob@example.com',
types: [{ type: 'Admin', primary: true }]
});
// Jane is still primary for Billing, but Bob is now primary for AdminBrowse and query the Pax8 product catalog. This API uses page-based pagination (default size: 10, max: 200).
// List products with default pagination
const result = await client.products.list();
console.log(result.content); // Product[]
console.log(result.page.size); // 10
console.log(result.page.totalElements); // Total products
console.log(result.page.totalPages); // Total pages
console.log(result.page.number); // Current page (0-indexed)
// Filter products by vendor
const msProducts = await client.products.list({
vendorName: 'Microsoft',
size: 25,
sort: 'name'
});
// Search products
const searchResults = await client.products.list({
search: 'Office 365',
size: 50
});
// Get a specific product by ID
const product = await client.products.get('a1b2c3d4-e5f6-7890-abcd-ef1234567890');
console.log(product.name, product.vendorName);
console.log(product.description);
// Get provisioning details for a product
const provisioning = await client.products.getProvisioningDetails('a1b2c3d4-e5f6-7890-abcd-ef1234567890');
for (const field of provisioning.content) {
console.log(`${field.label} (${field.valueType}): ${field.description}`);
}
// Get product dependencies
const deps = await client.products.getDependencies('a1b2c3d4-e5f6-7890-abcd-ef1234567890');
if (deps.productDependencies) {
for (const dep of deps.productDependencies) {
console.log(`Requires: ${dep.name}`);
}
}
// Get pricing information
const pricing = await client.products.getPricing('a1b2c3d4-e5f6-7890-abcd-ef1234567890');
for (const price of pricing.content) {
console.log(`${price.billingTerm}: $${price.rates[0].partnerBuyRate}`);
}
// Get company-specific pricing
const companyPricing = await client.products.getPricing(
'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
{ companyId: 'f9e8d7c6-b5a4-3210-fedc-ba9876543210' }
);interface ListProductsOptions {
page?: number; // Optional: 0-indexed page number (default 0)
size?: number; // Optional: page size (default 10, max 200)
sort?: 'name' | 'vendor'; // Optional: sort field
vendorName?: string; // Optional: filter by vendor name
search?: string; // Optional: search across name, vendor, SKU, ID
}
interface Product {
id: string;
name: string;
vendorName: string;
shortDescription?: string;
sku?: string;
vendorSku?: string;
altVendorSku?: string; // Deprecated: Microsoft legacy SKU
requiresCommitment?: boolean;
}
interface ProductDetail extends Product {
description?: string; // Long product description
}
interface ProvisioningDetail {
label: string;
key: string;
description?: string;
valueType: 'Input' | 'Single-Value' | 'Multi-Value';
possibleValues?: string[];
}
interface Dependencies {
commitmentDependencies?: Commitment[];
productDependencies?: ProductDependency[];
}
interface Pricing {
productId: string;
productName: string;
billingTerm: 'Monthly' | 'Annual' | '2-Year' | '3-Year' | 'One-Time' | 'Trial' | 'Activation';
commitmentTerm?: string;
commitmentTermInMonths?: number;
type: 'Flat' | 'Volume' | 'Tiered' | 'Mark-Up';
unitOfMeasurement?: string;
rates: Rate[];
}
interface Rate {
partnerBuyRate: number;
suggestedRetailPrice: number;
startQuantityRange?: number;
endQuantityRange?: number;
chargeType: 'Per Unit' | 'Flat Rate';
}
interface ProductListResponse {
content: Product[];
page: {
size: number; // Page size used
totalElements: number; // Total products across all pages
totalPages: number; // Total pages
number: number; // Current page (0-indexed)
};
}Manage product purchase records with line items, provisioning details, and order tracking.
// List orders with pagination and optional companyId filter
client.orders.list(options?: ListOrdersOptions): Promise<OrderListResponse>
// Get a specific order by ID
client.orders.get(orderId: string): Promise<Order>
// Create a new order with line items
client.orders.create(request: CreateOrderRequest, options?: { isMock?: boolean }): Promise<Order>// List orders for a company
const orders = await client.orders.list({
companyId: 'company-id',
page: 0,
size: 20
});
console.log(orders.content.length);
console.log(orders.page.totalElements);
// Get order details
const order = await client.orders.get('order-id');
console.log(order.companyId);
console.log(order.lineItems[0]?.productId);
// Create an order with billing term and provisioning details
const created = await client.orders.create({
companyId: 'company-id',
orderedBy: 'Pax8 Partner',
orderedByUserEmail: 'agent@example.com',
lineItems: [
{
productId: 'product-id',
quantity: 5,
billingTerm: 'Monthly',
lineItemNumber: 1,
provisioningDetails: [
{ key: 'tenantDomain', values: ['contoso.onmicrosoft.com'] }
]
}
]
});
console.log(created.id);
console.log(created.lineItems[0]?.subscriptionId); // may be null until provisioned
// Validate order without creating (mock mode)
await client.orders.create(
{
companyId: 'company-id',
lineItems: [
{ productId: 'product-id', quantity: 1, billingTerm: 'Annual', lineItemNumber: 1 }
]
},
{ isMock: true }
);Notes:
- Pagination is page-based (
page,sizeup to 200) and supports optionalcompanyIdfilter billingTermis required per line item; includecommitmentTermIdwhen the product requires commitmentprovisioningDetailsusevalues(array) for inputs; these are write-only and may not be echoed in responsessubscriptionIdon line items may be null until the order provisions subscriptions
Manage active service agreements. The Subscriptions API uses page-based pagination with page (0-based) and size (default 10, max 200).
// List subscriptions with optional filters
const result = await client.subscriptions.list({
page: 0, // Optional: page number (default 0)
size: 25, // Optional: page size (default 10, max 200)
companyId: 'comp-123', // Optional: filter by company
productId: 'prod-456', // Optional: filter by product
status: 'Active', // Optional: filter by status
sort: 'productId,asc' // Optional: sort order
});
console.log(result.content); // Subscription[]
console.log(result.page.totalElements); // Total subscriptions matching filters
console.log(result.page.totalPages); // Total pages available
// Get a specific subscription by ID
const subscription = await client.subscriptions.get('sub-123');
console.log(subscription.quantity);
console.log(subscription.status);
console.log(subscription.billingTerm);
// Update subscription quantity (only quantity modifications supported)
const updated = await client.subscriptions.update('sub-123', {
quantity: 15
});
console.log(updated.quantity); // 15
// Cancel a subscription immediately
await client.subscriptions.cancel('sub-123');
// Schedule cancellation on a future billing date
await client.subscriptions.cancel('sub-123', {
billingDate: '2025-12-31' // ISO 8601 date string
});
// Get subscription change history
const history = await client.subscriptions.getHistory('sub-123');
for (const entry of history) {
console.log(`${entry.action} on ${entry.date}`);
if (entry.previousQuantity !== null && entry.newQuantity !== null) {
console.log(` Quantity: ${entry.previousQuantity} → ${entry.newQuantity}`);
}
}interface ListSubscriptionsOptions {
page?: number; // Optional: 0-indexed page number (default 0)
size?: number; // Optional: page size (default 10, max 200)
sort?: string; // Optional: sort field and direction (e.g., 'productId,asc')
companyId?: string; // Optional: filter by company UUID
productId?: string; // Optional: filter by product UUID
status?: SubscriptionStatus; // Optional: filter by status
}
type SubscriptionStatus =
| 'Active' // Active subscription
| 'Cancelled' // Cancelled subscription
| 'PendingManual' // Pending manual provisioning
| 'PendingAutomated' // Pending automated provisioning
| 'PendingCancel' // Scheduled for cancellation
| 'WaitingForDetails' // Awaiting provisioning details
| 'Trial' // Trial subscription
| 'Converted' // Converted from trial
| 'PendingActivation' // Pending activation
| 'Activated'; // Recently activated
interface Subscription {
id: string;
companyId: string;
productId: string;
quantity: number;
status: SubscriptionStatus;
price: number;
billingTerm: BillingTerm; // 'Monthly' | 'Annual' | '2-Year' | '3-Year' | 'One-Time' | 'Trial' | 'Activation'
billingStart: string; // ISO 8601
startDate: string; // ISO 8601
endDate: string | null; // ISO 8601, nullable
createdDate: string; // ISO 8601
commitmentTermId?: string | null;
commitmentTermMonths?: number | null;
commitmentEndDate?: string | null; // ISO 8601, nullable
}
interface UpdateSubscriptionRequest {
quantity: number; // Required: new quantity (must be positive)
}
interface CancelOptions {
billingDate?: string; // Optional: ISO 8601 date for future-effective cancellation
}
interface SubscriptionHistory {
id: string;
subscriptionId: string;
action: string; // e.g., 'QuantityUpdate', 'StatusChange', 'Created'
date: string; // ISO 8601
userId?: string | null;
previousQuantity?: number | null;
newQuantity?: number | null;
}- 404 Not Found: Subscription ID does not exist
- 422 Unprocessable Entity: Invalid quantity (must be positive) or invalid billingDate format
Access billing records.
// List invoices with optional filters
client.invoices.list(options?: ListInvoicesOptions): Promise<Page<Invoice>>
// Get a specific invoice by ID
client.invoices.get(id: string): Promise<Invoice>
// List invoice line items
client.invoices.listItems(id: string, options?: ListItemsOptions): Promise<Page<InvoiceItem>>
// List draft invoice items
client.invoices.listDraftItems(companyId: string, options?: ListItemsOptions): Promise<Page<InvoiceItem>>// List invoices for a company
const invoices = await client.invoices.list({
companyId: 'company-id',
size: 20
});
// Get invoice details
const invoice = await client.invoices.get('invoice-id');
// Get invoice line items
const items = await client.invoices.listItems('invoice-id', {
page: 0,
size: 100
});Access usage-based billing data for subscriptions.
// List usage summaries for a subscription
client.subscriptions.listUsageSummaries(subscriptionId: string, options?: ListUsageSummariesOptions): Promise<Page<UsageSummary>>
// Get a specific usage summary by ID
client.usageSummaries.get(id: string): Promise<UsageSummary>
// List usage lines for a summary (requires usageDate)
client.usageSummaries.listLines(id: string, options: ListUsageLinesOptions): Promise<Page<UsageLine>>// List usage summaries for a subscription
const summaries = await client.subscriptions.listUsageSummaries('subscription-id', {
page: 0,
size: 50,
sort: 'resourceGroup,asc'
});
for (const summary of summaries.content) {
console.log(`${summary.resourceGroup} (${summary.vendorName})`);
console.log(`Charges: ${summary.currentCharges} ${summary.currencyCode}`);
}
// Get a specific usage summary
const summary = await client.usageSummaries.get('usage-summary-id');
// Get detailed usage lines for a specific date (usageDate is required)
const lines = await client.usageSummaries.listLines('usage-summary-id', {
usageDate: '2024-01-15',
page: 0,
size: 100
});
for (const line of lines.content) {
console.log(`${line.productName}: ${line.quantity} ${line.unitOfMeasure}`);
console.log(` Charges: ${line.currentCharges} ${line.currencyCode}`);
}Manage sales quotes with full lifecycle support: CRUD operations, line items, sections, and access control. Uses v2 API with 1-indexed pagination.
// Create a new quote
const quote = await client.quotes.create({
clientId: 'company-123',
quoteRequestId: 'optional-request-id'
});
// List quotes with pagination and filters
const result = await client.quotes.list({
limit: 20,
page: 1,
status: 'draft',
search: 'customer name'
});
console.log(result.content); // Quote[]
console.log(result.statusCounts); // Counts by status
console.log(result.page); // V2PageInfo (1-indexed)
// Get a specific quote
const quote = await client.quotes.get('quote-123');
// Update quote details
const updated = await client.quotes.update('quote-123', {
status: 'sent',
introMessage: 'Thank you for your interest!',
published: true
});
// Delete a quote
await client.quotes.delete('quote-123');Add, update, and remove line items with support for Standard, Custom, and UsageBased types via discriminated unions.
// Add standard product line items
await client.quotes.addLineItems('quote-123', [
{
type: 'Standard',
productId: 'prod-456',
billingTerm: 'Monthly',
quantity: 10,
price: { amount: 20, currency: 'USD' }
}
]);
// Add custom line items
await client.quotes.addLineItems('quote-123', [
{
type: 'Custom',
productName: 'Consulting Services',
productSku: 'CONSULT-001',
billingTerm: 'One-Time',
quantity: 1,
cost: { amount: 500, currency: 'USD' },
price: { amount: 750, currency: 'USD' }
}
]);
// Add usage-based line items
await client.quotes.addLineItems('quote-123', [
{
type: 'UsageBased',
productId: 'prod-789',
billingTerm: 'Monthly',
quantity: 1,
cost: { amount: 0, currency: 'USD' },
usageBased: {
type: 'QTY_VARIES',
displayNote: true
}
}
]);
// Update line items
await client.quotes.updateLineItems('quote-123', [
{
id: 'line-item-1',
quantity: 15,
price: { amount: 25, currency: 'USD' }
}
]);
// Delete a single line item
await client.quotes.deleteLineItem('quote-123', 'line-item-1');
// Bulk delete line items
await client.quotes.bulkDeleteLineItems('quote-123', {
lineItemIds: ['line-item-1', 'line-item-2']
});Organize line items into named sections for better presentation.
// Get sections
const sections = await client.quotes.getSections('quote-123');
// Create a new section
await client.quotes.createSection('quote-123', {
name: 'Microsoft Products'
});
// Update sections (batch)
await client.quotes.updateSections('quote-123', {
sections: [
{
id: 'section-1',
name: 'Updated Name',
order: 1,
lineItems: [
{ lineItemId: 'line-item-1', order: 1 },
{ lineItemId: 'line-item-2', order: 2 }
]
}
]
});Share quotes with customers by managing email access lists.
// Get access list
const accessList = await client.quotes.getAccessList('quote-123');
// Add email recipients
await client.quotes.addAccessListEntries('quote-123', {
emails: ['customer@example.com', 'stakeholder@example.com']
});
// Remove access
await client.quotes.deleteAccessListEntry('quote-123', 'entry-id');Manage partner-level default preferences for quotes.
// Get current preferences
const prefs = await client.quotePreferences.get();
console.log(`Quotes expire after ${prefs.daysToExpire} days`);
// Update preferences
await client.quotePreferences.update({
daysToExpire: 45,
introMessage: 'Thank you for your interest in our services!',
termsAndDisclaimers: 'Standard terms and conditions apply.'
});Manage event notification webhooks for real-time updates. The Webhooks API uses API v2 endpoints (/api/v2) distinct from v1 endpoints.
// List webhooks with filters and pagination
client.webhooks.list(options?: ListWebhooksOptions): Promise<WebhookPagedResult>
// Get a specific webhook by ID
client.webhooks.get(webhookId: string): Promise<Webhook>
// Create a new webhook
client.webhooks.create(webhook: CreateWebhook): Promise<Webhook>
// Update webhook configuration
client.webhooks.updateConfiguration(webhookId: string, config: UpdateWebhookConfiguration): Promise<Webhook>
// Update webhook status (enable/disable)
client.webhooks.updateStatus(webhookId: string, status: UpdateWebhookStatus): Promise<Webhook>
// Delete a webhook
client.webhooks.delete(webhookId: string): Promise<void>
// Topic management
client.webhooks.addTopic(webhookId: string, topic: AddWebhookTopic): Promise<Webhook>
client.webhooks.replaceTopics(webhookId: string, topics: ReplaceWebhookTopics): Promise<Webhook>
client.webhooks.removeTopic(webhookId: string, topicId: string): Promise<void>
client.webhooks.updateTopicConfiguration(webhookId: string, topicId: string, config: UpdateWebhookTopicConfiguration): Promise<Webhook>
// Get available topic definitions
client.webhooks.getTopicDefinitions(options?: ListTopicDefinitionsOptions): Promise<TopicDefinitionPagedResult>
// Test webhook delivery
client.webhooks.testTopic(webhookId: string, topic: string): Promise<WebhookTest>
// Webhook logs
client.webhooks.listLogs(webhookId: string, options: ListWebhookLogsOptions): Promise<WebhookLogPagedResult>
client.webhooks.getLog(webhookId: string, logId: string): Promise<WebhookLog>
// Retry failed delivery
client.webhooks.retryDelivery(webhookId: string, logId: string): Promise<void>// Create a webhook for order notifications
const webhook = await client.webhooks.create({
displayName: 'Order Notifications',
url: 'https://example.com/webhooks/pax8',
authorization: 'Bearer my-secret-token',
active: true,
contactEmail: 'alerts@example.com',
errorThreshold: 5,
webhookTopics: [
{
topic: 'order.created',
filters: [
{
action: 'created',
conditions: [
{ field: 'companyId', operator: 'EQUALS', value: ['company-uuid'] }
]
}
]
}
]
});
console.log(webhook.id);
// List webhooks with filters
const webhooks = await client.webhooks.list({
active: true,
topic: 'subscription.updated',
page: 0,
size: 20
});
// Add a topic to an existing webhook
const updated = await client.webhooks.addTopic(webhook.id, {
topic: 'subscription.cancelled',
filters: [{ action: 'cancelled', conditions: [] }]
});
// Test webhook delivery
const testResult = await client.webhooks.testTopic(webhook.id, 'order.created');
console.log(testResult.url);
console.log(testResult.samplePayload);
// View webhook logs
const logs = await client.webhooks.listLogs(webhook.id, {
query: {},
status: 'FAILED',
page: 0,
size: 10
});
// Retry failed delivery
if (logs.content.length > 0) {
await client.webhooks.retryDelivery(webhook.id, logs.content[0].id);
}
// Get available topic definitions
const topicDefs = await client.webhooks.getTopicDefinitions({
search: 'order',
page: 0,
size: 20
});
console.log(topicDefs.content.map(t => t.topic));Notes:
- Webhooks API uses v2 endpoints (
/api/v2/webhooks) errorThresholdmaximum is 20 (controls retry attempts before email notification)- Topics cannot be duplicated on a single webhook
- Webhook testing sends sample events regardless of subscription
- Failed deliveries can be retried via log ID (returns HTTP 202 Accepted)
// Note: This is an example for other APIs that may be added in the future
// The Companies API uses cursor-based pagination shown above
const page1 = await client.companies.list({ page: 0, size: 50 });
console.log(`Page 1: ${page1.content.length} companies`);
console.log(`Total: ${page1.page.totalElements} companies across ${page1.page.totalPages} pages`);
// Get second page
const page2 = await client.companies.list({ page: 1, size: 50 });
console.log(`Page 2: ${page2.content.length} companies`);
// Manually iterate through all pages
let currentPage = 0;
let hasMore = true;
while (hasMore) {
const page = await client.companies.list({ page: currentPage, size: 100 });
for (const company of page.content) {
console.log(company.name);
}
currentPage++;
hasMore = currentPage < page.page.totalPages;
}Use the async iterator for automatic pagination (recommended for large collections):
// Iterate through all companies automatically
for await (const company of client.companies.listAll()) {
console.log(company.name);
}
// With filtering
for await (const company of client.companies.listAll({ stateOrProvince: 'CA' })) {
console.log(`${company.name} (California)`);
}
// Process in batches
const companies: Company[] = [];
for await (const company of client.companies.listAll({ size: 100 })) {
companies.push(company);
// Process batch every 100 items
if (companies.length >= 100) {
await processBatch(companies);
companies.length = 0; // Clear array
}
}
// Process remaining items
if (companies.length > 0) {
await processBatch(companies);
}- For bulk operations, use larger page sizes (e.g.,
100) to minimize API calls - The async iterator automatically handles rate limiting and retries
- Use
sortparameter to ensure consistent ordering across pages
The package provides typed error classes for precise error handling. All errors extend the base Pax8Error class.
// Base error class
class Pax8Error extends Error {
readonly status: number; // HTTP status code
readonly type: string; // Error type from API
readonly instance: string; // Request path
readonly details?: ErrorDetail[]; // Additional error details
}
// Specific error types
class Pax8AuthenticationError extends Pax8Error {
// status: 401
// Thrown when credentials are invalid or token is expired
}
class Pax8ForbiddenError extends Pax8Error {
// status: 403
// Thrown when you don't have permission to access a resource
}
class Pax8NotFoundError extends Pax8Error {
// status: 404
// Thrown when a resource doesn't exist
}
class Pax8ValidationError extends Pax8Error {
// status: 422
// Thrown when request data fails validation
readonly fieldErrors: FieldError[];
}
class Pax8RateLimitError extends Pax8Error {
// status: 429
// Thrown when rate limit is exceeded
readonly retryAfter: number; // Seconds until retry allowed
}
class Pax8TimeoutError extends Pax8Error {
// status: 408
// Thrown when request exceeds timeout
}
class Pax8ServerError extends Pax8Error {
// status: 500+
// Thrown when API encounters a server error
}import {
Pax8NotFoundError,
Pax8ValidationError,
Pax8RateLimitError,
Pax8AuthenticationError
} from '@imperian-systems/pax8-api';
try {
const company = await client.companies.get('invalid-id');
} catch (error) {
if (error instanceof Pax8NotFoundError) {
console.log('Company not found');
} else if (error instanceof Pax8AuthenticationError) {
console.log('Authentication failed - check your credentials');
} else {
console.error('Unexpected error:', error);
}
}try {
const company = await client.companies.create({
name: '', // Invalid: empty name
address: { /* incomplete address */ }
});
} catch (error) {
if (error instanceof Pax8ValidationError) {
console.log('Validation failed:');
for (const fieldError of error.fieldErrors) {
console.log(` - ${fieldError.field}: ${fieldError.message}`);
}
}
}try {
const companies = await client.companies.list({ size: 200 });
} catch (error) {
if (error instanceof Pax8RateLimitError) {
console.log(`Rate limited. Retry after ${error.retryAfter} seconds`);
// Wait and retry
await new Promise(resolve => setTimeout(resolve, error.retryAfter * 1000));
const companies = await client.companies.list({ size: 200 });
}
}import { Pax8Error } from '@imperian-systems/pax8-api';
try {
await client.companies.get('company-id');
} catch (error) {
if (error instanceof Pax8Error) {
console.log(`Pax8 API error [${error.status}]: ${error.message}`);
console.log(`Request: ${error.instance}`);
if (error.details) {
console.log('Details:', error.details);
}
} else {
// Network error or other non-API error
console.error('Unexpected error:', error);
}
}The client automatically retries failed requests (configurable via retryAttempts):
- Retried: Network errors, timeouts, 5xx server errors, rate limit errors (with backoff)
- Not retried: 4xx client errors (except 429 rate limit)
This package is written in TypeScript and provides full type definitions out of the box.
Import types for use in your application:
import type {
Company,
Contact,
ContactType,
ContactTypeEnum,
CreateContactRequest,
UpdateContactRequest,
ListContactsParams,
Product,
Order,
Subscription,
Invoice,
UsageSummary,
Quote,
Webhook,
Page
} from '@imperian-systems/pax8-api';
// Use types for function parameters and return types
async function getCompanyOrders(companyId: string): Promise<Order[]> {
const page = await client.orders.list({ companyId, size: 100 });
return page.content;
}
// Type-safe contact filtering
async function getPrimaryAdminContact(companyId: string): Promise<Contact | undefined> {
const { content } = await client.contacts.list({ companyId, size: 200 });
return content.find(c =>
c.types?.some(t => t.type === 'Admin' && t.primary)
);
}
// Type-safe filtering
function filterActiveSubscriptions(subscriptions: Subscription[]): Subscription[] {
return subscriptions.filter(sub => sub.status === 'Active');
}The pagination Page<T> type is generic:
import type { Page, Company } from '@imperian-systems/pax8-api';
async function getAllCompanies(): Promise<Page<Company>> {
return await client.companies.list({ size: 200 });
}Status and state fields use TypeScript union types:
import type { SubscriptionStatus, BillingTerm } from '@imperian-systems/pax8-api';
const status: SubscriptionStatus = 'Active';
const term: BillingTerm = 'Monthly';
// TypeScript will catch invalid values at compile time
// const invalid: SubscriptionStatus = 'InvalidStatus'; // ❌ ErrorTypeScript provides autocomplete for all methods and properties:
const client = new Pax8Client({ /* config */ });
// Type "client." to see all available resources
client.companies // ✓
client.products // ✓
client.subscriptions // ✓
// Type "client.companies." to see all available methods
client.companies.list // ✓
client.companies.get // ✓
client.companies.create // ✓The Pax8 API enforces rate limits to ensure fair usage:
- Limit: 1000 requests per minute per application
- Headers: Rate limit information is included in response headers:
X-RateLimit-Limit: Total requests allowed per windowX-RateLimit-Remaining: Requests remaining in current windowX-RateLimit-Reset: Unix timestamp when limit resets
This package automatically handles rate limiting:
- Respects
Retry-Afterheader: When rate limited (429 error), the client waits for the duration specified in theRetry-Afterheader before retrying - Exponential backoff: For other retryable errors, uses exponential backoff
- Configurable retries: Set
retryAttemptsin client configuration
To avoid rate limiting:
// ✅ Good: Use large page sizes to minimize requests
const companies = await client.companies.list({ size: 200 });
// ✅ Good: Use async iterator for automatic pagination
for await (const company of client.companies.listAll({ size: 100 })) {
await processCompany(company);
}
// ⚠️ Caution: Making many requests in quick succession
// Consider adding delays between requests
for (const id of companyIds) {
await client.companies.get(id);
await new Promise(resolve => setTimeout(resolve, 100)); // Add delay
}
// ✅ Good: Batch operations when possible
const companyIds = ['id1', 'id2', 'id3', /* ... */];
const results = await Promise.all(
companyIds.map(id => client.companies.get(id))
);You can monitor rate limit status by catching Pax8RateLimitError:
import { Pax8RateLimitError } from '@imperian-systems/pax8-api';
try {
await client.companies.list();
} catch (error) {
if (error instanceof Pax8RateLimitError) {
console.warn(`Rate limited! Retry after ${error.retryAfter} seconds`);
// Implement backoff strategy
}
}- Pax8 API Documentation - Official API reference and guides
- Pax8 Integrations Hub - Get your API credentials
- Pax8 Status Page - API status and incident reports
- npm Package - Package on npm registry
- GitHub Repository - Source code and issue tracker
This project is licensed under the MIT License. See the LICENSE file for details.