Skip to main content
Time to first API call: Less than 5 minutes
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

1

Sign up for StateSet

Create your account at stateset.com/sign-up
2

Navigate to API Settings

Go to Dashboard → Settings → API Keys
3

Create Your First API Key

Click “Create API Key” and save it securely - you’ll only see it once!
Security First: Never commit API keys to version control. Use environment variables instead.

Step 2: Set Up Your Environment

# 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!');

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

Code Examples

Support

Troubleshooting

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
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
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
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.