Time to first API call: Less than 5 minutes
Prerequisites: Basic programming knowledge and a StateSet account
Prerequisites: Basic programming knowledge and a StateSet account
Overview
This quickstart guide will walk you through making your first StateSet API call, then progressively build up to a complete integration. By the end, you’ll be able to:- ✅ Authenticate with the API
- ✅ Create and manage orders
- ✅ Handle webhooks
- ✅ Implement error handling
- ✅ Use advanced features
Step 1: Get Your API Keys
Sign up for StateSet
Create your account at stateset.com/sign-up
Security First: Never commit API keys to version control. Use environment variables instead.
Step 2: Set Up Your Environment
- Node.js
- Python
- cURL
# Install the StateSet SDK
npm install stateset-node dotenv
# Create .env file
echo "STATESET_API_KEY=sk_test_your_key_here" > .env
// index.js
require('dotenv').config();
const { StateSetClient } = require('stateset-node');
const stateset = new StateSetClient({
apiKey: process.env.STATESET_API_KEY
});
console.log('StateSet client initialized!');
# Install the StateSet SDK
pip install stateset-python python-dotenv
# Create .env file
echo "STATESET_API_KEY=sk_test_your_key_here" > .env
# main.py
import os
from dotenv import load_dotenv
from stateset import StateSet
load_dotenv()
stateset = StateSet(
api_key=os.getenv('STATESET_API_KEY')
)
print('StateSet client initialized!')
# Set your API key as an environment variable
export STATESET_API_KEY="sk_test_your_key_here"
# Test the connection
curl https://api.stateset.com/v1/ping \
-H "Authorization: Bearer $STATESET_API_KEY"
Step 3: Make Your First API Call
Let’s start with a simple request to list orders:async function getOrders() {
try {
const orders = await stateset.orders.list({
limit: 10,
status: 'pending'
});
console.log(`Found ${orders.data.length} orders`);
orders.data.forEach(order => {
console.log(`- Order ${order.order_number}: ${order.status}`);
});
} catch (error) {
console.error('Error fetching orders:', error.message);
}
}
getOrders();
def get_orders():
try:
orders = stateset.orders.list(
limit=10,
status='pending'
)
print(f"Found {len(orders.data)} orders")
for order in orders.data:
print(f"- Order {order.order_number}: {order.status}")
except Exception as e:
print(f"Error fetching orders: {e}")
get_orders()
curl https://api.stateset.com/v1/orders?limit=10&status=pending \
-H "Authorization: Bearer $STATESET_API_KEY" \
-H "Content-Type: application/json"
Step 4: Create Your First Resource
Now let’s create an order:async function createOrder() {
try {
const order = await stateset.orders.create({
customer: {
email: 'customer@example.com',
first_name: 'John',
last_name: 'Doe'
},
items: [
{
sku: 'WIDGET-001',
quantity: 2,
price: 2999, // in cents
name: 'Premium Widget'
}
],
shipping_address: {
line1: '123 Main St',
city: 'San Francisco',
state: 'CA',
postal_code: '94105',
country: 'US'
},
payment: {
method: 'card',
payment_intent_id: 'pi_test_123'
}
});
console.log('Order created successfully!');
console.log(`Order ID: ${order.id}`);
console.log(`Order Number: ${order.order_number}`);
console.log(`Total: $${(order.totals.total / 100).toFixed(2)}`);
return order;
} catch (error) {
console.error('Error creating order:', error.message);
if (error.details) {
console.error('Validation errors:', error.details);
}
}
}
createOrder();
def create_order():
try:
order = stateset.orders.create(
customer={
'email': 'customer@example.com',
'first_name': 'John',
'last_name': 'Doe'
},
items=[
{
'sku': 'WIDGET-001',
'quantity': 2,
'price': 2999, # in cents
'name': 'Premium Widget'
}
],
shipping_address={
'line1': '123 Main St',
'city': 'San Francisco',
'state': 'CA',
'postal_code': '94105',
'country': 'US'
},
payment={
'method': 'card',
'payment_intent_id': 'pi_test_123'
}
)
print('Order created successfully!')
print(f'Order ID: {order.id}')
print(f'Order Number: {order.order_number}')
print(f'Total: ${order.totals.total / 100:.2f}')
return order
except Exception as e:
print(f'Error creating order: {e}')
if hasattr(e, 'details'):
print(f'Validation errors: {e.details}')
create_order()
Step 5: Handle Webhooks
Set up webhook handling for real-time events:const express = require('express');
const crypto = require('crypto');
const app = express();
// Middleware to capture raw body for signature verification
app.use('/webhooks', express.raw({ type: 'application/json' }));
function verifyWebhookSignature(payload, signature, secret) {
const elements = signature.split(' ');
const timestamp = elements.find(e => e.startsWith('t=')).slice(2);
const signatures = elements.filter(e => e.startsWith('v1='));
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
return signatures.some(sig =>
sig.slice(3) === expectedSignature
);
}
app.post('/webhooks/stateset', async (req, res) => {
const signature = req.headers['stateset-signature'];
const secret = process.env.STATESET_WEBHOOK_SECRET;
try {
// Verify webhook signature
if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body);
// Handle different event types
switch (event.type) {
case 'order.created':
console.log('New order:', event.data.object.id);
await handleNewOrder(event.data.object);
break;
case 'order.shipped':
console.log('Order shipped:', event.data.object.id);
await notifyCustomerShipped(event.data.object);
break;
case 'return.created':
console.log('Return initiated:', event.data.object.id);
await processReturn(event.data.object);
break;
default:
console.log('Unhandled event type:', event.type);
}
res.json({ received: true });
} catch (error) {
console.error('Webhook error:', error);
res.status(400).json({ error: 'Webhook processing failed' });
}
});
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});
from flask import Flask, request, jsonify
import hmac
import hashlib
import json
import os
app = Flask(__name__)
def verify_webhook_signature(payload, signature, secret):
elements = signature.split(' ')
timestamp = next(e[2:] for e in elements if e.startswith('t='))
signatures = [e[3:] for e in elements if e.startswith('v1=')]
signed_payload = f"{timestamp}.{payload}"
expected_sig = hmac.new(
secret.encode(),
signed_payload.encode(),
hashlib.sha256
).hexdigest()
return expected_sig in signatures
@app.route('/webhooks/stateset', methods=['POST'])
def handle_webhook():
signature = request.headers.get('stateset-signature')
secret = os.getenv('STATESET_WEBHOOK_SECRET')
payload = request.get_data(as_text=True)
try:
# Verify webhook signature
if not verify_webhook_signature(payload, signature, secret):
return jsonify({'error': 'Invalid signature'}), 401
event = json.loads(payload)
# Handle different event types
if event['type'] == 'order.created':
print(f"New order: {event['data']['object']['id']}")
handle_new_order(event['data']['object'])
elif event['type'] == 'order.shipped':
print(f"Order shipped: {event['data']['object']['id']}")
notify_customer_shipped(event['data']['object'])
elif event['type'] == 'return.created':
print(f"Return initiated: {event['data']['object']['id']}")
process_return(event['data']['object'])
else:
print(f"Unhandled event type: {event['type']}")
return jsonify({'received': True})
except Exception as e:
print(f"Webhook error: {e}")
return jsonify({'error': 'Webhook processing failed'}), 400
if __name__ == '__main__':
app.run(port=3000)
Step 6: Implement Error Handling
Add robust error handling to your integration:class StateSetAPIHandler {
constructor(apiKey) {
this.client = new StateSetClient({ apiKey });
this.maxRetries = 3;
}
async executeWithRetry(operation, data) {
let lastError;
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
return await operation(data);
} catch (error) {
lastError = error;
// Don't retry on validation errors
if (error.code === 'VALIDATION_ERROR') {
throw error;
}
// Retry with exponential backoff for rate limits
if (error.code === 'RATE_LIMITED' && attempt < this.maxRetries) {
const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
console.log(`Rate limited. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
// Retry on network errors
if (error.code === 'NETWORK_ERROR' && attempt < this.maxRetries) {
const delay = 1000 * attempt;
console.log(`Network error. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
}
}
throw lastError;
}
async createOrder(orderData) {
return this.executeWithRetry(
async (data) => await this.client.orders.create(data),
orderData
);
}
handleError(error) {
const errorHandlers = {
'VALIDATION_ERROR': () => {
console.error('Validation failed:', error.details);
// Show validation errors to user
},
'INSUFFICIENT_INVENTORY': () => {
console.error('Not enough inventory:', error.details);
// Update UI with available quantity
},
'PAYMENT_FAILED': () => {
console.error('Payment failed:', error.message);
// Prompt for different payment method
},
'RATE_LIMITED': () => {
console.error('Rate limited. Please slow down requests.');
// Implement request queuing
},
'UNAUTHORIZED': () => {
console.error('Authentication failed. Check API key.');
// Refresh authentication
}
};
const handler = errorHandlers[error.code] || (() => {
console.error('Unexpected error:', error.message);
});
handler();
}
}
import time
from typing import Optional, Callable, Any
class StateSetAPIHandler:
def __init__(self, api_key: str):
self.client = StateSet(api_key=api_key)
self.max_retries = 3
def execute_with_retry(
self,
operation: Callable,
data: Any,
max_retries: Optional[int] = None
) -> Any:
max_retries = max_retries or self.max_retries
last_error = None
for attempt in range(1, max_retries + 1):
try:
return operation(data)
except Exception as error:
last_error = error
# Don't retry on validation errors
if hasattr(error, 'code') and error.code == 'VALIDATION_ERROR':
raise error
# Retry with exponential backoff for rate limits
if hasattr(error, 'code') and error.code == 'RATE_LIMITED':
if attempt < max_retries:
delay = min(2 ** attempt, 10)
print(f"Rate limited. Retrying in {delay}s...")
time.sleep(delay)
continue
# Retry on network errors
if hasattr(error, 'code') and error.code == 'NETWORK_ERROR':
if attempt < max_retries:
delay = attempt
print(f"Network error. Retrying in {delay}s...")
time.sleep(delay)
continue
raise last_error
def create_order(self, order_data: dict) -> dict:
return self.execute_with_retry(
lambda data: self.client.orders.create(**data),
order_data
)
def handle_error(self, error: Exception) -> None:
error_handlers = {
'VALIDATION_ERROR': lambda: print(f"Validation failed: {error.details}"),
'INSUFFICIENT_INVENTORY': lambda: print(f"Not enough inventory: {error.details}"),
'PAYMENT_FAILED': lambda: print(f"Payment failed: {error.message}"),
'RATE_LIMITED': lambda: print("Rate limited. Please slow down requests."),
'UNAUTHORIZED': lambda: print("Authentication failed. Check API key.")
}
handler = error_handlers.get(
getattr(error, 'code', 'UNKNOWN'),
lambda: print(f"Unexpected error: {error}")
)
handler()
Step 7: Use Advanced Features
Pagination
Handle large datasets efficiently:async function getAllOrders() {
const allOrders = [];
let cursor = null;
do {
const response = await stateset.orders.list({
limit: 100,
cursor: cursor
});
allOrders.push(...response.data);
cursor = response.pagination.next_cursor;
} while (cursor);
console.log(`Total orders: ${allOrders.length}`);
return allOrders;
}
Filtering and Searching
// Complex filtering
const orders = await stateset.orders.list({
status_in: ['pending', 'processing'],
created_after: '2024-01-01T00:00:00Z',
total_amount_gte: 10000, // $100.00 in cents
customer_email_like: '%@example.com',
sort: 'created_at',
order: 'desc'
});
// Full-text search
const searchResults = await stateset.orders.search({
query: 'john doe widget',
fields: ['customer.email', 'customer.name', 'items.name'],
limit: 20
});
Batch Operations
// Batch update multiple orders
const updates = await stateset.orders.batchUpdate({
filters: {
status: 'pending',
created_before: '2024-01-01T00:00:00Z'
},
updates: {
status: 'cancelled',
metadata: {
cancelled_reason: 'expired'
}
}
});
console.log(`Updated ${updates.affected_count} orders`);
Idempotency
Ensure safe retries with idempotency keys:const { v4: uuidv4 } = require('uuid');
async function createOrderSafely(orderData) {
const idempotencyKey = `order-${uuidv4()}`;
try {
return await stateset.orders.create(orderData, {
idempotencyKey: idempotencyKey
});
} catch (error) {
if (error.code === 'DUPLICATE_REQUEST') {
console.log('Order already created:', error.details.existing_id);
return error.details.existing_resource;
}
throw error;
}
}
Complete Example: Order Management System
Here’s a complete example that ties everything together:const { StateSetClient } = require('stateset-node');
const express = require('express');
require('dotenv').config();
class OrderManagementSystem {
constructor() {
this.stateset = new StateSetClient({
apiKey: process.env.STATESET_API_KEY
});
this.app = express();
this.setupRoutes();
}
setupRoutes() {
this.app.use(express.json());
// Create order endpoint
this.app.post('/api/orders', async (req, res) => {
try {
const order = await this.createOrder(req.body);
res.json({ success: true, order });
} catch (error) {
this.handleApiError(error, res);
}
});
// Get orders endpoint
this.app.get('/api/orders', async (req, res) => {
try {
const orders = await this.getOrders(req.query);
res.json({ success: true, orders });
} catch (error) {
this.handleApiError(error, res);
}
});
// Update order status
this.app.patch('/api/orders/:id/status', async (req, res) => {
try {
const order = await this.updateOrderStatus(
req.params.id,
req.body.status
);
res.json({ success: true, order });
} catch (error) {
this.handleApiError(error, res);
}
});
// Webhook endpoint
this.app.post('/webhooks/stateset',
express.raw({ type: 'application/json' }),
async (req, res) => {
await this.handleWebhook(req, res);
}
);
}
async createOrder(orderData) {
// Validate order data
this.validateOrderData(orderData);
// Check inventory
await this.checkInventory(orderData.items);
// Create order with idempotency
const idempotencyKey = `order-${Date.now()}-${orderData.customer.email}`;
const order = await this.stateset.orders.create(orderData, {
idempotencyKey
});
// Send confirmation email
await this.sendOrderConfirmation(order);
return order;
}
async getOrders(filters) {
const orders = await this.stateset.orders.list({
limit: filters.limit || 20,
status: filters.status,
created_after: filters.from_date,
created_before: filters.to_date,
sort: filters.sort || 'created_at',
order: filters.order || 'desc'
});
return orders;
}
async updateOrderStatus(orderId, newStatus) {
// Validate status transition
const order = await this.stateset.orders.get(orderId);
this.validateStatusTransition(order.status, newStatus);
// Update order
const updated = await this.stateset.orders.update(orderId, {
status: newStatus
});
// Trigger relevant workflows
await this.triggerStatusWorkflow(updated);
return updated;
}
async handleWebhook(req, res) {
// Verify signature
if (!this.verifyWebhook(req)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body);
try {
switch (event.type) {
case 'order.created':
await this.onOrderCreated(event.data.object);
break;
case 'order.shipped':
await this.onOrderShipped(event.data.object);
break;
case 'return.created':
await this.onReturnCreated(event.data.object);
break;
}
res.json({ received: true });
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).json({ error: 'Processing failed' });
}
}
validateOrderData(data) {
const required = ['customer', 'items', 'shipping_address'];
for (const field of required) {
if (!data[field]) {
throw new Error(`Missing required field: ${field}`);
}
}
if (!data.items.length) {
throw new Error('Order must contain at least one item');
}
}
async checkInventory(items) {
for (const item of items) {
const inventory = await this.stateset.inventory.get(item.sku);
if (inventory.available < item.quantity) {
throw new Error(
`Insufficient inventory for ${item.sku}. ` +
`Available: ${inventory.available}`
);
}
}
}
validateStatusTransition(currentStatus, newStatus) {
const validTransitions = {
'pending': ['processing', 'cancelled'],
'processing': ['shipped', 'cancelled'],
'shipped': ['delivered', 'returned'],
'delivered': ['returned'],
'cancelled': [],
'returned': []
};
if (!validTransitions[currentStatus].includes(newStatus)) {
throw new Error(
`Invalid status transition from ${currentStatus} to ${newStatus}`
);
}
}
handleApiError(error, res) {
const statusMap = {
'VALIDATION_ERROR': 400,
'UNAUTHORIZED': 401,
'FORBIDDEN': 403,
'NOT_FOUND': 404,
'RATE_LIMITED': 429,
'INTERNAL_ERROR': 500
};
const status = statusMap[error.code] || 500;
res.status(status).json({
success: false,
error: {
code: error.code,
message: error.message,
details: error.details
}
});
}
start(port = 3000) {
this.app.listen(port, () => {
console.log(`Order Management System running on port ${port}`);
});
}
}
// Start the system
const oms = new OrderManagementSystem();
oms.start();
from flask import Flask, request, jsonify
from stateset import StateSet
import os
from datetime import datetime
from dotenv import load_dotenv
load_dotenv()
class OrderManagementSystem:
def __init__(self):
self.stateset = StateSet(api_key=os.getenv('STATESET_API_KEY'))
self.app = Flask(__name__)
self.setup_routes()
def setup_routes(self):
@self.app.route('/api/orders', methods=['POST'])
def create_order():
try:
order = self.create_order(request.json)
return jsonify({'success': True, 'order': order})
except Exception as e:
return self.handle_api_error(e)
@self.app.route('/api/orders', methods=['GET'])
def get_orders():
try:
orders = self.get_orders(request.args)
return jsonify({'success': True, 'orders': orders})
except Exception as e:
return self.handle_api_error(e)
@self.app.route('/api/orders/<order_id>/status', methods=['PATCH'])
def update_status(order_id):
try:
order = self.update_order_status(
order_id,
request.json['status']
)
return jsonify({'success': True, 'order': order})
except Exception as e:
return self.handle_api_error(e)
@self.app.route('/webhooks/stateset', methods=['POST'])
def handle_webhook():
return self.handle_webhook(request)
def create_order(self, order_data):
# Validate order data
self.validate_order_data(order_data)
# Check inventory
self.check_inventory(order_data['items'])
# Create order with idempotency
idempotency_key = f"order-{datetime.now().timestamp()}-{order_data['customer']['email']}"
order = self.stateset.orders.create(
**order_data,
idempotency_key=idempotency_key
)
# Send confirmation
self.send_order_confirmation(order)
return order
def get_orders(self, filters):
orders = self.stateset.orders.list(
limit=filters.get('limit', 20),
status=filters.get('status'),
created_after=filters.get('from_date'),
created_before=filters.get('to_date'),
sort=filters.get('sort', 'created_at'),
order=filters.get('order', 'desc')
)
return orders
def update_order_status(self, order_id, new_status):
# Get current order
order = self.stateset.orders.get(order_id)
# Validate transition
self.validate_status_transition(order.status, new_status)
# Update order
updated = self.stateset.orders.update(
order_id,
status=new_status
)
# Trigger workflows
self.trigger_status_workflow(updated)
return updated
def validate_order_data(self, data):
required = ['customer', 'items', 'shipping_address']
for field in required:
if field not in data:
raise ValueError(f"Missing required field: {field}")
if not data['items']:
raise ValueError("Order must contain at least one item")
def check_inventory(self, items):
for item in items:
inventory = self.stateset.inventory.get(item['sku'])
if inventory.available < item['quantity']:
raise ValueError(
f"Insufficient inventory for {item['sku']}. "
f"Available: {inventory.available}"
)
def validate_status_transition(self, current_status, new_status):
valid_transitions = {
'pending': ['processing', 'cancelled'],
'processing': ['shipped', 'cancelled'],
'shipped': ['delivered', 'returned'],
'delivered': ['returned'],
'cancelled': [],
'returned': []
}
if new_status not in valid_transitions.get(current_status, []):
raise ValueError(
f"Invalid status transition from {current_status} to {new_status}"
)
def handle_api_error(self, error):
status_map = {
'VALIDATION_ERROR': 400,
'UNAUTHORIZED': 401,
'FORBIDDEN': 403,
'NOT_FOUND': 404,
'RATE_LIMITED': 429,
'INTERNAL_ERROR': 500
}
error_code = getattr(error, 'code', 'INTERNAL_ERROR')
status = status_map.get(error_code, 500)
return jsonify({
'success': False,
'error': {
'code': error_code,
'message': str(error),
'details': getattr(error, 'details', None)
}
}), status
def start(self, port=3000):
self.app.run(port=port, debug=True)
# Start the system
if __name__ == '__main__':
oms = OrderManagementSystem()
oms.start()
Next Steps
Now that you have a working integration, explore these advanced features:GraphQL API
Use our GraphQL API for flexible queries
Batch Operations
Process multiple resources efficiently
Real-time Updates
Subscribe to real-time events via WebSockets
Advanced Search
Implement full-text search and filtering
Resources
Documentation
- API Reference - Complete API documentation
- SDKs - Language-specific SDKs
- Authentication - Auth methods and security
- Error Handling - Error codes and handling
Code Examples
- GitHub Examples - Sample implementations
- Postman Collection - Pre-built API requests
- CodeSandbox Templates - Live playground
Support
- Discord Community - Get help from the community
- API Status - Check service status
- Support Email - Direct support
Troubleshooting
Authentication Issues
Authentication Issues
Problem: Getting 401 Unauthorized errorsSolutions:
- Verify your API key is correct and active
- Check you’re using the right environment (test vs live)
- Ensure Bearer prefix in Authorization header
- Verify API key has required permissions
Rate Limiting
Rate Limiting
Problem: Getting 429 Too Many Requests errorsSolutions:
- Implement exponential backoff
- Cache frequently accessed data
- Use batch operations where possible
- Consider upgrading your plan for higher limits
Webhook Issues
Webhook Issues
Problem: Webhooks not being receivedSolutions:
- Verify webhook URL is publicly accessible
- Check webhook signature verification
- Ensure your server responds with 2xx status
- Test with webhook simulator in dashboard
Data Validation
Data Validation
Problem: Getting validation errorsSolutions:
- Check required fields are included
- Verify data types match specification
- Ensure enum values are valid
- Review error details for specific issues
🎉 Congratulations! You’ve successfully integrated with the StateSet API. You’re now ready to build powerful commerce applications! For questions or feedback, reach out on Discord or email api-support@stateset.com.