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 Type Prefix Use Case Permissions Secret Key sk_live_
Server-side operations Full API access Restricted Key rk_live_
Limited scope access Custom permissions Publishable Key pk_live_
Client-side operations Read-only public data Test Key sk_test_
Development & testing Sandbox environment
Creating API Keys
Navigate to Settings → API Keys in your dashboard
Click Create New Key
Select key type and permissions
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
Apollo Client
Python GraphQL
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
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
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
Use Access Token
GET https://api.stateset.com/v1/orders
Authorization: Bearer ACCESS_TOKEN
Available Scopes
Scope Description orders:read
Read order data orders:write
Create and update orders customers:read
Read customer data customers:write
Manage customers inventory:read
View inventory levels inventory:write
Update inventory returns:*
Full returns access admin
Full API access
Role-Based Access Control (RBAC)
User Roles and Permissions
Stateset implements fine-grained permissions using role-based access control:
Role Description Default Permissions API Key Prefix anonymous Unauthenticated user Public read-only endpoints N/A viewer Read-only access All read operations rk_view_
operator Standard user CRUD on assigned resources sk_live_
manager Team manager CRUD on team resources sk_mgr_
admin Full access All operations sk_admin_
super_admin System admin System configuration sk_super_
Permission Matrix
Resource Anonymous Viewer Operator Manager Admin Orders ❌ Read CRUD (own) CRUD (team) CRUD (all) Customers ❌ Read Read (own) CRUD (team) CRUD (all) Inventory Read Read Read CRUD CRUD Returns ❌ Read CRUD (own) CRUD (team) CRUD (all) Reports ❌ Read Read (own) Read (team) CRUD Settings ❌ ❌ Read (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
Store keys in environment variables, never in code
Use different keys for different environments
Rotate keys regularly (every 90 days recommended)
Use restricted keys with minimal permissions
Monitor key usage for anomalies
Implement short token lifetimes (15-30 minutes)
Use refresh tokens for long-lived sessions
Store tokens securely (httpOnly cookies)
Implement token revocation
Log all authentication events
Always use HTTPS for API calls
Implement IP allowlisting for production
Use VPN or private networks when possible
Enable CORS with specific origins
Implement rate limiting per API key
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
Issue Cause Solution 401 Unauthorized
Invalid or expired API key Check key validity in dashboard 403 Forbidden
Insufficient permissions Verify key has required scopes 429 Too Many Requests
Rate limit exceeded Implement exponential backoff CORS Error
Cross-origin request blocked Use server-side proxy or SDK Signature Mismatch
Invalid webhook secret Verify 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 →