--- name: salesloft-sdk-patterns description: 'Apply production-ready SalesLoft API patterns for TypeScript and Python. Use when building SalesLoft integrations, implementing pagination, or wrapping the REST API v2 with typed clients. Trigger: "salesloft SDK patterns", "salesloft best practices", "salesloft client wrapper". ' allowed-tools: Read, Write, Edit version: 1.0.0 license: MIT author: Jeremy Longshore tags: - saas - sales - outreach - salesloft compatibility: Designed for Claude Code --- # SalesLoft SDK Patterns ## Overview Production-ready patterns for the SalesLoft REST API v2. There is no official TypeScript/Python SDK -- build a typed wrapper around `https://api.salesloft.com/v2/` with automatic pagination, retry, and error normalization. ## Prerequisites - Completed `salesloft-install-auth` setup - `axios` or `node-fetch` installed - Familiarity with async/await and generics ## Instructions ### Step 1: Typed API Client Singleton ```typescript // src/salesloft/client.ts import axios, { AxiosInstance, AxiosError } from 'axios'; interface SalesloftPaging { per_page: number; current_page: number; total_pages: number; total_count: number; } interface SalesloftListResponse { data: T[]; metadata: { paging: SalesloftPaging }; } interface SalesloftSingleResponse { data: T; } let instance: AxiosInstance | null = null; export function getSalesloftClient(): AxiosInstance { if (!instance) { instance = axios.create({ baseURL: process.env.SALESLOFT_BASE_URL || 'https://api.salesloft.com/v2', headers: { Authorization: `Bearer ${process.env.SALESLOFT_API_KEY}` }, timeout: 30_000, }); // Add response interceptor for rate-limit headers instance.interceptors.response.use(undefined, handleRateLimitError); } return instance; } ``` ### Step 2: Automatic Pagination Iterator ```typescript // SalesLoft paginates with page/per_page params, max 100 per page async function* paginate( endpoint: string, params: Record = {}, ): AsyncGenerator { const client = getSalesloftClient(); let page = 1; let totalPages = 1; do { const { data: response } = await client.get>( endpoint, { params: { ...params, per_page: 100, page } } ); for (const item of response.data) yield item; totalPages = response.metadata.paging.total_pages; page++; } while (page <= totalPages); } // Usage: iterate all people for await (const person of paginate('/people.json')) { console.log(person.display_name); } ``` ### Step 3: Error Handling with Rate-Limit Awareness ```typescript // SalesLoft uses cost-based rate limiting: 600 cost/min // High-page requests (page > 100) cost 3-30 points instead of 1 async function handleRateLimitError(error: AxiosError) { if (error.response?.status === 429) { const retryAfter = parseInt( error.response.headers['retry-after'] || '60', 10 ); console.warn(`Rate limited. Waiting ${retryAfter}s...`); await new Promise(r => setTimeout(r, retryAfter * 1000)); return getSalesloftClient().request(error.config!); } throw error; } class SalesloftApiError extends Error { constructor( message: string, public status: number, public retryable: boolean, ) { super(message); this.name = 'SalesloftApiError'; } } ``` ### Step 4: Python Equivalent ```python import os, time, requests from typing import Iterator, Any class SalesloftClient: BASE_URL = "https://api.salesloft.com/v2" def __init__(self, api_key: str | None = None): self.session = requests.Session() self.session.headers["Authorization"] = f"Bearer {api_key or os.environ['SALESLOFT_API_KEY']}" def get(self, endpoint: str, **params) -> dict: resp = self.session.get(f"{self.BASE_URL}/{endpoint}", params=params) if resp.status_code == 429: time.sleep(int(resp.headers.get("Retry-After", 60))) return self.get(endpoint, **params) resp.raise_for_status() return resp.json() def paginate(self, endpoint: str, **params) -> Iterator[dict]: page = 1 while True: result = self.get(endpoint, page=page, per_page=100, **params) yield from result["data"] if page >= result["metadata"]["paging"]["total_pages"]: break page += 1 ``` ## Output - Type-safe client singleton with rate-limit retry - Automatic pagination that handles all pages - Error normalization for consistent handling - Python and TypeScript implementations ## Error Handling | Status | Meaning | Retryable | Cost | |--------|---------|-----------|------| | `401` | Invalid/expired token | No (refresh token) | 0 | | `404` | Resource not found | No | 1 | | `422` | Validation error | No (fix payload) | 1 | | `429` | Rate limited | Yes (wait `Retry-After`) | 0 | | `5xx` | Server error | Yes (backoff) | 1 | ## Resources - [SalesLoft API Basics](https://developers.salesloft.com/docs/platform/api-basics/) - [Rate Limits](https://developers.salesloft.com/docs/platform/api-basics/rate-limits/) - [People Endpoint](https://developers.salesloft.com/docs/api/people-index/) ## Next Steps Apply patterns in `salesloft-core-workflow-a` for real-world usage.