@albertogferrario/stockx-api
v1.5.0
Published
StockX Public API node package with OAuth2 helpers and rate limiting
Maintainers
Readme
StockX API
Node.js wrapper for the StockX Public API with built-in OAuth2 helpers and rate limiting.
Features
- OAuth2 helper utilities for authentication flows
- Automatic token refresh with request interceptors
- Built-in rate limiting (25,000 requests/day)
- Full TypeScript support
- Secure token storage options
- Promise-based async/await API
- Comprehensive error handling
Installation
npm install @albertogferrario/stockx-apiQuick Start
Basic Usage
const StockxApi = require('@albertogferrario/stockx-api');
// Initialize with your API key and JWT token
const stockxApi = new StockxApi('your-api-key', 'your-jwt-token');
// Search for products
const results = await stockxApi.catalog.search('Jordan 1', 1, 20);
// Get product by slug
const product = await stockxApi.catalog.getProductBySlug('air-jordan-1-retro-high-og-chicago');
// Get product variants using UUID
const variants = await stockxApi.catalog.getVariants(product.productId);
// Get market data
const marketData = await stockxApi.catalog.getVariantMarketData(
product.productId,
variants[0].variantId,
'USD'
);Complete User Flow: Slug to Pricing
Most common scenario: Get pricing for all sizes of a product from its slug.
async function getProductPricing(slug, currency = 'USD') {
const stockxApi = new StockxApi(apiKey, jwt);
// Step 1: Get product details from slug
const product = await stockxApi.catalog.getProductBySlug(slug);
console.log(`Found: ${product.title}`);
// Step 2: Get all available sizes
const variants = await stockxApi.catalog.getVariants(product.productId);
console.log(`Available sizes: ${variants.map(v => v.variantValue).join(', ')}`);
// Step 3: Get pricing for each size
const pricing = [];
for (const variant of variants.slice(0, 5)) { // First 5 sizes
try {
const marketData = await stockxApi.catalog.getVariantMarketData(
product.productId,
variant.variantId,
currency
);
pricing.push({
size: variant.variantValue,
lowestAsk: marketData.lowestAskAmount,
highestBid: marketData.highestBidAmount,
currency: currency
});
} catch (error) {
console.log(`No pricing data for size ${variant.variantValue}`);
}
}
return { product, pricing };
}
// Usage
const result = await getProductPricing('nike-dunk-low-se-easter-w');
console.log(result.pricing);
// Output: [
// { size: '5W', lowestAsk: '178', highestBid: null, currency: 'USD' },
// { size: '5.5W', lowestAsk: '227', highestBid: null, currency: 'USD' },
// ...
// ]Authentication
StockX uses OAuth2 Authorization Code Flow. You need to:
- Register your application at StockX Developer Portal
- Implement OAuth2 flow to obtain JWT tokens
- Use the
offline_accessscope to get refresh tokens
Easy OAuth2 Setup
Option 1: Automated OAuth Flow
# Set up your credentials in .env.test first
node scripts/oauth2-flow.jsThis opens a browser, handles the OAuth flow automatically, and saves tokens.
Option 2: Manual OAuth Flow
# For environments where callback servers can't run
node scripts/manual-oauth2.jsThis provides URLs to copy/paste manually.
Option 3: Token Refresh
# Refresh expired tokens automatically
node scripts/auto-refresh.jsOAuth2 Helpers (Advanced)
const { helpers } = require('@albertogferrario/stockx-api');
// Generate authorization URL (includes required audience parameter)
const authUrl = helpers.auth.buildAuthUrl(
'https://accounts.stockx.com/oauth',
'your-client-id',
'https://your-app.com/callback',
['offline_access', 'openid'],
'oauth-state',
'gateway.stockx.com' // Required audience
);
// Parse callback URL
const { code } = helpers.auth.parseAuthCode(callbackUrl);
// Exchange authorization code for tokens
const tokenResponse = await helpers.auth.exchangeAuthCode(
code,
'your-client-id',
'your-client-secret',
'https://your-app.com/callback',
'https://accounts.stockx.com/oauth/token'
);
// Or refresh existing tokens
const refreshResponse = await helpers.refresh.refreshToken(
refreshToken,
'your-client-id',
'your-client-secret',
'https://accounts.stockx.com/oauth/token',
'gateway.stockx.com' // Required audience
);
// Parse token response
const tokens = helpers.token.parse(tokenResponse);
console.log('Access Token:', tokens.accessToken);
console.log('Refresh Token:', tokens.refreshToken);
console.log('Expires At:', tokens.expiresAt);API Reference
Constructor
new StockxApi(apiKey, jwt, config)apiKey(string, required): Your StockX API keyjwt(string, required): JWT access tokenconfig(object, optional): Configuration overridesbaseUrl: API base URL (default:https://api.stockx.com/v2)requestTimeout: Request timeout in ms (default: 60000)requestRateLimitMinTime: Min time between requests in ms (default: 1000)requestRateLimitReservoirAmount: Daily request limit (default: 25000)requestRateLimitReservoirRefreshCronExpression: Reset schedule (default:0 0 * * *)
Methods
catalog.search(query, pageNumber, pageSize)
Search for products in the StockX catalog.
query(string): Search querypageNumber(number): Page number (default: 1, min: 1)pageSize(number): Results per page (default: 10, max: 50)
const searchResponse = await stockxApi.catalog.search('Nike Air Max', 1, 20);
// searchResponse contains: { count, pageSize, pageNumber, hasNextPage, products }
console.log(`Found ${searchResponse.count} products`);
const products = searchResponse.products;catalog.getProductBySlug(slug)
Get product details by slug/urlKey. This method searches for products and returns the one with an exact urlKey match. This ensures you get the correct product even if the search returns similar products.
slug(string): Product slug/urlKey (e.g., 'nike-dunk-low-se-easter-w')
const product = await stockxApi.catalog.getProductBySlug('nike-dunk-low-se-easter-w');
console.log(product.productId); // UUID: c318bbcc-312a-4396-9252-698c203d1dea
console.log(product.urlKey); // Slug: nike-dunk-low-se-easter-w (exact match guaranteed)Note: This method performs an exact match on the urlKey field to ensure accuracy. If the search returns similar products but none with the exact slug, it will throw an error rather than return a potentially incorrect product.
catalog.getVariants(productId)
Get all variants for a specific product.
productId(string): StockX product UUID (not slug)
// First get the product UUID
const product = await stockxApi.catalog.getProductBySlug('nike-dunk-low-se-easter-w');
// Then get variants using the UUID
const variants = await stockxApi.catalog.getVariants(product.productId);catalog.getVariantMarketData(productId, variantId, currencyCode)
Get market data for a specific variant.
productId(string): StockX product UUID (not slug)variantId(string): Variant IDcurrencyCode(string): Currency code (e.g., 'USD', 'EUR')
const product = await stockxApi.catalog.getProductBySlug('nike-dunk-low-se-easter-w');
const variants = await stockxApi.catalog.getVariants(product.productId);
const marketData = await stockxApi.catalog.getVariantMarketData(
product.productId,
variants[0].variantId,
'USD'
);updateJwt(jwt)
Update the JWT token without creating a new client instance.
jwt(string): New JWT token
stockxApi.updateJwt('new-jwt-token');OAuth2 Helpers
auth
OAuth2 URL builders and PKCE utilities.
const { auth } = require('@albertogferrario/stockx-api').helpers;
// Build authorization URL
const authUrl = auth.buildAuthUrl(baseUrl, clientId, redirectUri, scopes, state);
// Generate PKCE challenge
const { verifier, challenge, method } = auth.generatePKCE();
// Parse authorization code from callback
const { code, state } = auth.parseAuthCode(callbackUrl);
// Build token exchange URL
const tokenUrl = auth.buildTokenUrl(baseUrl);token
JWT token parsing and validation utilities.
const { token } = require('@albertogferrario/stockx-api').helpers;
// Decode JWT payload (without verification)
const payload = token.decode(jwt);
// Check if token is expired
const expired = token.isExpired(jwt, bufferSeconds);
// Get expiration date
const expiryDate = token.getExpiry(jwt);
// Parse OAuth2 token response
const parsed = token.parse(tokenResponse);
// Returns: { accessToken, tokenType, refreshToken, scope, expiresAt }refresh
Axios interceptor for automatic token refresh.
const { refresh } = require('@albertogferrario/stockx-api').helpers;
// Create interceptor for automatic token refresh
refresh.createInterceptor(axiosClient, {
onRefresh: async () => {
// Refresh token and return new access token
const response = await refresh.refreshToken(
refreshToken,
clientId,
clientSecret,
tokenUrl
);
const { accessToken } = token.parse(response);
return accessToken;
},
shouldRefresh: (error) => error.response?.status === 401,
maxRetries: 1
});
// Helper for token refresh
const newTokens = await refresh.refreshToken(
refreshToken,
clientId,
clientSecret,
tokenUrl
);storage
Token storage interface with multiple implementations.
const { storage } = require('@albertogferrario/stockx-api').helpers;
// In-memory storage (default)
const memoryStore = new storage.MemoryStore();
// File-based storage
const fileStore = storage.createFileStore('./tokens.json', {
encrypt: true,
secret: 'your-encryption-secret'
});
// Custom storage implementation
class CustomStore extends storage.StorageInterface {
async get(key) { /* implementation */ }
async set(key, value) { /* implementation */ }
async delete(key) { /* implementation */ }
async clear() { /* implementation */ }
}
// Usage
await store.set('tokens', { accessToken: '...', refreshToken: '...' });
const tokens = await store.get('tokens');Examples
Complete OAuth2 Flow
const StockxApi = require('@albertogferrario/stockx-api');
const { helpers } = StockxApi;
const express = require('express');
const app = express();
const CLIENT_ID = 'your-client-id';
const CLIENT_SECRET = 'your-client-secret';
const REDIRECT_URI = 'http://localhost:3000/callback';
// Step 1: Redirect to authorization
app.get('/auth', (req, res) => {
const pkce = helpers.auth.generatePKCE();
req.session.verifier = pkce.verifier;
const authUrl = helpers.auth.buildAuthUrl(
'https://accounts.stockx.com/oauth',
CLIENT_ID,
REDIRECT_URI,
['offline_access', 'openid']
);
res.redirect(authUrl);
});
// Step 2: Handle callback
app.get('/callback', async (req, res) => {
try {
const { code } = helpers.auth.parseAuthCode(req.url);
// Exchange authorization code for tokens
const tokenResponse = await helpers.auth.exchangeAuthCode(
code,
CLIENT_ID,
CLIENT_SECRET,
REDIRECT_URI,
'https://accounts.stockx.com/oauth/token'
);
const tokens = helpers.token.parse(tokenResponse);
// Create API instance
const stockxApi = new StockxApi('your-api-key', tokens.accessToken);
// Store tokens securely
await tokenStore.set('user-123', tokens);
res.json({ success: true });
} catch (error) {
res.status(400).json({ error: error.message });
}
});Automatic Token Refresh
const StockxApi = require('@albertogferrario/stockx-api');
const { helpers } = StockxApi;
// Create token manager
const tokenStore = helpers.storage.createFileStore('./tokens.json');
async function createApiClient() {
const tokens = await tokenStore.get('tokens');
const api = new StockxApi('your-api-key', tokens.accessToken);
// Add automatic refresh
helpers.refresh.createInterceptor(api.client, {
onRefresh: async () => {
const newTokens = await helpers.refresh.refreshToken(
tokens.refreshToken,
CLIENT_ID,
CLIENT_SECRET,
'https://accounts.stockx.com/oauth/token'
);
const parsed = helpers.token.parse(newTokens);
await tokenStore.set('tokens', parsed);
// Update the API instance
api.updateJwt(parsed.accessToken);
return parsed.accessToken;
}
});
return api;
}
// Use the client
const api = await createApiClient();
const products = await api.catalog.search('Jordan 1');Token Expiry Monitoring
const { helpers } = StockxApi;
// Check token expiry before requests
async function makeApiCall() {
const tokens = await tokenStore.get('tokens');
// Check if token expires in next 5 minutes
if (helpers.token.isExpired(tokens.accessToken, 300)) {
// Refresh token
const newTokens = await refreshTokens();
await tokenStore.set('tokens', newTokens);
}
const api = new StockxApi('your-api-key', tokens.accessToken);
return await api.catalog.search('Nike');
}Error Handling
The API wrapper provides detailed error messages:
try {
const results = await stockxApi.catalog.search('Nike');
} catch (error) {
if (error.response) {
// API error response
console.error('API Error:', error.response.status, error.response.data);
if (error.response.status === 401) {
// Token expired - refresh and retry
} else if (error.response.status === 429) {
// Rate limited - wait and retry
}
} else if (error.request) {
// Request made but no response
console.error('Network error:', error.message);
} else {
// Request setup error
console.error('Error:', error.message);
}
}Rate Limiting
The package includes built-in rate limiting to comply with StockX API limits:
- Daily limit: 25,000 requests (resets at midnight UTC)
- Minimum delay: 1 second between requests
- Sequential processing: Requests are queued to prevent overwhelming the API
You can monitor rate limit usage:
// Get current reservoir count
const remaining = stockxApi.limiter.reservoir;
console.log(`Requests remaining today: ${remaining}`);
// Listen for rate limit events
stockxApi.limiter.on('depleted', () => {
console.log('Rate limit reached - requests will be queued');
});Configuration
Custom Rate Limits
const stockxApi = new StockxApi('api-key', 'jwt', {
requestRateLimitMinTime: 2000, // 2 seconds between requests
requestRateLimitReservoirAmount: 10000, // 10k requests per day
requestRateLimitReservoirRefreshCronExpression: '0 */6 * * *' // Reset every 6 hours
});Custom Timeout
const stockxApi = new StockxApi('api-key', 'jwt', {
requestTimeout: 30000 // 30 second timeout
});Development
# Install dependencies
npm install
# Run unit tests
npm test
# Run integration tests (requires API credentials)
npm run test:integration
# Run all tests
npm run test:all
# Run linter
npm run lintIntegration Testing
This package includes integration tests that test against the real StockX API. To run these tests, you need valid API credentials.
Setup
Copy the example environment file:
cp .env.test.example .env.testEdit
.env.testand add your StockX API credentials:STOCKX_API_KEY=your-api-key-here STOCKX_JWT_TOKEN=your-jwt-token-hereRun integration tests:
npm run test:integration
Test Configuration
The integration tests support the following environment variables in .env.test:
STOCKX_API_KEY(required): Your StockX API keySTOCKX_JWT_TOKEN(required): Your StockX JWT tokenTEST_PRODUCT_SLUG(optional): Product slug to use for testing (default:air-jordan-1-retro-high-og-chicago-reimagined)TEST_TIMEOUT(optional): Test timeout in milliseconds (default: 30000)TEST_DELAY(optional): Delay between API calls in milliseconds (default: 2000)
Important Notes
- Integration tests are automatically skipped if credentials are not provided
- Tests include delays between API calls to respect rate limits
- The
.env.testfile is gitignored to protect your credentials - Integration tests make real API calls and count against your daily rate limit
Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
License
ISC © Alberto Giancarlo Ferrario
