Claude-code-plugins-plus flexport-sdk-patterns
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/flexport-pack/skills/flexport-sdk-patterns" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-flexport-sdk-patterns && rm -rf "$T"
manifest:
plugins/saas-packs/flexport-pack/skills/flexport-sdk-patterns/SKILL.mdsource content
Flexport SDK Patterns
Overview
Production-ready patterns for the Flexport REST API v2. Since Flexport has no official npm/pip SDK, you build typed HTTP clients. Key patterns: singleton client, paginated iteration, retry wrapper, and Zod response validation.
Instructions
Pattern 1: Singleton Client with Auto-Retry
// src/flexport/client.ts import { z } from 'zod'; class FlexportClient { private static instance: FlexportClient | null = null; private base = 'https://api.flexport.com'; private headers: Record<string, string>; private constructor(apiKey: string) { this.headers = { 'Authorization': `Bearer ${apiKey}`, 'Flexport-Version': '2', 'Content-Type': 'application/json', }; } static getInstance(): FlexportClient { if (!this.instance) { const key = process.env.FLEXPORT_API_KEY; if (!key) throw new Error('Missing FLEXPORT_API_KEY'); this.instance = new FlexportClient(key); } return this.instance; } async request<T>(path: string, options: RequestInit = {}): Promise<T> { const res = await fetch(`${this.base}${path}`, { ...options, headers: { ...this.headers, ...options.headers } }); if (res.status === 429) { const retryAfter = parseInt(res.headers.get('Retry-After') || '60'); await new Promise(r => setTimeout(r, retryAfter * 1000)); return this.request(path, options); // Retry once } if (!res.ok) { const body = await res.text(); throw new FlexportAPIError(res.status, body, path); } return res.json(); } } class FlexportAPIError extends Error { constructor(public status: number, public body: string, public path: string) { super(`Flexport ${status} on ${path}: ${body}`); this.name = 'FlexportAPIError'; } }
Pattern 2: Paginated Iterator
// Iterate all pages of a Flexport list endpoint async function* paginate<T>(path: string, perPage = 25): AsyncGenerator<T> { const client = FlexportClient.getInstance(); let page = 1; while (true) { const separator = path.includes('?') ? '&' : '?'; const res = await client.request<{ data: { records: T[]; total_count: number } }>( `${path}${separator}page=${page}&per=${perPage}` ); for (const record of res.data.records) yield record; if (res.data.records.length < perPage) break; page++; } } // Usage: iterate all shipments for await (const shipment of paginate<Shipment>('/shipments')) { console.log(shipment.id, shipment.status); }
Pattern 3: Zod Response Validation
const ShipmentSchema = z.object({ id: z.string(), status: z.enum(['booked', 'in_transit', 'arrived', 'delivered']), freight_type: z.enum(['ocean', 'air', 'trucking']), origin_port: z.object({ code: z.string(), name: z.string() }), destination_port: z.object({ code: z.string(), name: z.string() }), cargo_ready_date: z.string(), estimated_arrival_date: z.string().nullable(), }); type Shipment = z.infer<typeof ShipmentSchema>; async function getShipment(id: string): Promise<Shipment> { const client = FlexportClient.getInstance(); const res = await client.request<{ data: unknown }>(`/shipments/${id}`); return ShipmentSchema.parse(res.data); // Throws ZodError on mismatch }
Pattern 4: Python Typed Client
import os, requests from dataclasses import dataclass from typing import Iterator @dataclass class Shipment: id: str status: str freight_type: str class FlexportClient: BASE = 'https://api.flexport.com' def __init__(self): self.session = requests.Session() self.session.headers.update({ 'Authorization': f'Bearer {os.environ["FLEXPORT_API_KEY"]}', 'Flexport-Version': '2', }) def list_shipments(self, per: int = 25) -> Iterator[Shipment]: page = 1 while True: r = self.session.get(f'{self.BASE}/shipments', params={'page': page, 'per': per}) r.raise_for_status() records = r.json()['data']['records'] for rec in records: yield Shipment(id=rec['id'], status=rec['status'], freight_type=rec['freight_type']) if len(records) < per: break page += 1
Error Handling
| Pattern | Use Case | Benefit |
|---|---|---|
| Singleton | All API calls | One instance, consistent config |
| Paginator | List endpoints | No data loss from pagination |
| Zod validation | Response parsing | Catches API contract changes early |
| Error class | All failures | Structured error data for logging |
Resources
Next Steps
Apply patterns in
flexport-core-workflow-a for real-world usage.