maintainx-sdk-patterns
Learn MaintainX REST API patterns, pagination, filtering, and client architecture. Use when building robust API integrations, implementing pagination, or creating reusable SDK patterns for MaintainX. Trigger with phrases like "maintainx sdk", "maintainx api patterns", "maintainx pagination", "maintainx filtering", "maintainx client design".
Install
mkdir -p .claude/skills/maintainx-sdk-patterns && curl -L -o skill.zip "https://mcp.directory/api/skills/download/8636" && unzip -o skill.zip -d .claude/skills/maintainx-sdk-patterns && rm skill.zipInstalls to .claude/skills/maintainx-sdk-patterns
About this skill
MaintainX SDK Patterns
Overview
Production-grade patterns for building robust MaintainX API integrations with proper error handling, cursor-based pagination, retry logic, and type safety.
Prerequisites
- Completed
maintainx-install-authsetup - TypeScript/Node.js familiarity
- Understanding of REST API principles
Instructions
Step 1: Type-Safe Client with Generics
// src/maintainx/typed-client.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios';
interface PaginatedResponse<T> {
cursor: string | null;
}
interface WorkOrder {
id: number;
title: string;
status: 'OPEN' | 'IN_PROGRESS' | 'ON_HOLD' | 'COMPLETED' | 'CLOSED';
priority: 'NONE' | 'LOW' | 'MEDIUM' | 'HIGH';
description?: string;
assignees: Array<{ type: 'USER' | 'TEAM'; id: number }>;
assetId?: number;
locationId?: number;
createdAt: string;
updatedAt: string;
completedAt?: string;
dueDate?: string;
categories: string[];
}
interface Asset {
id: number;
name: string;
serialNumber?: string;
model?: string;
manufacturer?: string;
locationId?: number;
createdAt: string;
}
interface WorkOrdersResponse extends PaginatedResponse<WorkOrder> {
workOrders: WorkOrder[];
}
interface AssetsResponse extends PaginatedResponse<Asset> {
assets: Asset[];
}
export class MaintainXClient {
private http: AxiosInstance;
constructor(apiKey?: string) {
const key = apiKey || process.env.MAINTAINX_API_KEY;
if (!key) throw new Error('MAINTAINX_API_KEY required');
this.http = axios.create({
baseURL: 'https://api.getmaintainx.com/v1',
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
timeout: 30_000,
});
}
async getWorkOrders(params?: Record<string, any>): Promise<WorkOrdersResponse> {
const { data } = await this.http.get<WorkOrdersResponse>('/workorders', { params });
return data;
}
async getWorkOrder(id: number): Promise<WorkOrder> {
const { data } = await this.http.get<WorkOrder>(`/workorders/${id}`);
return data;
}
async createWorkOrder(input: Partial<WorkOrder>): Promise<WorkOrder> {
const { data } = await this.http.post<WorkOrder>('/workorders', input);
return data;
}
async updateWorkOrder(id: number, input: Partial<WorkOrder>): Promise<WorkOrder> {
const { data } = await this.http.patch<WorkOrder>(`/workorders/${id}`, input);
return data;
}
async getAssets(params?: Record<string, any>): Promise<AssetsResponse> {
const { data } = await this.http.get<AssetsResponse>('/assets', { params });
return data;
}
async request<T = any>(method: string, path: string, body?: any): Promise<T> {
const config: AxiosRequestConfig = { method, url: path, data: body };
const { data } = await this.http.request<T>(config);
return data;
}
}
Step 2: Cursor-Based Pagination
MaintainX uses cursor-based pagination. The response includes a cursor field; pass it as a query parameter to get the next page.
async function paginate<T>(
fetcher: (cursor?: string) => Promise<{ cursor: string | null } & Record<string, T[]>>,
key: string,
): Promise<T[]> {
const all: T[] = [];
let cursor: string | undefined;
do {
const response = await fetcher(cursor);
const items = (response as any)[key] as T[];
all.push(...items);
cursor = response.cursor ?? undefined;
} while (cursor);
return all;
}
// Usage
const allWorkOrders = await paginate(
(cursor) => client.getWorkOrders({ limit: 100, cursor, status: 'OPEN' }),
'workOrders',
);
console.log(`Total open work orders: ${allWorkOrders.length}`);
const allAssets = await paginate(
(cursor) => client.getAssets({ limit: 100, cursor }),
'assets',
);
console.log(`Total assets: ${allAssets.length}`);
Step 3: Retry with Exponential Backoff
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelayMs = 1000,
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (err: any) {
const status = err?.response?.status;
const isRetryable = status === 429 || (status >= 500 && status < 600);
if (!isRetryable || attempt === maxRetries) throw err;
// Honor Retry-After header if present
const retryAfter = err.response?.headers?.['retry-after'];
const delayMs = retryAfter
? parseInt(retryAfter) * 1000
: baseDelayMs * Math.pow(2, attempt) + Math.random() * 500;
console.warn(`Retry ${attempt + 1}/${maxRetries} after ${delayMs}ms (HTTP ${status})`);
await new Promise((r) => setTimeout(r, delayMs));
}
}
throw new Error('Unreachable');
}
// Usage
const wo = await withRetry(() => client.getWorkOrder(12345));
Step 4: Batch Operations
import PQueue from 'p-queue';
const queue = new PQueue({ concurrency: 5, interval: 1000, intervalCap: 10 });
async function batchCreateWorkOrders(items: Array<Partial<WorkOrder>>): Promise<WorkOrder[]> {
const results = await Promise.all(
items.map((item) =>
queue.add(() => withRetry(() => client.createWorkOrder(item)))
),
);
return results as WorkOrder[];
}
// Create 50 PMs in controlled batches
const pms = Array.from({ length: 50 }, (_, i) => ({
title: `Weekly Inspection - Zone ${i + 1}`,
priority: 'LOW' as const,
categories: ['PREVENTIVE'],
}));
const created = await batchCreateWorkOrders(pms);
console.log(`Created ${created.length} preventive maintenance orders`);
Step 5: Fluent Query Builder
class WorkOrderQuery {
private params: Record<string, any> = {};
status(s: WorkOrder['status']) { this.params.status = s; return this; }
priority(p: WorkOrder['priority']) { this.params.priority = p; return this; }
assignee(userId: number) { this.params.assigneeId = userId; return this; }
asset(assetId: number) { this.params.assetId = assetId; return this; }
location(locationId: number) { this.params.locationId = locationId; return this; }
createdAfter(date: string) { this.params.createdAtGte = date; return this; }
createdBefore(date: string) { this.params.createdAtLte = date; return this; }
limit(n: number) { this.params.limit = n; return this; }
async execute(client: MaintainXClient) {
return client.getWorkOrders(this.params);
}
}
// Usage
const results = await new WorkOrderQuery()
.status('OPEN')
.priority('HIGH')
.location(2345)
.createdAfter('2026-01-01T00:00:00Z')
.limit(25)
.execute(client);
Output
- Type-safe MaintainX client with full TypeScript interfaces
- Cursor-based pagination utility that works across all list endpoints
- Retry logic with exponential backoff and
Retry-Afterheader support - Rate-limited batch processor using
p-queue - Fluent query builder for readable work order filters
Error Handling
| Pattern | Use Case |
|---|---|
withRetry() | Transient errors (429, 5xx) with exponential backoff |
paginate() | Collecting all items from cursor-based endpoints |
PQueue | Controlled concurrency to avoid rate limits |
WorkOrderQuery | Type-safe filtering to prevent invalid API calls |
Resources
- MaintainX API Reference
- p-queue -- Promise queue with concurrency control
Next Steps
For core workflows, see maintainx-core-workflow-a (Work Orders) and maintainx-core-workflow-b (Assets).
Examples
Stream large datasets with async iterators:
async function* streamWorkOrders(client: MaintainXClient, params?: Record<string, any>) {
let cursor: string | undefined;
do {
const response = await client.getWorkOrders({ ...params, limit: 100, cursor });
for (const wo of response.workOrders) {
yield wo;
}
cursor = response.cursor ?? undefined;
} while (cursor);
}
for await (const wo of streamWorkOrders(client, { status: 'COMPLETED' })) {
console.log(`Processing completed WO #${wo.id}`);
}
More by jeremylongshore
View all skills by jeremylongshore →You might also like
flutter-development
aj-geddes
Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.
drawio-diagrams-enhanced
jgtolentino
Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.
ui-ux-pro-max
nextlevelbuilder
"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."
godot
bfollington
This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.
nano-banana-pro
garg-aayush
Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.
pdf-to-markdown
aliceisjustplaying
Convert entire PDF documents to clean, structured Markdown for full context loading. Use this skill when the user wants to extract ALL text from a PDF into context (not grep/search), when discussing or analyzing PDF content in full, when the user mentions "load the whole PDF", "bring the PDF into context", "read the entire PDF", or when partial extraction/grepping would miss important context. This is the preferred method for PDF text extraction over page-by-page or grep approaches.
Related MCP Servers
Browse all serversEasily enable Bitbucket and Jira integration with REST APIs for seamless repository management, pull requests, and works
Strapi CMS offers robust content management software features like filtering, pagination, and media uploads, ideal for m
Unlock advanced workflow automation, user management, and job tracking with Alteryx Server. Learn about Alteryx Server c
Learn how to use Python to read a file and manipulate local files safely through the Filesystem API.
Access Confluence pages and Jira in the cloud with Atlassian API. Integrate effortlessly using the REST API for Jira.
By Open WebUI. A simple, secure MCP-to-OpenAPI proxy server. Expose any MCP server as a REST API with automatic OpenAPI
Stay ahead of the MCP ecosystem
Get weekly updates on new skills and servers.