@imperian-systems/pax8-api
v1.0.2
Published
TypeScript client for the Pax8 API with OAuth 2.0 client credentials and zero runtime dependencies.
Maintainers
Readme
@imperian-systems/pax8-api
A TypeScript client library for the Pax8 API with full type safety, automatic token management, and zero runtime dependencies.
Table of Contents
- Requirements
- Installation
- Quick Start
- Configuration
- Authentication
- API Reference
- Pagination
- Error Handling
- TypeScript
- Rate Limiting
- Links
- License
Requirements
Node.js 22 or higher is required. This package uses native fetch API available in Node.js 22+ and has zero runtime dependencies.
Installation
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-apiQuick Start
Get 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);Configuration
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 |
Example with Custom Configuration
const client = new Pax8Client({
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
retryAttempts: 5,
retryDelay: 2000,
timeout: 60000
});Authentication
This package uses OAuth 2.0 Client Credentials flow for authentication with the Pax8 API.
Getting Your Credentials
- 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
How It Works
- 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
Manual Token Management
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();API Reference
This section documents all available API resources and their methods.
Companies
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}`);
}Types
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;
};
}Contacts
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: '[email protected]',
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');Types
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)
};
}Contact Type System
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: '[email protected]',
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: '[email protected]',
types: [{ type: 'Admin', primary: true }]
});
// Jane is still primary for Billing, but Bob is now primary for AdminProducts
Browse 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' }
);Types
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)
};
}Dynamic Data Warning
⚠️ Important: Provisioning details, dependencies, and pricing are dynamic and can change frequently based on vendor updates. Always fetch fresh data when creating orders or subscriptions. Do not cache this information for extended periods.
Orders
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>Example
// 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: '[email protected]',
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
Subscriptions
Manage active service agreements. The Subscriptions API uses page-based pagination with page (0-based) and size (default 10, max 200).
Methods
// 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}`);
}
}Types
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;
}Error Responses
- 404 Not Found: Subscription ID does not exist
- 422 Unprocessable Entity: Invalid quantity (must be positive) or invalid billingDate format
Invoices
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>>Example
// 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
});Usage Summaries
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>>Example
// 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}`);
}Quotes
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');Line Item Management
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']
});Section Management
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 }
]
}
]
});Access List Management
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: ['[email protected]', '[email protected]']
});
// Remove access
await client.quotes.deleteAccessListEntry('quote-123', 'entry-id');Quote Preferences
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.'
});Webhooks
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>Example
// 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: '[email protected]',
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)
Pagination
// 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;
}Async Iterator Pattern
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);
}Pagination Tips
- 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
Error Handling
The package provides typed error classes for precise error handling. All errors extend the base Pax8Error class.
Error Class Hierarchy
// 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
}Error Handling Examples
Handle Specific Error Types
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);
}
}Handle Validation Errors
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}`);
}
}
}Handle Rate Limiting
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 });
}
}Catch All Pax8 Errors
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);
}
}Automatic Retries
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)
TypeScript
This package is written in TypeScript and provides full type definitions out of the box.
Type Imports
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');
}Generic Types
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 });
}Type-Safe Enums
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'; // ❌ ErrorIntelliSense & Autocomplete
TypeScript 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 // ✓Rate Limiting
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
Automatic Handling
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
Best Practices
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))
);Monitoring Rate Limits
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
}
}Links
- 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
License
This project is licensed under the MIT License. See the LICENSE file for details.
