JavaScript Integration
There is no official Storelayer JavaScript SDK; use fetch from your server runtime. The client below is all most integrations need: it adds the key, unwraps { success, data }, and turns both error shapes into one typed error. The only published npm package is @storelayer/mcp-server, which is for agents, not application code.
Minimal typed client (server-side)
Section titled “Minimal typed client (server-side)”// storelayer.ts — server only; never import from browser codeconst BASE_URL = process.env.STORE_LAYER_API_URL ?? 'https://api.storelayer.io'const API_KEY = process.env.STORE_LAYER_API_KEY!const PROJECT_ID = process.env.STORE_LAYER_PROJECT_ID! // must be the key's project
export class StorelayerError extends Error { constructor( readonly status: number, readonly code: number | string, // 'VALIDATION' for schema issues; promotion evaluate may send 'VALIDATION_ERROR' message: string, readonly issues?: unknown[] ) { super(message) }}
export async function sl<T>(method: string, path: string, body?: unknown): Promise<T> { const res = await fetch(`${BASE_URL}${path}`, { method, headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(10_000) }) const json = (await res.json().catch(() => null)) as any if (res.ok && json?.success) return json.data as T
// Schema validation failure: { success: false, error: Issue[], data } if (Array.isArray(json?.error)) { throw new StorelayerError(res.status, 'VALIDATION', 'Request validation failed', json.error) } // Standard error: { success: false, error: { code, message } } throw new StorelayerError(res.status, json?.error?.code ?? 0, json?.error?.message ?? res.statusText)}
const p = (path: string) => `/projects/${PROJECT_ID}${path}`
type AssetBalance = { assetType: string; balance: number; lifetimeEarned: number; lifetimeSpent: number; lifetimeExpired: number; nextExpiration: string | null }
export const storelayer = { registerCustomer: (c: { userId: string; firstName: string; lastName: string; email?: string }) => sl<{ id: string }>('POST', p('/users'), c), getWallet: (userId: string) => sl<{ assets: Record<string, AssetBalance> }>('GET', p(`/users/${encodeURIComponent(userId)}/wallet`)), earn: (userId: string, body: { assetType: string; amount: number; referenceId?: string; referenceType?: string; description?: string }) => sl('POST', p(`/users/${encodeURIComponent(userId)}/wallet/earn`), body), sendEvent: (event: { type: string; userId?: string; payload: Record<string, unknown> }) => sl<{ id: string; status: 'accepted' }>('POST', '/ingest/events', event)}Usage:
try { await storelayer.registerCustomer({ userId: 'cus_1001', firstName: 'Ada', lastName: 'Lovelace' })} catch (e) { if (!(e instanceof StorelayerError && e.code === 2500)) throw e // 2500 = already registered}
await storelayer.sendEvent({ type: 'purchase', userId: 'cus_1001', payload: { orderId: 'ord_5001', amount: 5000 } })
const wallet = await storelayer.getWallet('cus_1001')const points = wallet.assets.points?.balance ?? 0 // asset absent until first earnError handling rules
Section titled “Error handling rules”| Situation | Signal | What to do |
|---|---|---|
| Bad request body/params | code === 'VALIDATION', issues[] | Fix the payload; never retry as-is |
| Bad or revoked key | 401, code 1002 | Alert; don’t retry |
| Key/project mismatch | 403, code 1003 | Fix configuration |
Business outcome (e.g. insufficient balance 2601, user exists 2500) | 4xx with domain code | Handle in your flow |
Timeout / network error / 5xx on a read | thrown | Safe to retry with backoff |
Timeout / 5xx on a write | thrown | Check state before retrying — wallet earn/spend are not idempotent |
A 200 is not always a “yes”: promotion evaluation can succeed with no qualifying promotions, and an accepted event may match no rule. Check data.
Generating types from OpenAPI
Section titled “Generating types from OpenAPI”For full typing across all endpoints, generate types from the live spec with a third-party tool such as openapi-typescript:
npx openapi-typescript https://api.storelayer.io/openapi.json -o src/storelayer-api.d.tsMany response schemas in the spec describe only the envelope (data is untyped), so keep hand-written response types like the ones above for the fields you read. Regenerate when you upgrade and review the diff.