Quickstart
- SDK Installation & Setup Guide
- Platform Quickstarts
StateSet One
- Guides
- Order Management
- Returns & Warranties
- Supply Chain
- Best Practices
StateSet Response
- Core Concepts
- Guides
StateSet Commerce
- Guides
Supplier Quickstart
Learn how to manage suppliers and purchase orders with the StateSet API
Introduction
The StateSet API provides a comprehensive suite of tools to manage your supplier relationships, including creating and updating suppliers, tracking performance metrics, managing purchase orders (POs), and handling advanced shipment notices (ASNs). This quickstart guide will walk you through key operations to integrate supplier management into your application.
Prerequisites
Before you begin, make sure you have:
- A StateSet account with API access
- Your API key (found in your dashboard)
- Node.js 18+ installed (for SDK usage)
Installation
Install the StateSet Node SDK:
npm install stateset-node
Configuration
Set up your environment variables:
# .env file
STATESET_API_KEY=your_api_key_here
NODE_ENV=production
Initialize the StateSet client with proper error handling:
import { StateSetClient } from 'stateset-node';
class SupplierService {
constructor() {
this.client = new StateSetClient({
apiKey: process.env.STATESET_API_KEY,
environment: process.env.NODE_ENV === 'production' ? 'production' : 'sandbox',
timeout: 30000,
retries: 3
});
this.logger = this.setupLogger();
}
setupLogger() {
// Use your preferred logging service
return {
info: (message, data) => {
if (process.env.NODE_ENV !== 'production') {
console.info(`[INFO] ${message}`, data);
}
// Send to logging service
},
error: (message, error) => {
console.error(`[ERROR] ${message}`, error);
// Send to error tracking service
},
debug: (message, data) => {
if (process.env.NODE_ENV === 'development') {
console.debug(`[DEBUG] ${message}`, data);
}
}
};
}
async checkConnection() {
try {
const health = await this.client.health.check();
this.logger.info('StateSet API connection established', { status: health.status });
return health;
} catch (error) {
this.logger.error('Failed to connect to StateSet API', error);
throw new Error('Unable to establish API connection');
}
}
}
const supplierService = new SupplierService();
Supplier Management
Creating a Supplier
async function createSupplier(supplierData) {
try {
const supplier = await supplierService.client.suppliers.create({
name: supplierData.name,
contact_email: supplierData.contact_email,
contact_phone: supplierData.contact_phone,
address: supplierData.address,
payment_terms: supplierData.payment_terms || 'NET30',
lead_time_days: supplierData.lead_time_days || 7,
minimum_order_value: supplierData.minimum_order_value || 0,
preferred: supplierData.preferred || false,
active: true,
metadata: {
category: supplierData.category,
rating: supplierData.rating
}
});
supplierService.logger.info('Supplier created', {
supplierId: supplier.id,
name: supplier.name
});
return {
success: true,
supplier
};
} catch (error) {
supplierService.logger.error('Failed to create supplier', error);
throw error;
}
}
// Example usage
const newSupplier = await createSupplier({
name: 'Acme Manufacturing Co.',
contact_email: 'orders@acmemfg.com',
contact_phone: '+1-555-0123',
address: {
street: '123 Industrial Way',
city: 'Detroit',
state: 'MI',
postal_code: '48201',
country: 'US'
},
payment_terms: 'NET30',
lead_time_days: 14,
minimum_order_value: 500,
category: 'electronics',
rating: 4.5
});
Updating Supplier Performance Metrics
async function updateSupplierMetrics(supplierId, metrics) {
try {
const updated = await supplierService.client.suppliers.update(supplierId, {
performance_metrics: {
on_time_delivery_rate: metrics.on_time_delivery_rate,
quality_rating: metrics.quality_rating,
response_time_hours: metrics.response_time_hours,
defect_rate: metrics.defect_rate,
last_evaluated: new Date().toISOString()
}
});
supplierService.logger.info('Supplier metrics updated', {
supplierId,
metrics: updated.performance_metrics
});
return updated;
} catch (error) {
supplierService.logger.error('Failed to update supplier metrics', error);
throw error;
}
}
Finding Alternative Suppliers
async function findAlternativeSuppliers(criteria) {
try {
const alternatives = await supplierService.client.suppliers.list({
filter: {
active: true,
'metadata.category': criteria.category,
'performance_metrics.quality_rating': { $gte: criteria.min_quality_rating || 3.5 },
'performance_metrics.on_time_delivery_rate': { $gte: criteria.min_delivery_rate || 0.85 }
},
sort: '-performance_metrics.quality_rating',
limit: criteria.limit || 5
});
supplierService.logger.info('Alternative suppliers found', {
partNumber: criteria.part_number,
count: alternatives.data.length
});
// Calculate scores for each supplier
const scoredSuppliers = alternatives.data.map(supplier => ({
...supplier,
score: calculateSupplierScore(supplier, criteria)
}));
return scoredSuppliers.sort((a, b) => b.score - a.score);
} catch (error) {
supplierService.logger.error('Failed to find alternative suppliers', error);
throw error;
}
}
function calculateSupplierScore(supplier, criteria) {
const weights = {
quality: 0.3,
delivery: 0.3,
price: 0.2,
leadTime: 0.2
};
const scores = {
quality: (supplier.performance_metrics?.quality_rating || 0) / 5,
delivery: supplier.performance_metrics?.on_time_delivery_rate || 0,
price: 1 - (supplier.metadata?.price_index || 1), // Lower price = higher score
leadTime: Math.max(0, 1 - (supplier.lead_time_days / 30))
};
return Object.keys(weights).reduce((total, key) =>
total + (weights[key] * scores[key]), 0
);
}
Purchase Order Management
Creating a Purchase Order
async function createPurchaseOrder(poData) {
try {
// Validate supplier exists and is active
const supplier = await supplierService.client.suppliers.get(poData.supplier_id);
if (!supplier.active) {
throw new Error('Cannot create PO for inactive supplier');
}
// Calculate totals
const subtotal = poData.line_items.reduce((sum, item) =>
sum + (item.quantity * item.unit_price), 0
);
const tax = subtotal * (poData.tax_rate || 0);
const total = subtotal + tax + (poData.shipping_cost || 0);
const purchaseOrder = await supplierService.client.purchaseOrders.create({
supplier_id: poData.supplier_id,
po_number: generatePONumber(),
order_date: new Date().toISOString(),
expected_delivery_date: calculateExpectedDelivery(supplier.lead_time_days),
line_items: poData.line_items.map(item => ({
...item,
subtotal: item.quantity * item.unit_price
})),
subtotal,
tax,
shipping_cost: poData.shipping_cost || 0,
total,
status: 'pending',
payment_terms: supplier.payment_terms,
shipping_address: poData.shipping_address,
notes: poData.notes
});
supplierService.logger.info('Purchase order created', {
poId: purchaseOrder.id,
poNumber: purchaseOrder.po_number,
supplierId: purchaseOrder.supplier_id,
total: purchaseOrder.total
});
// Send notification to supplier
await notifySupplier(purchaseOrder);
return purchaseOrder;
} catch (error) {
supplierService.logger.error('Failed to create purchase order', error);
throw error;
}
}
function generatePONumber() {
const timestamp = Date.now().toString(36);
const random = Math.random().toString(36).substr(2, 5);
return `PO-${timestamp}-${random}`.toUpperCase();
}
function calculateExpectedDelivery(leadTimeDays) {
const date = new Date();
date.setDate(date.getDate() + leadTimeDays);
return date.toISOString();
}
Tracking Purchase Order Status
async function getPurchaseOrderStatus(poId) {
try {
const po = await supplierService.client.purchaseOrders.get(poId);
const statusInfo = {
current_status: po.status,
status_history: po.status_history || [],
expected_delivery: po.expected_delivery_date,
days_until_delivery: calculateDaysUntilDelivery(po.expected_delivery_date),
is_overdue: isOrderOverdue(po),
completion_percentage: calculateCompletionPercentage(po)
};
supplierService.logger.info('Purchase order status retrieved', {
poId,
status: statusInfo.current_status
});
return statusInfo;
} catch (error) {
supplierService.logger.error('Failed to get purchase order status', error);
throw error;
}
}
function calculateDaysUntilDelivery(expectedDate) {
const now = new Date();
const delivery = new Date(expectedDate);
const diffTime = delivery - now;
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
}
function isOrderOverdue(po) {
return po.status !== 'delivered' &&
new Date(po.expected_delivery_date) < new Date();
}
function calculateCompletionPercentage(po) {
const statusProgress = {
'pending': 0,
'confirmed': 20,
'in_production': 40,
'shipped': 80,
'delivered': 100
};
return statusProgress[po.status] || 0;
}
Advanced Shipment Notice (ASN) Management
Creating an ASN
async function createASN(asnData) {
try {
// Validate PO exists and is in correct status
const po = await supplierService.client.purchaseOrders.get(asnData.po_id);
if (!['confirmed', 'in_production'].includes(po.status)) {
throw new Error(`Cannot create ASN for PO in ${po.status} status`);
}
const asn = await supplierService.client.asns.create({
po_id: asnData.po_id,
po_number: po.po_number,
supplier_id: po.supplier_id,
asn_number: generateASNNumber(),
ship_date: asnData.ship_date || new Date().toISOString(),
expected_arrival_date: asnData.expected_arrival_date,
carrier: asnData.carrier,
tracking_number: asnData.tracking_number,
line_items: asnData.line_items.map(item => ({
po_line_item_id: item.po_line_item_id,
part_number: item.part_number,
description: item.description,
quantity_shipped: item.quantity_shipped,
unit_of_measure: item.unit_of_measure
})),
packing_list_number: asnData.packing_list_number,
total_weight: asnData.total_weight,
total_packages: asnData.total_packages,
status: 'in_transit',
notes: asnData.notes
});
// Update PO status
await supplierService.client.purchaseOrders.update(po.id, {
status: 'shipped',
tracking_info: {
carrier: asn.carrier,
tracking_number: asn.tracking_number,
ship_date: asn.ship_date
}
});
supplierService.logger.info('ASN created', {
asnId: asn.id,
asnNumber: asn.asn_number,
poNumber: asn.po_number,
trackingNumber: asn.tracking_number
});
return asn;
} catch (error) {
supplierService.logger.error('Failed to create ASN', error);
throw error;
}
}
function generateASNNumber() {
const date = new Date();
const dateStr = date.toISOString().split('T')[0].replace(/-/g, '');
const random = Math.random().toString(36).substr(2, 4);
return `ASN-${dateStr}-${random}`.toUpperCase();
}
Receiving and Reconciling ASN
async function receiveASN(asnId, receivingData) {
try {
const asn = await supplierService.client.asns.get(asnId);
// Record receiving details
const receiving = await supplierService.client.asns.update(asnId, {
status: 'received',
received_date: new Date().toISOString(),
received_by: receivingData.received_by,
receiving_notes: receivingData.notes,
line_items: asn.line_items.map(item => {
const receivedItem = receivingData.items.find(
r => r.po_line_item_id === item.po_line_item_id
);
return {
...item,
quantity_received: receivedItem?.quantity_received || 0,
condition: receivedItem?.condition || 'good',
discrepancy_notes: receivedItem?.discrepancy_notes
};
})
});
// Reconcile with PO
const reconciliationResult = await reconcileASNWithPO(asn.id, asn.po_id);
supplierService.logger.info('ASN received and reconciled', {
asnId,
poId: asn.po_id,
hasDiscrepancies: reconciliationResult.has_discrepancies
});
return {
asn: receiving,
reconciliation: reconciliationResult
};
} catch (error) {
supplierService.logger.error('Failed to receive ASN', error);
throw error;
}
}
async function reconcileASNWithPO(asnId, poId) {
try {
const [asn, po] = await Promise.all([
supplierService.client.asns.get(asnId),
supplierService.client.purchaseOrders.get(poId)
]);
const discrepancies = [];
// Check each line item
asn.line_items.forEach(asnItem => {
const poItem = po.line_items.find(
p => p.id === asnItem.po_line_item_id
);
if (!poItem) {
discrepancies.push({
type: 'item_not_in_po',
part_number: asnItem.part_number,
message: 'Item in ASN not found in PO'
});
return;
}
const quantityDiff = asnItem.quantity_received - poItem.quantity;
if (quantityDiff !== 0) {
discrepancies.push({
type: 'quantity_mismatch',
part_number: asnItem.part_number,
po_quantity: poItem.quantity,
received_quantity: asnItem.quantity_received,
difference: quantityDiff
});
}
if (asnItem.condition !== 'good') {
discrepancies.push({
type: 'condition_issue',
part_number: asnItem.part_number,
condition: asnItem.condition,
notes: asnItem.discrepancy_notes
});
}
});
const result = {
asn_id: asnId,
po_id: poId,
has_discrepancies: discrepancies.length > 0,
discrepancies,
reconciled_at: new Date().toISOString()
};
// Update PO status based on reconciliation
const newStatus = discrepancies.length > 0 ? 'received_with_issues' : 'received';
await supplierService.client.purchaseOrders.update(poId, {
status: newStatus,
reconciliation_result: result
});
return result;
} catch (error) {
supplierService.logger.error('Failed to reconcile ASN with PO', error);
throw error;
}
}
Analytics and Reporting
Safety Stock Status
async function getSafetyStockStatus() {
try {
const inventory = await supplierService.client.inventory.list({
filter: {
quantity: { $lte: 'reorder_point' }
},
include: ['supplier']
});
const criticalItems = inventory.data.map(item => ({
part_number: item.part_number,
description: item.description,
current_quantity: item.quantity,
reorder_point: item.reorder_point,
safety_stock: item.safety_stock,
days_of_supply: calculateDaysOfSupply(item),
supplier: item.supplier,
urgency: calculateUrgency(item)
}));
supplierService.logger.info('Safety stock status retrieved', {
criticalItemsCount: criticalItems.length
});
return criticalItems.sort((a, b) => b.urgency - a.urgency);
} catch (error) {
supplierService.logger.error('Failed to get safety stock status', error);
throw error;
}
}
function calculateDaysOfSupply(item) {
if (!item.average_daily_usage || item.average_daily_usage === 0) return Infinity;
return Math.floor(item.quantity / item.average_daily_usage);
}
function calculateUrgency(item) {
const stockPercentage = item.quantity / item.reorder_point;
const daysOfSupply = calculateDaysOfSupply(item);
if (stockPercentage <= 0.25 || daysOfSupply <= 3) return 5; // Critical
if (stockPercentage <= 0.5 || daysOfSupply <= 7) return 4; // High
if (stockPercentage <= 0.75 || daysOfSupply <= 14) return 3; // Medium
if (stockPercentage <= 1 || daysOfSupply <= 21) return 2; // Low
return 1; // Normal
}
Supply Chain KPIs
async function getSupplyChainKPIs(params = {}) {
try {
const timeframe = params.timeframe || '30d';
const startDate = calculateStartDate(timeframe);
const [suppliers, purchaseOrders, asns] = await Promise.all([
supplierService.client.suppliers.list({ filter: { active: true } }),
supplierService.client.purchaseOrders.list({
filter: { created_at: { $gte: startDate } }
}),
supplierService.client.asns.list({
filter: { created_at: { $gte: startDate } }
})
]);
const kpis = {
timeframe,
supplier_performance: calculateSupplierPerformanceKPIs(suppliers.data),
order_metrics: calculateOrderMetrics(purchaseOrders.data),
delivery_metrics: calculateDeliveryMetrics(asns.data),
cost_metrics: calculateCostMetrics(purchaseOrders.data),
generated_at: new Date().toISOString()
};
supplierService.logger.info('Supply chain KPIs calculated', {
timeframe,
supplierCount: suppliers.data.length
});
return kpis;
} catch (error) {
supplierService.logger.error('Failed to calculate supply chain KPIs', error);
throw error;
}
}
function calculateSupplierPerformanceKPIs(suppliers) {
const activeSuppliers = suppliers.filter(s => s.active);
const avgMetrics = activeSuppliers.reduce((acc, supplier) => {
const metrics = supplier.performance_metrics || {};
return {
quality_rating: acc.quality_rating + (metrics.quality_rating || 0),
on_time_delivery: acc.on_time_delivery + (metrics.on_time_delivery_rate || 0),
defect_rate: acc.defect_rate + (metrics.defect_rate || 0)
};
}, { quality_rating: 0, on_time_delivery: 0, defect_rate: 0 });
const count = activeSuppliers.length || 1;
return {
average_quality_rating: (avgMetrics.quality_rating / count).toFixed(2),
average_on_time_delivery: (avgMetrics.on_time_delivery / count * 100).toFixed(1) + '%',
average_defect_rate: (avgMetrics.defect_rate / count * 100).toFixed(2) + '%',
total_active_suppliers: activeSuppliers.length
};
}
Error Handling and Monitoring
Implement comprehensive error handling and monitoring:
class SupplierAPIError extends Error {
constructor(message, code, details) {
super(message);
this.name = 'SupplierAPIError';
this.code = code;
this.details = details;
this.timestamp = new Date().toISOString();
}
}
async function withErrorHandling(operation, context) {
const startTime = Date.now();
try {
const result = await operation();
// Track successful operations
supplierService.logger.info('Operation completed', {
context,
duration: Date.now() - startTime
});
return result;
} catch (error) {
// Categorize and handle different error types
if (error.response?.status === 429) {
throw new SupplierAPIError(
'Rate limit exceeded',
'RATE_LIMIT',
{ retryAfter: error.response.headers['retry-after'] }
);
}
if (error.response?.status === 404) {
throw new SupplierAPIError(
'Resource not found',
'NOT_FOUND',
{ resource: context }
);
}
// Log and re-throw
supplierService.logger.error('Operation failed', {
context,
error: error.message,
duration: Date.now() - startTime
});
throw error;
}
}
// Usage example
const supplier = await withErrorHandling(
() => createSupplier(supplierData),
'create_supplier'
);
Best Practices
- Always use environment variables for sensitive configuration
- Implement proper logging instead of console.log statements
- Handle errors gracefully with meaningful error messages
- Use transactions for operations that modify multiple resources
- Implement retry logic for transient failures
- Monitor API usage to stay within rate limits
- Cache frequently accessed data to improve performance
- Validate data before sending to the API
- Use webhook events for real-time updates instead of polling
- Document your integration for future maintenance
Next Steps
Now that you understand the basics of supplier management with StateSet:
- Explore advanced features like automated reordering and demand forecasting
- Set up webhooks to receive real-time updates on PO and ASN status changes
- Integrate with your ERP for seamless data synchronization
- Build custom dashboards using the analytics data
- Implement approval workflows for purchase orders
For more information, check out:
If you need help, contact support@stateset.com or join our Discord community.
- Introduction
- Prerequisites
- Installation
- Configuration
- Supplier Management
- Creating a Supplier
- Updating Supplier Performance Metrics
- Finding Alternative Suppliers
- Purchase Order Management
- Creating a Purchase Order
- Tracking Purchase Order Status
- Advanced Shipment Notice (ASN) Management
- Creating an ASN
- Receiving and Reconciling ASN
- Analytics and Reporting
- Safety Stock Status
- Supply Chain KPIs
- Error Handling and Monitoring
- Best Practices
- Next Steps