[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F84M33MNCBH4Z9CFWHPE5X":3,"skill-same:01M6F84M33MNCBH4Z9CFWHPE5X":79},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":47,"linkOnly":48,"markdownContent":49,"markdownBody":50,"frontMatter":51,"refName":56,"commitSha":57,"skillDirectory":58,"forksCount":59,"repositoryHtmlUrl":60,"repositoryHomepageUrl":6,"ownerHtmlUrl":61,"ownerBlogUrl":62,"ownerDescription":63,"updatedAt":64,"install":65,"resources":78},"01M6F84M33MNCBH4Z9CFWHPE5X","maintainx-sdk-patterns",null,"Learn MaintainX REST API patterns, pagination, filtering, and client architecture.\nUse when building robust API integrations, implementing pagination,\nor creating reusable SDK patterns for MaintainX.\nTrigger with phrases like \"maintainx sdk\", \"maintainx api patterns\",\n\"maintainx pagination\", \"maintainx filtering\", \"maintainx client design\".","Dicklesworthstone\u002Fpi_agent_rust","Dicklesworthstone","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F35050222?v=4","01M6CG26AR942CSVMN1M5E3DSE","Jeff Emanuel","01M6CG26EPP71PSHW1VDS71121",1992,"MIT","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Fplugins-community\u002Fplugins\u002Fsaas-packs\u002Fmaintainx-pack\u002Fskills\u002Fmaintainx-sdk-patterns\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46],"sdk","maintainx sdk patterns","patterns","when","design","for","implementing","with","trigger","io","reusable","use","filtering","edit","write","read","integrations","Bash","api","api patterns","learn","architecture","rest api patterns","2026-11-10 10:12:30",false,"---\nname: maintainx-sdk-patterns\ndescription: |\n  Learn MaintainX REST API patterns, pagination, filtering, and client architecture.\n  Use when building robust API integrations, implementing pagination,\n  or creating reusable SDK patterns for MaintainX.\n  Trigger with phrases like \"maintainx sdk\", \"maintainx api patterns\",\n  \"maintainx pagination\", \"maintainx filtering\", \"maintainx client design\".\nallowed-tools: Read, Write, Edit, Bash(npm:*), Grep\nversion: 1.0.0\nlicense: MIT\nauthor: Jeremy Longshore \u003Cjeremy@intentsolutions.io>\n---\n\n# MaintainX SDK Patterns\n\n## Overview\n\nProduction-grade patterns for building robust MaintainX API integrations with proper error handling, pagination, and type safety.\n\n## Prerequisites\n\n- Completed `maintainx-install-auth` setup\n- Understanding of REST API principles\n- TypeScript\u002FNode.js familiarity\n\n## Core API Endpoints\n\n### Available Endpoints\n\n| Resource | Endpoint | Methods | Description |\n|----------|----------|---------|-------------|\n| Work Orders | `\u002Fworkorders` | GET, POST | Maintenance tasks |\n| Work Requests | `\u002Fworkrequests` | GET, POST | Maintenance requests |\n| Assets | `\u002Fassets` | GET | Equipment tracking |\n| Locations | `\u002Flocations` | GET | Facility\u002Farea hierarchy |\n| Users | `\u002Fusers` | GET | Team members |\n| Parts | `\u002Fparts` | GET | Inventory items |\n| Procedures | `\u002Fprocedures` | GET | Standard checklists |\n\n## Instructions\n\n### Step 1: Type-Safe API Client\n\n```typescript\n\u002F\u002F src\u002Fapi\u002Fmaintainx-client.ts\nimport axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';\n\n\u002F\u002F Base types\ninterface PaginatedResponse\u003CT> {\n  [key: string]: T[];\n  nextCursor: string | null;\n}\n\ninterface WorkOrder {\n  id: string;\n  title: string;\n  description?: string;\n  status: WorkOrderStatus;\n  priority: WorkOrderPriority;\n  assignees?: User[];\n  asset?: Asset;\n  location?: Location;\n  dueDate?: string;\n  completedAt?: string;\n  createdAt: string;\n  updatedAt: string;\n}\n\ntype WorkOrderStatus = 'OPEN' | 'IN_PROGRESS' | 'ON_HOLD' | 'DONE';\ntype WorkOrderPriority = 'NONE' | 'LOW' | 'MEDIUM' | 'HIGH';\n\ninterface Asset {\n  id: string;\n  name: string;\n  serialNumber?: string;\n  model?: string;\n  manufacturer?: string;\n  location?: Location;\n  status: 'OPERATIONAL' | 'NON_OPERATIONAL' | 'DECOMMISSIONED';\n}\n\ninterface Location {\n  id: string;\n  name: string;\n  address?: string;\n  parentId?: string;\n}\n\ninterface User {\n  id: string;\n  firstName: string;\n  lastName: string;\n  email: string;\n  role: string;\n}\n\n\u002F\u002F Query parameters\ninterface QueryParams {\n  cursor?: string;\n  limit?: number;\n}\n\ninterface WorkOrderQueryParams extends QueryParams {\n  status?: WorkOrderStatus;\n  priority?: WorkOrderPriority;\n  assigneeId?: string;\n  assetId?: string;\n  locationId?: string;\n  createdAfter?: string;\n  createdBefore?: string;\n}\n\ninterface AssetQueryParams extends QueryParams {\n  locationId?: string;\n  status?: Asset['status'];\n}\n\n\u002F\u002F Main client class\nexport class MaintainXClient {\n  private client: AxiosInstance;\n\n  constructor(config?: { apiKey?: string; baseUrl?: string; timeout?: number }) {\n    const apiKey = config?.apiKey || process.env.MAINTAINX_API_KEY;\n    if (!apiKey) {\n      throw new Error('MAINTAINX_API_KEY is required');\n    }\n\n    this.client = axios.create({\n      baseURL: config?.baseUrl || 'https:\u002F\u002Fapi.getmaintainx.com\u002Fv1',\n      timeout: config?.timeout || 30000,\n      headers: {\n        'Authorization': `Bearer ${apiKey}`,\n        'Content-Type': 'application\u002Fjson',\n      },\n    });\n\n    this.setupInterceptors();\n  }\n\n  private setupInterceptors() {\n    \u002F\u002F Request logging\n    this.client.interceptors.request.use(config => {\n      console.debug(`[MaintainX] ${config.method?.toUpperCase()} ${config.url}`);\n      return config;\n    });\n\n    \u002F\u002F Response error handling\n    this.client.interceptors.response.use(\n      response => response,\n      error => {\n        if (error.response) {\n          const { status, data } = error.response;\n          const message = data?.message || data?.error || 'Unknown error';\n          console.error(`[MaintainX] Error ${status}: ${message}`);\n        }\n        throw error;\n      }\n    );\n  }\n\n  \u002F\u002F Work Orders\n  async getWorkOrders(params?: WorkOrderQueryParams): Promise\u003CPaginatedResponse\u003CWorkOrder>> {\n    const response = await this.client.get('\u002Fworkorders', { params });\n    return response.data;\n  }\n\n  async getWorkOrder(id: string): Promise\u003CWorkOrder> {\n    const response = await this.client.get(`\u002Fworkorders\u002F${id}`);\n    return response.data;\n  }\n\n  async createWorkOrder(data: Partial\u003CWorkOrder>): Promise\u003CWorkOrder> {\n    const response = await this.client.post('\u002Fworkorders', data);\n    return response.data;\n  }\n\n  \u002F\u002F Assets\n  async getAssets(params?: AssetQueryParams): Promise\u003CPaginatedResponse\u003CAsset>> {\n    const response = await this.client.get('\u002Fassets', { params });\n    return response.data;\n  }\n\n  async getAsset(id: string): Promise\u003CAsset> {\n    const response = await this.client.get(`\u002Fassets\u002F${id}`);\n    return response.data;\n  }\n\n  \u002F\u002F Locations\n  async getLocations(params?: QueryParams): Promise\u003CPaginatedResponse\u003CLocation>> {\n    const response = await this.client.get('\u002Flocations', { params });\n    return response.data;\n  }\n\n  async getLocation(id: string): Promise\u003CLocation> {\n    const response = await this.client.get(`\u002Flocations\u002F${id}`);\n    return response.data;\n  }\n\n  \u002F\u002F Users\n  async getUsers(params?: QueryParams): Promise\u003CPaginatedResponse\u003CUser>> {\n    const response = await this.client.get('\u002Fusers', { params });\n    return response.data;\n  }\n\n  async getUser(id: string): Promise\u003CUser> {\n    const response = await this.client.get(`\u002Fusers\u002F${id}`);\n    return response.data;\n  }\n}\n```\n\n### Step 2: Cursor-Based Pagination\n\n```typescript\n\u002F\u002F src\u002Futils\u002Fpagination.ts\nimport { MaintainXClient } from '..\u002Fapi\u002Fmaintainx-client';\n\ninterface PaginationOptions {\n  limit?: number;\n  maxPages?: number;\n  delayMs?: number;\n}\n\n\u002F\u002F Generic paginator\nexport async function* paginate\u003CT>(\n  fetchFn: (cursor?: string) => Promise\u003C{ items: T[]; nextCursor: string | null }>,\n  options: PaginationOptions = {}\n): AsyncGenerator\u003CT[], void, unknown> {\n  const { limit = 100, maxPages = Infinity, delayMs = 0 } = options;\n  let cursor: string | undefined;\n  let pageCount = 0;\n\n  do {\n    const response = await fetchFn(cursor);\n    yield response.items;\n\n    cursor = response.nextCursor || undefined;\n    pageCount++;\n\n    if (delayMs > 0 && cursor) {\n      await new Promise(r => setTimeout(r, delayMs));\n    }\n  } while (cursor && pageCount \u003C maxPages);\n}\n\n\u002F\u002F Work order specific paginator\nexport async function getAllWorkOrders(\n  client: MaintainXClient,\n  params: Parameters\u003Ctypeof client.getWorkOrders>[0] = {},\n  options: PaginationOptions = {}\n): Promise\u003CWorkOrder[]> {\n  const allWorkOrders: WorkOrder[] = [];\n\n  for await (const batch of paginate(\n    async (cursor) => {\n      const response = await client.getWorkOrders({ ...params, cursor, limit: options.limit || 100 });\n      return { items: response.workOrders, nextCursor: response.nextCursor };\n    },\n    options\n  )) {\n    allWorkOrders.push(...batch);\n  }\n\n  return allWorkOrders;\n}\n\n\u002F\u002F Usage example\nasync function fetchAllOpenWorkOrders(client: MaintainXClient) {\n  const workOrders = await getAllWorkOrders(\n    client,\n    { status: 'OPEN' },\n    { limit: 100, maxPages: 10 }\n  );\n  console.log(`Fetched ${workOrders.length} open work orders`);\n  return workOrders;\n}\n```\n\n### Step 3: Retry with Exponential Backoff\n\n```typescript\n\u002F\u002F src\u002Futils\u002Fretry.ts\ninterface RetryConfig {\n  maxRetries: number;\n  baseDelayMs: number;\n  maxDelayMs: number;\n  retryableStatuses: number[];\n}\n\nconst defaultConfig: RetryConfig = {\n  maxRetries: 3,\n  baseDelayMs: 1000,\n  maxDelayMs: 30000,\n  retryableStatuses: [429, 500, 502, 503, 504],\n};\n\nexport async function withRetry\u003CT>(\n  operation: () => Promise\u003CT>,\n  config: Partial\u003CRetryConfig> = {}\n): Promise\u003CT> {\n  const { maxRetries, baseDelayMs, maxDelayMs, retryableStatuses } = {\n    ...defaultConfig,\n    ...config,\n  };\n\n  let lastError: Error;\n\n  for (let attempt = 0; attempt \u003C= maxRetries; attempt++) {\n    try {\n      return await operation();\n    } catch (error: any) {\n      lastError = error;\n      const status = error.response?.status;\n\n      \u002F\u002F Don't retry non-retryable errors\n      if (status && !retryableStatuses.includes(status)) {\n        throw error;\n      }\n\n      if (attempt === maxRetries) {\n        throw error;\n      }\n\n      \u002F\u002F Calculate delay with exponential backoff + jitter\n      const exponentialDelay = baseDelayMs * Math.pow(2, attempt);\n      const jitter = Math.random() * 500;\n      const delay = Math.min(exponentialDelay + jitter, maxDelayMs);\n\n      console.log(`Retry ${attempt + 1}\u002F${maxRetries} after ${delay}ms`);\n      await new Promise(r => setTimeout(r, delay));\n    }\n  }\n\n  throw lastError!;\n}\n\n\u002F\u002F Usage\nasync function resilientApiCall(client: MaintainXClient) {\n  return withRetry(\n    () => client.getWorkOrders({ limit: 100 }),\n    { maxRetries: 5 }\n  );\n}\n```\n\n### Step 4: Batch Operations\n\n```typescript\n\u002F\u002F src\u002Futils\u002Fbatch.ts\ninterface BatchConfig {\n  batchSize: number;\n  concurrency: number;\n  delayBetweenBatches: number;\n}\n\nexport async function processBatch\u003CT, R>(\n  items: T[],\n  processor: (item: T) => Promise\u003CR>,\n  config: Partial\u003CBatchConfig> = {}\n): Promise\u003CR[]> {\n  const { batchSize = 10, concurrency = 5, delayBetweenBatches = 100 } = config;\n  const results: R[] = [];\n\n  for (let i = 0; i \u003C items.length; i += batchSize) {\n    const batch = items.slice(i, i + batchSize);\n\n    \u002F\u002F Process batch with limited concurrency\n    const batchResults = await Promise.all(\n      batch.map(item => processor(item))\n    );\n\n    results.push(...batchResults);\n\n    \u002F\u002F Delay between batches to avoid rate limiting\n    if (i + batchSize \u003C items.length) {\n      await new Promise(r => setTimeout(r, delayBetweenBatches));\n    }\n  }\n\n  return results;\n}\n\n\u002F\u002F Create multiple work orders\nasync function createWorkOrders(\n  client: MaintainXClient,\n  workOrderData: Partial\u003CWorkOrder>[]\n): Promise\u003CWorkOrder[]> {\n  return processBatch(\n    workOrderData,\n    data => client.createWorkOrder(data),\n    { batchSize: 5, delayBetweenBatches: 200 }\n  );\n}\n```\n\n### Step 5: Query Builder Pattern\n\n```typescript\n\u002F\u002F src\u002Futils\u002Fquery-builder.ts\nclass WorkOrderQueryBuilder {\n  private params: WorkOrderQueryParams = {};\n\n  status(status: WorkOrderStatus): this {\n    this.params.status = status;\n    return this;\n  }\n\n  priority(priority: WorkOrderPriority): this {\n    this.params.priority = priority;\n    return this;\n  }\n\n  assignedTo(userId: string): this {\n    this.params.assigneeId = userId;\n    return this;\n  }\n\n  forAsset(assetId: string): this {\n    this.params.assetId = assetId;\n    return this;\n  }\n\n  atLocation(locationId: string): this {\n    this.params.locationId = locationId;\n    return this;\n  }\n\n  createdBetween(start: Date, end: Date): this {\n    this.params.createdAfter = start.toISOString();\n    this.params.createdBefore = end.toISOString();\n    return this;\n  }\n\n  limit(count: number): this {\n    this.params.limit = count;\n    return this;\n  }\n\n  build(): WorkOrderQueryParams {\n    return { ...this.params };\n  }\n\n  async execute(client: MaintainXClient) {\n    return client.getWorkOrders(this.build());\n  }\n}\n\n\u002F\u002F Fluent API usage\nasync function queryExample(client: MaintainXClient) {\n  const query = new WorkOrderQueryBuilder()\n    .status('OPEN')\n    .priority('HIGH')\n    .limit(50);\n\n  const results = await query.execute(client);\n  return results;\n}\n```\n\n## Output\n\n- Type-safe MaintainX client with full TypeScript support\n- Cursor-based pagination utilities\n- Retry logic with exponential backoff\n- Batch processing helpers\n- Fluent query builder\n\n## Error Handling\n\n| Pattern | Use Case |\n|---------|----------|\n| Retry with backoff | Transient errors (429, 5xx) |\n| Pagination | Large result sets |\n| Batch processing | Bulk operations |\n| Query builder | Complex filtering |\n\n## Resources\n\n- [MaintainX API Documentation](https:\u002F\u002Fmaintainx.dev\u002F)\n- [REST API Best Practices](https:\u002F\u002Fdocs.github.com\u002Fen\u002Frest\u002Fusing-the-rest-api\u002Fbest-practices-for-using-the-rest-api)\n\n## Next Steps\n\nFor core workflows, see `maintainx-core-workflow-a` (Work Orders) and `maintainx-core-workflow-b` (Assets).\n","# MaintainX SDK Patterns\n\n## Overview\n\nProduction-grade patterns for building robust MaintainX API integrations with proper error handling, pagination, and type safety.\n\n## Prerequisites\n\n- Completed `maintainx-install-auth` setup\n- Understanding of REST API principles\n- TypeScript\u002FNode.js familiarity\n\n## Core API Endpoints\n\n### Available Endpoints\n\n| Resource | Endpoint | Methods | Description |\n|----------|----------|---------|-------------|\n| Work Orders | `\u002Fworkorders` | GET, POST | Maintenance tasks |\n| Work Requests | `\u002Fworkrequests` | GET, POST | Maintenance requests |\n| Assets | `\u002Fassets` | GET | Equipment tracking |\n| Locations | `\u002Flocations` | GET | Facility\u002Farea hierarchy |\n| Users | `\u002Fusers` | GET | Team members |\n| Parts | `\u002Fparts` | GET | Inventory items |\n| Procedures | `\u002Fprocedures` | GET | Standard checklists |\n\n## Instructions\n\n### Step 1: Type-Safe API Client\n\n```typescript\n\u002F\u002F src\u002Fapi\u002Fmaintainx-client.ts\nimport axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';\n\n\u002F\u002F Base types\ninterface PaginatedResponse\u003CT> {\n  [key: string]: T[];\n  nextCursor: string | null;\n}\n\ninterface WorkOrder {\n  id: string;\n  title: string;\n  description?: string;\n  status: WorkOrderStatus;\n  priority: WorkOrderPriority;\n  assignees?: User[];\n  asset?: Asset;\n  location?: Location;\n  dueDate?: string;\n  completedAt?: string;\n  createdAt: string;\n  updatedAt: string;\n}\n\ntype WorkOrderStatus = 'OPEN' | 'IN_PROGRESS' | 'ON_HOLD' | 'DONE';\ntype WorkOrderPriority = 'NONE' | 'LOW' | 'MEDIUM' | 'HIGH';\n\ninterface Asset {\n  id: string;\n  name: string;\n  serialNumber?: string;\n  model?: string;\n  manufacturer?: string;\n  location?: Location;\n  status: 'OPERATIONAL' | 'NON_OPERATIONAL' | 'DECOMMISSIONED';\n}\n\ninterface Location {\n  id: string;\n  name: string;\n  address?: string;\n  parentId?: string;\n}\n\ninterface User {\n  id: string;\n  firstName: string;\n  lastName: string;\n  email: string;\n  role: string;\n}\n\n\u002F\u002F Query parameters\ninterface QueryParams {\n  cursor?: string;\n  limit?: number;\n}\n\ninterface WorkOrderQueryParams extends QueryParams {\n  status?: WorkOrderStatus;\n  priority?: WorkOrderPriority;\n  assigneeId?: string;\n  assetId?: string;\n  locationId?: string;\n  createdAfter?: string;\n  createdBefore?: string;\n}\n\ninterface AssetQueryParams extends QueryParams {\n  locationId?: string;\n  status?: Asset['status'];\n}\n\n\u002F\u002F Main client class\nexport class MaintainXClient {\n  private client: AxiosInstance;\n\n  constructor(config?: { apiKey?: string; baseUrl?: string; timeout?: number }) {\n    const apiKey = config?.apiKey || process.env.MAINTAINX_API_KEY;\n    if (!apiKey) {\n      throw new Error('MAINTAINX_API_KEY is required');\n    }\n\n    this.client = axios.create({\n      baseURL: config?.baseUrl || 'https:\u002F\u002Fapi.getmaintainx.com\u002Fv1',\n      timeout: config?.timeout || 30000,\n      headers: {\n        'Authorization': `Bearer ${apiKey}`,\n        'Content-Type': 'application\u002Fjson',\n      },\n    });\n\n    this.setupInterceptors();\n  }\n\n  private setupInterceptors() {\n    \u002F\u002F Request logging\n    this.client.interceptors.request.use(config => {\n      console.debug(`[MaintainX] ${config.method?.toUpperCase()} ${config.url}`);\n      return config;\n    });\n\n    \u002F\u002F Response error handling\n    this.client.interceptors.response.use(\n      response => response,\n      error => {\n        if (error.response) {\n          const { status, data } = error.response;\n          const message = data?.message || data?.error || 'Unknown error';\n          console.error(`[MaintainX] Error ${status}: ${message}`);\n        }\n        throw error;\n      }\n    );\n  }\n\n  \u002F\u002F Work Orders\n  async getWorkOrders(params?: WorkOrderQueryParams): Promise\u003CPaginatedResponse\u003CWorkOrder>> {\n    const response = await this.client.get('\u002Fworkorders', { params });\n    return response.data;\n  }\n\n  async getWorkOrder(id: string): Promise\u003CWorkOrder> {\n    const response = await this.client.get(`\u002Fworkorders\u002F${id}`);\n    return response.data;\n  }\n\n  async createWorkOrder(data: Partial\u003CWorkOrder>): Promise\u003CWorkOrder> {\n    const response = await this.client.post('\u002Fworkorders', data);\n    return response.data;\n  }\n\n  \u002F\u002F Assets\n  async getAssets(params?: AssetQueryParams): Promise\u003CPaginatedResponse\u003CAsset>> {\n    const response = await this.client.get('\u002Fassets', { params });\n    return response.data;\n  }\n\n  async getAsset(id: string): Promise\u003CAsset> {\n    const response = await this.client.get(`\u002Fassets\u002F${id}`);\n    return response.data;\n  }\n\n  \u002F\u002F Locations\n  async getLocations(params?: QueryParams): Promise\u003CPaginatedResponse\u003CLocation>> {\n    const response = await this.client.get('\u002Flocations', { params });\n    return response.data;\n  }\n\n  async getLocation(id: string): Promise\u003CLocation> {\n    const response = await this.client.get(`\u002Flocations\u002F${id}`);\n    return response.data;\n  }\n\n  \u002F\u002F Users\n  async getUsers(params?: QueryParams): Promise\u003CPaginatedResponse\u003CUser>> {\n    const response = await this.client.get('\u002Fusers', { params });\n    return response.data;\n  }\n\n  async getUser(id: string): Promise\u003CUser> {\n    const response = await this.client.get(`\u002Fusers\u002F${id}`);\n    return response.data;\n  }\n}\n```\n\n### Step 2: Cursor-Based Pagination\n\n```typescript\n\u002F\u002F src\u002Futils\u002Fpagination.ts\nimport { MaintainXClient } from '..\u002Fapi\u002Fmaintainx-client';\n\ninterface PaginationOptions {\n  limit?: number;\n  maxPages?: number;\n  delayMs?: number;\n}\n\n\u002F\u002F Generic paginator\nexport async function* paginate\u003CT>(\n  fetchFn: (cursor?: string) => Promise\u003C{ items: T[]; nextCursor: string | null }>,\n  options: PaginationOptions = {}\n): AsyncGenerator\u003CT[], void, unknown> {\n  const { limit = 100, maxPages = Infinity, delayMs = 0 } = options;\n  let cursor: string | undefined;\n  let pageCount = 0;\n\n  do {\n    const response = await fetchFn(cursor);\n    yield response.items;\n\n    cursor = response.nextCursor || undefined;\n    pageCount++;\n\n    if (delayMs > 0 && cursor) {\n      await new Promise(r => setTimeout(r, delayMs));\n    }\n  } while (cursor && pageCount \u003C maxPages);\n}\n\n\u002F\u002F Work order specific paginator\nexport async function getAllWorkOrders(\n  client: MaintainXClient,\n  params: Parameters\u003Ctypeof client.getWorkOrders>[0] = {},\n  options: PaginationOptions = {}\n): Promise\u003CWorkOrder[]> {\n  const allWorkOrders: WorkOrder[] = [];\n\n  for await (const batch of paginate(\n    async (cursor) => {\n      const response = await client.getWorkOrders({ ...params, cursor, limit: options.limit || 100 });\n      return { items: response.workOrders, nextCursor: response.nextCursor };\n    },\n    options\n  )) {\n    allWorkOrders.push(...batch);\n  }\n\n  return allWorkOrders;\n}\n\n\u002F\u002F Usage example\nasync function fetchAllOpenWorkOrders(client: MaintainXClient) {\n  const workOrders = await getAllWorkOrders(\n    client,\n    { status: 'OPEN' },\n    { limit: 100, maxPages: 10 }\n  );\n  console.log(`Fetched ${workOrders.length} open work orders`);\n  return workOrders;\n}\n```\n\n### Step 3: Retry with Exponential Backoff\n\n```typescript\n\u002F\u002F src\u002Futils\u002Fretry.ts\ninterface RetryConfig {\n  maxRetries: number;\n  baseDelayMs: number;\n  maxDelayMs: number;\n  retryableStatuses: number[];\n}\n\nconst defaultConfig: RetryConfig = {\n  maxRetries: 3,\n  baseDelayMs: 1000,\n  maxDelayMs: 30000,\n  retryableStatuses: [429, 500, 502, 503, 504],\n};\n\nexport async function withRetry\u003CT>(\n  operation: () => Promise\u003CT>,\n  config: Partial\u003CRetryConfig> = {}\n): Promise\u003CT> {\n  const { maxRetries, baseDelayMs, maxDelayMs, retryableStatuses } = {\n    ...defaultConfig,\n    ...config,\n  };\n\n  let lastError: Error;\n\n  for (let attempt = 0; attempt \u003C= maxRetries; attempt++) {\n    try {\n      return await operation();\n    } catch (error: any) {\n      lastError = error;\n      const status = error.response?.status;\n\n      \u002F\u002F Don't retry non-retryable errors\n      if (status && !retryableStatuses.includes(status)) {\n        throw error;\n      }\n\n      if (attempt === maxRetries) {\n        throw error;\n      }\n\n      \u002F\u002F Calculate delay with exponential backoff + jitter\n      const exponentialDelay = baseDelayMs * Math.pow(2, attempt);\n      const jitter = Math.random() * 500;\n      const delay = Math.min(exponentialDelay + jitter, maxDelayMs);\n\n      console.log(`Retry ${attempt + 1}\u002F${maxRetries} after ${delay}ms`);\n      await new Promise(r => setTimeout(r, delay));\n    }\n  }\n\n  throw lastError!;\n}\n\n\u002F\u002F Usage\nasync function resilientApiCall(client: MaintainXClient) {\n  return withRetry(\n    () => client.getWorkOrders({ limit: 100 }),\n    { maxRetries: 5 }\n  );\n}\n```\n\n### Step 4: Batch Operations\n\n```typescript\n\u002F\u002F src\u002Futils\u002Fbatch.ts\ninterface BatchConfig {\n  batchSize: number;\n  concurrency: number;\n  delayBetweenBatches: number;\n}\n\nexport async function processBatch\u003CT, R>(\n  items: T[],\n  processor: (item: T) => Promise\u003CR>,\n  config: Partial\u003CBatchConfig> = {}\n): Promise\u003CR[]> {\n  const { batchSize = 10, concurrency = 5, delayBetweenBatches = 100 } = config;\n  const results: R[] = [];\n\n  for (let i = 0; i \u003C items.length; i += batchSize) {\n    const batch = items.slice(i, i + batchSize);\n\n    \u002F\u002F Process batch with limited concurrency\n    const batchResults = await Promise.all(\n      batch.map(item => processor(item))\n    );\n\n    results.push(...batchResults);\n\n    \u002F\u002F Delay between batches to avoid rate limiting\n    if (i + batchSize \u003C items.length) {\n      await new Promise(r => setTimeout(r, delayBetweenBatches));\n    }\n  }\n\n  return results;\n}\n\n\u002F\u002F Create multiple work orders\nasync function createWorkOrders(\n  client: MaintainXClient,\n  workOrderData: Partial\u003CWorkOrder>[]\n): Promise\u003CWorkOrder[]> {\n  return processBatch(\n    workOrderData,\n    data => client.createWorkOrder(data),\n    { batchSize: 5, delayBetweenBatches: 200 }\n  );\n}\n```\n\n### Step 5: Query Builder Pattern\n\n```typescript\n\u002F\u002F src\u002Futils\u002Fquery-builder.ts\nclass WorkOrderQueryBuilder {\n  private params: WorkOrderQueryParams = {};\n\n  status(status: WorkOrderStatus): this {\n    this.params.status = status;\n    return this;\n  }\n\n  priority(priority: WorkOrderPriority): this {\n    this.params.priority = priority;\n    return this;\n  }\n\n  assignedTo(userId: string): this {\n    this.params.assigneeId = userId;\n    return this;\n  }\n\n  forAsset(assetId: string): this {\n    this.params.assetId = assetId;\n    return this;\n  }\n\n  atLocation(locationId: string): this {\n    this.params.locationId = locationId;\n    return this;\n  }\n\n  createdBetween(start: Date, end: Date): this {\n    this.params.createdAfter = start.toISOString();\n    this.params.createdBefore = end.toISOString();\n    return this;\n  }\n\n  limit(count: number): this {\n    this.params.limit = count;\n    return this;\n  }\n\n  build(): WorkOrderQueryParams {\n    return { ...this.params };\n  }\n\n  async execute(client: MaintainXClient) {\n    return client.getWorkOrders(this.build());\n  }\n}\n\n\u002F\u002F Fluent API usage\nasync function queryExample(client: MaintainXClient) {\n  const query = new WorkOrderQueryBuilder()\n    .status('OPEN')\n    .priority('HIGH')\n    .limit(50);\n\n  const results = await query.execute(client);\n  return results;\n}\n```\n\n## Output\n\n- Type-safe MaintainX client with full TypeScript support\n- Cursor-based pagination utilities\n- Retry logic with exponential backoff\n- Batch processing helpers\n- Fluent query builder\n\n## Error Handling\n\n| Pattern | Use Case |\n|---------|----------|\n| Retry with backoff | Transient errors (429, 5xx) |\n| Pagination | Large result sets |\n| Batch processing | Bulk operations |\n| Query builder | Complex filtering |\n\n## Resources\n\n- [MaintainX API Documentation](https:\u002F\u002Fmaintainx.dev\u002F)\n- [REST API Best Practices](https:\u002F\u002Fdocs.github.com\u002Fen\u002Frest\u002Fusing-the-rest-api\u002Fbest-practices-for-using-the-rest-api)\n\n## Next Steps\n\nFor core workflows, see `maintainx-core-workflow-a` (Work Orders) and `maintainx-core-workflow-b` (Assets).\n",{"name":5,"author":52,"license":15,"version":53,"description":54,"allowed-tools":55},"Jeremy Longshore \u003Cjeremy@intentsolutions.io>","1.0.0","Learn MaintainX REST API patterns, pagination, filtering, and client architecture.\nUse when building robust API integrations, implementing pagination,\nor creating reusable SDK patterns for MaintainX.\nTrigger with phrases like \"maintainx sdk\", \"maintainx api patterns\",\n\"maintainx pagination\", \"maintainx filtering\", \"maintainx client design\".\n","Read, Write, Edit, Bash(npm:*), Grep","main","90b50ed0778745a80fd5bfb13b1843d45b9e82f7","tests\u002Fext_conformance\u002Fartifacts\u002Fplugins-community\u002Fplugins\u002Fsaas-packs\u002Fmaintainx-pack\u002Fskills\u002Fmaintainx-sdk-patterns",233,"https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust","https:\u002F\u002Fgithub.com\u002FDicklesworthstone","https:\u002F\u002Fwww.jeffreyemanuel.com\u002F","Building in NY","2026-11-15 01:54:15",{"source":8,"skillName":5,"commands":66,"prompt":76,"zipAvailable":77},[67,70,73],{"tool":68,"command":69},"npx","npx skills add Dicklesworthstone\u002Fpi_agent_rust --skill maintainx-sdk-patterns",{"tool":71,"command":72},"bunx","bunx skills add Dicklesworthstone\u002Fpi_agent_rust --skill maintainx-sdk-patterns",{"tool":74,"command":75},"pnpm","pnpm dlx skills add Dicklesworthstone\u002Fpi_agent_rust --skill maintainx-sdk-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Fplugins-community\u002Fplugins\u002Fsaas-packs\u002Fmaintainx-pack\u002Fskills\u002Fmaintainx-sdk-patterns\u002FSKILL.md",true,[],[80,119,143,169,195,217],{"skillNo":81,"name":82,"nameZh":6,"description":83,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":84,"licenseFileUrl":85,"sourceHtmlUrl":86,"categories":87,"keywords":93,"publishedAt":47},"01M6FEGPPK0ZTKCCM0R3E3KS33","web-artifacts-builder","Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn\u002Fui). Use for complex artifacts requiring state management, routing, or shadcn\u002Fui components - not for simple single-file HTML\u002FJSX artifacts.","Apache-2.0","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Futilities\u002Fweb-artifacts-builder\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Futilities\u002Fweb-artifacts-builder\u002FSKILL.md",[88],{"categoryNo":89,"categoryName":90,"taxonomyType":91,"taxonomyTypeDesc":92},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",[94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,35,112,113,114,115,116,29,117,118],"web artifacts builder","web","artifacts","artifacts builder","builder","frontend","css","components","react","component","routing","state management","state","shadcn","file","ui","Tools","Tailwind","html","single-file","simple","ui components","multi","jsx","suite",{"skillNo":120,"name":121,"nameZh":6,"description":122,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":84,"licenseFileUrl":123,"sourceHtmlUrl":124,"categories":125,"keywords":130,"publishedAt":47},"01M6FEE9VTK1W4GY21AKREJXRG","webapp-testing","Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fsecurity\u002Fwebapp-testing\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fsecurity\u002Fwebapp-testing\u002FSKILL.md",[126,129],{"categoryNo":127,"categoryName":128,"taxonomyType":91,"taxonomyTypeDesc":92},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":89,"categoryName":90,"taxonomyType":91,"taxonomyTypeDesc":92},[131,132,133,99,134,135,95,136,137,138,109,139,140,141,142,31,29],"testing","webapp testing","webapp","screenshots","playwright","debugging","toolkit","browser","logs","applications","behavior","local",{"skillNo":144,"name":145,"nameZh":6,"description":146,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":147,"sourceHtmlUrl":148,"categories":149,"keywords":150,"publishedAt":47},"01M6FE20F7ZTQBFWKF4MPJ2SBX","neuropixels-analysis","Neuropixels neural recording analysis. Load SpikeGLX\u002FOpenEphys data, preprocess, motion correction, Kilosort4 spike sorting, quality metrics, Allen\u002FIBL curation, AI-assisted visual analysis, for Neuropixels 1.0\u002F2.0 extracellular electrophysiology. Use when working with neural recordings, spike sorting, extracellular electrophysiology, or when the user mentions Neuropixels, SpikeGLX, Open Ephys, Kilosort, quality metrics, or unit curation.","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fscientific\u002Fneuropixels-analysis\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fscientific\u002Fneuropixels-analysis\u002FSKILL.md",[],[151,152,153,154,155,156,157,158,35,159,160,161,162,163,164,165,31,166,167,168,29,27],"analysis","neuropixels analysis","spike","metrics","data","open","quality","user","recordings","Neural","motion","visual","quality metrics","unit","sorting","curation","recording","load",{"skillNo":170,"name":171,"nameZh":6,"description":172,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":84,"licenseFileUrl":173,"sourceHtmlUrl":174,"categories":175,"keywords":179,"publishedAt":47},"01M6FDP0PDK6F1GGX395C7625Z","skill-creator","Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fskill-creator\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fskill-creator\u002FSKILL.md",[176],{"categoryNo":177,"categoryName":178,"taxonomyType":91,"taxonomyTypeDesc":92},"01JBSEEDCAT000000000000023","Skill与智能体开发",[180,181,182,183,184,185,186,187,40,188,189,190,191,192,31,29,193,27,194],"skill creator","creator","workflows","create","update","capabilities","knowledge","tool","specialized","guide","new skill","existing skill","users","Existing","new",{"skillNo":196,"name":197,"nameZh":6,"description":198,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":199,"sourceHtmlUrl":200,"categories":201,"keywords":203,"publishedAt":47},"01M6FDM8Y7VK7NYG9SM7QPA3PW","notebooklm","Use this skill to query your Google NotebookLM notebooks directly from Claude Code for source-grounded, citation-backed answers from Gemini. Browser automation, library management, persistent auth. Drastically reduced hallucinations through document-only responses.","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fnotebooklm\u002FLICENSE","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fnotebooklm\u002FSKILL.md",[202],{"categoryNo":177,"categoryName":178,"taxonomyType":91,"taxonomyTypeDesc":92},[197,204,205,206,207,208,209,138,210,211,35,212,213,214,215,29,216],"google","browser automation","gemini","automation","auth","code","library","management","only","document","query","Citation","source",{"skillNo":218,"name":219,"nameZh":6,"description":220,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":84,"licenseFileUrl":221,"sourceHtmlUrl":222,"categories":223,"keywords":230,"publishedAt":47},"01M6FDHPXE10J3XGTCH5HBHS1M","slack-gif-creator","Knowledge and utilities for creating animated GIFs optimized for Slack. Provides constraints, validation tools, and animation concepts. Use when users request animated GIFs for Slack like \"make me a GIF of X doing Y for Slack.\"","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fenterprise-communication\u002Fslack-gif-creator\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fenterprise-communication\u002Fslack-gif-creator\u002FSKILL.md",[224,227],{"categoryNo":225,"categoryName":226,"taxonomyType":91,"taxonomyTypeDesc":92},"01JBSEEDCAT000000000000005","邮件与沟通",{"categoryNo":228,"categoryName":229,"taxonomyType":91,"taxonomyTypeDesc":92},"01JBSEEDCAT000000000000016","图像与多媒体",[231,232,233,181,234,110,235,236,186,35,237,192,238,239,240,29,241,27],"slack gif creator","slack","gif","validation","constraints","Animation","me","make","Utilities","concepts","request"]