Quick Start: All API requests require authentication via API key in the Authorization header:
Authorization: Bearer sk_live_your_api_key_here
Stateset provides multiple authentication methods to secure your API access, including API keys, JWT tokens, and OAuth 2.0 for different use cases.

Authentication Overview

All data in Stateset is private by default, requiring authentication for every API request. We support multiple authentication methods:

Supported Authentication Methods

API Keys

Best for server-to-server communication and backend integrations

JWT Tokens

Ideal for user-specific access and session management

OAuth 2.0

Perfect for third-party integrations and partner access

Webhook Signatures

Secure webhook delivery with HMAC signatures

API Key Authentication

Key Types and Permissions

Key TypePrefixUse CasePermissions
Secret Keysk_live_Server-side operationsFull API access
Restricted Keyrk_live_Limited scope accessCustom permissions
Publishable Keypk_live_Client-side operationsRead-only public data
Test Keysk_test_Development & testingSandbox environment

Creating API Keys

  1. Navigate to Settings → API Keys in your dashboard
  2. Click Create New Key
  3. Select key type and permissions
  4. Copy and securely store your key
API keys are shown only once. Store them securely and never expose secret keys in client-side code.

Using API Keys

curl https://api.stateset.com/v1/orders \
  -H "Authorization: Bearer sk_live_your_api_key_here" \
  -H "Content-Type: application/json"

Restricted API Keys

Create keys with specific permissions for enhanced security:
// Creating a restricted key via API
const restrictedKey = await stateset.apiKeys.create({
  name: 'Read-only Orders Key',
  permissions: [
    'orders:read',
    'customers:read'
  ],
  expires_at: '2024-12-31T23:59:59Z'
});

JWT Token Authentication

JWT tokens provide secure, stateless authentication for user sessions.

JWT Token Structure

{
  "header": {
    "alg": "RS256",
    "typ": "JWT",
    "kid": "key_id_123"
  },
  "payload": {
    "sub": "user_123",
    "org_id": "org_456",
    "role": "admin",
    "permissions": ["orders:*", "customers:*"],
    "iat": 1704067200,
    "exp": 1704070800,
    "iss": "https://api.stateset.com"
  },
  "signature": "..."
}

GraphQL API Authentication

GraphQL Endpoint Access

Stateset GraphQL API requires authentication via HTTP headers:
# GraphQL endpoint
https://api.stateset.com/graphql

# Required headers
Authorization: Bearer sk_live_your_api_key
Content-Type: application/json

GraphQL Request Example

import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

const httpLink = createHttpLink({
  uri: 'https://api.stateset.com/graphql',
});

const authLink = setContext((_, { headers }) => {
  const token = process.env.STATESET_API_KEY;
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    }
  }
});

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache()
});

OAuth 2.0 Authentication

For third-party integrations and partner access, we support OAuth 2.0:

OAuth Flow

1

Authorization Request

GET https://api.stateset.com/oauth/authorize?
  client_id=YOUR_CLIENT_ID&
  redirect_uri=YOUR_REDIRECT_URI&
  response_type=code&
  scope=orders:read customers:read&
  state=RANDOM_STATE
2

Token Exchange

POST https://api.stateset.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=AUTH_CODE&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET&
redirect_uri=YOUR_REDIRECT_URI
3

Use Access Token

GET https://api.stateset.com/v1/orders
Authorization: Bearer ACCESS_TOKEN

Available Scopes

ScopeDescription
orders:readRead order data
orders:writeCreate and update orders
customers:readRead customer data
customers:writeManage customers
inventory:readView inventory levels
inventory:writeUpdate inventory
returns:*Full returns access
adminFull API access

Role-Based Access Control (RBAC)

User Roles and Permissions

Stateset implements fine-grained permissions using role-based access control:
RoleDescriptionDefault PermissionsAPI Key Prefix
anonymousUnauthenticated userPublic read-only endpointsN/A
viewerRead-only accessAll read operationsrk_view_
operatorStandard userCRUD on assigned resourcessk_live_
managerTeam managerCRUD on team resourcessk_mgr_
adminFull accessAll operationssk_admin_
super_adminSystem adminSystem configurationsk_super_

Permission Matrix

ResourceAnonymousViewerOperatorManagerAdmin
OrdersReadCRUD (own)CRUD (team)CRUD (all)
CustomersReadRead (own)CRUD (team)CRUD (all)
InventoryReadReadReadCRUDCRUD
ReturnsReadCRUD (own)CRUD (team)CRUD (all)
ReportsReadRead (own)Read (team)CRUD
SettingsRead (own)Update (team)CRUD

Custom Permissions with Session Variables

Implement fine-grained access control using session variables:

JWT Claims Structure

{
  "https://hasura.io/jwt/claims": {
    "x-hasura-org-id": "org_123",
    "x-hasura-user-id": "user_456",
    "x-hasura-default-role": "operator",
    "x-hasura-allowed-roles": ["viewer", "operator", "manager"],
    "x-hasura-team-id": "team_789",
    "x-hasura-permissions": [
      "orders:read",
      "orders:write",
      "customers:read"
    ]
  },
  "iat": 1704067200,
  "exp": 1704070800
}

Permission Checks

// Middleware for permission checking
function requirePermission(permission) {
  return (req, res, next) => {
    const token = req.headers.authorization?.split(' ')[1];
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    
    const claims = decoded['https://hasura.io/jwt/claims'];
    const permissions = claims['x-hasura-permissions'] || [];
    
    if (!permissions.includes(permission)) {
      return res.status(403).json({
        error: 'INSUFFICIENT_PERMISSIONS',
        message: `Missing required permission: ${permission}`
      });
    }
    
    req.user = {
      id: claims['x-hasura-user-id'],
      org_id: claims['x-hasura-org-id'],
      role: claims['x-hasura-default-role'],
      permissions
    };
    
    next();
  };
}

