Flexport sdk patterns
'Apply production-ready Flexport API patterns for TypeScript and Python.From its SKILL.md
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill flexport-sdk-patternsAssembled 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.3 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it
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.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.