ShipStation Bridge Example
ShipStation is a shipping category bridge that demonstrates API key authentication and shipment syncing.
Manifest
const manifest: BridgeManifest = {
name: 'shipstation',
displayName: 'ShipStation',
description: 'Sync shipments and create orders in ShipStation',
version: '1.0.0',
category: 'shipping',
integrationType: 'api',
icon: 'ship',
authMethods: { apiKey: true, accessToken: false, oauth2: false },
configFields: [
{ name: 'apiKey', label: 'API Key', type: 'password', required: true },
{ name: 'apiSecret', label: 'API Secret', type: 'password', required: true },
],
tasks: [
{ name: 'grab-shipments', displayName: 'Grab Shipments', description: 'Fetch shipments from ShipStation', category: 'sync', supportsSchedule: true },
{ name: 'create-shipment', displayName: 'Create Shipment', description: 'Create an order in ShipStation', category: 'push', supportsSchedule: false },
{ name: 'get-rates', displayName: 'Get Rates', description: 'Get shipping rates', category: 'sync', supportsSchedule: false },
],
};Key Implementation Pattern
ShipStation uses Basic authentication with Base64-encoded credentials:
function createHeaders(credentials: Record<string, string>) {
const encoded = btoa(`${credentials.apiKey}:${credentials.apiSecret}`);
return {
'Authorization': `Basic ${encoded}`,
'Content-Type': 'application/json',
};
}Rate Limiting
ShipStation enforces strict rate limits (40 requests per minute). Use the rateLimiting config:
rateLimiting: {
requestsPerSecond: 0.6,
retryAfterHeader: 'X-Rate-Limit-Reset',
}Grab Shipments
async function grabShipments(context: BridgeTaskContext, input: Record<string, unknown>) {
const page = (input.page as number) || 1;
const response = await fetch(
`https://ssapi.shipstation.com/shipments?page=${page}&pageSize=500`,
{ headers: createHeaders(context.config.credentials) }
);
const data = await response.json();
const shipments = data.shipments.map(transformShipStationShipment);
return {
success: true,
data: shipments,
metrics: { recordsProcessed: shipments.length },
pagination: {
hasMore: data.page < data.pages,
nextPage: data.page + 1,
total: data.total,
},
};
}