// Usage
app.post('/api/orders', 
  requirePermission('orders:write'), 
  createOrderHandler
);

Webhook Authentication

Webhook Signature Verification

All webhooks from Stateset are signed for security:
const crypto = require('crypto');

class WebhookVerifier {
  constructor(secret) {
    this.secret = secret;
  }
  
  verify(payload, signature) {
    // Parse signature header
    const elements = signature.split(' ');
    const timestamp = elements.find(e => e.startsWith('t=')).slice(2);
    const signatures = elements
      .filter(e => e.startsWith('v1='))
      .map(e => e.slice(3));
    
    // Check timestamp (5 minute tolerance)
    const currentTime = Math.floor(Date.now() / 1000);
    if (currentTime - parseInt(timestamp) > 300) {
      throw new Error('Webhook timestamp expired');
    }
    
    // Compute expected signature
    const signedPayload = `${timestamp}.${payload}`;
    const expectedSig = crypto
      .createHmac('sha256', this.secret)
      .update(signedPayload)
      .digest('hex');
    
    // Timing-safe comparison
    const valid = signatures.some(sig => 
      crypto.timingSafeEqual(
        Buffer.from(sig),
        Buffer.from(expectedSig)
      )
    );
    
    if (!valid) {
      throw new Error('Invalid webhook signature');
    }
    
    return JSON.parse(payload);
  }
}

// Express middleware
const webhookAuth = (req, res, next) => {
  const verifier = new WebhookVerifier(process.env.WEBHOOK_SECRET);
  
  try {
    req.body = verifier.verify(
      req.rawBody,
      req.headers['stateset-signature']
    );
    next();
  } catch (error) {
    res.status(401).json({ error: 'Webhook verification failed' });
  }
};

Security Best Practices

Authentication Examples

React Hook with Authentication

import { useState, useEffect } from 'react';
import { useAuth } from '@clerk/nextjs';

/**
 * Custom hook for authenticated Stateset API calls
 */
export const useStatesetAPI = () => {
  const { getToken, isSignedIn } = useAuth();
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  const apiCall = async (endpoint, options = {}) => {
    if (!isSignedIn) {
      throw new Error('User not authenticated');
    }
    
    setLoading(true);
    setError(null);
    
    try {
      const token = await getToken({ template: 'stateset' });
      
      const response = await fetch(`https://api.stateset.com/v1${endpoint}`, {
        ...options,
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json',
          ...options.headers
        }
      });
      
      if (!response.ok) {
        const error = await response.json();
        throw new Error(error.message || 'API request failed');
      }
      
      const data = await response.json();
      return data;
      
    } catch (err) {
      setError(err);
      throw err;
    } finally {
      setLoading(false);
    }
  };
  
  return { apiCall, loading, error };
};

// Usage
function OrderList() {
  const { apiCall } = useStatesetAPI();
  const [orders, setOrders] = useState([]);
  
  useEffect(() => {
    apiCall('/orders')
      .then(data => setOrders(data.orders))
      .catch(console.error);
  }, []);
  
  return (
    <div>
      {orders.map(order => (
        <OrderCard key={order.id} order={order} />
      ))}
    </div>
  );
}

Python Authentication Manager

import os
import time
import requests
from typing import Optional, Dict, Any
from functools import wraps

class StatesetAuth:
    def __init__(self, api_key: Optional[str] = None):
        self.api_key = api_key or os.getenv('STATESET_API_KEY')
        self.base_url = 'https://api.stateset.com/v1'
        self.session = requests.Session()
        self.session.headers.update({
            'Authorization': f'Bearer {self.api_key}',
            'Content-Type': 'application/json'
        })
    
    def validate_key(self) -> bool:
        """Validate API key is active"""
        try:
            response = self.session.get(f'{self.base_url}/auth/validate')
            return response.status_code == 200
        except:
            return False
    
    def with_retry(self, max_retries: int = 3):
        """Decorator for automatic retry with exponential backoff"""
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                for attempt in range(max_retries):
                    try:
                        return func(*args, **kwargs)
                    except requests.exceptions.RequestException as e:
                        if attempt == max_retries - 1:
                            raise
                        wait_time = 2 ** attempt
                        time.sleep(wait_time)
                return None
            return wrapper
        return decorator
    
    @with_retry(max_retries=3)
    def api_request(
        self,
        method: str,
        endpoint: str,
        data: Optional[Dict[Any, Any]] = None
    ) -> Dict[Any, Any]:
        """Make authenticated API request"""
        url = f'{self.base_url}{endpoint}'
        response = self.session.request(method, url, json=data)
        response.raise_for_status()
        return response.json()

# Usage
auth = StatesetAuth()
if auth.validate_key():
    orders = auth.api_request('GET', '/orders')
    print(f"Found {len(orders['data'])} orders")

Troubleshooting Authentication

Common Issues and Solutions

IssueCauseSolution
401 UnauthorizedInvalid or expired API keyCheck key validity in dashboard
403 ForbiddenInsufficient permissionsVerify key has required scopes
429 Too Many RequestsRate limit exceededImplement exponential backoff
CORS ErrorCross-origin request blockedUse server-side proxy or SDK
Signature MismatchInvalid webhook secretVerify webhook secret matches

Debug Authentication

# Test API key validity
curl -I https://api.stateset.com/v1/auth/validate \
  -H "Authorization: Bearer YOUR_API_KEY"

# Check key permissions
curl https://api.stateset.com/v1/auth/permissions \
  -H "Authorization: Bearer YOUR_API_KEY"

Next Steps: Create your first API request →