agentsclimarketplace

Salesloft sdk patterns

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/salesloft-pack/skills/salesloft-sdk-patterns

'Apply production-ready SalesLoft API patterns for TypeScript and Python.From its SKILL.md

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill salesloft-sdk-patterns

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

5.2 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it

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

// 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<T> {
  data: T[];
  metadata: { paging: SalesloftPaging };
}

interface SalesloftSingleResponse<T> {
  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

// SalesLoft paginates with page/per_page params, max 100 per page
async function* paginate<T>(
  endpoint: string,
  params: Record<string, any> = {},
): AsyncGenerator<T> {
  const client = getSalesloftClient();
  let page = 1;
  let totalPages = 1;

  do {
    const { data: response } = await client.get<SalesloftListResponse<T>>(
      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<Person>('/people.json')) {
  console.log(person.display_name);
}

Step 3: Error Handling with Rate-Limit Awareness

// 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

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

StatusMeaningRetryableCost
401Invalid/expired tokenNo (refresh token)0
404Resource not foundNo1
422Validation errorNo (fix payload)1
429Rate limitedYes (wait Retry-After)0
5xxServer errorYes (backoff)1

Resources

Next Steps

Apply patterns in salesloft-core-workflow-a for real-world usage.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,144. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.