HTTP Integration
Any language with an HTTP client can integrate Storelayer: send JSON with a Bearer API key to https://api.storelayer.io, read data on success, read error on failure. This page covers the delivery concerns that apply in every language; the JavaScript page has a complete typed client you can port.
Request template
Section titled “Request template”POST /projects/prj_ab12cd34ef56/users/cus_1001/wallet/spend HTTP/1.1Host: api.storelayer.ioAuthorization: Bearer sk_your_key_hereContent-Type: application/json
{ "assetType": "points", "amount": 250, "referenceId": "ord_5002", "referenceType": "order" }Every response body is one of:
{ "success": true, "data": { "...": "..." } }{ "success": false, "error": { "code": 2601, "message": "Insufficient balance" } }{ "success": false, "error": [ { "path": ["amount"], "message": "..." } ], "data": { "...": "..." } }The third shape is a schema-validation failure (400). Branch on whether error is an array. Promotion evaluation can also report cart validation as { "success": false, "error": { "code": "VALIDATION_ERROR", "message": "…" } } — a string code — so don’t assume code is always a number. Codes are listed in the API overview.
Delivery rules
Section titled “Delivery rules”Writes that move value are not idempotent. wallet/earn and wallet/spend record a new transaction on every call; referenceId is stored but not used to deduplicate. Retry them only after confirming the first attempt did not land:
- Set a client timeout (for example 10 s).
- On timeout or
5xx, listGET /projects/{projectId}/users/{userId}/wallet/transactionsand look for yourreferenceId. - Retry only if it is absent.
For balance targets that must be exact (imports, corrections), use POST …/wallet/modify with targetAmount; repeating it with the same target produces no further change. See wallets.
| Call type | Safe to retry blindly? |
|---|---|
GET reads | Yes, with backoff |
POST …/promotions/evaluate (preview) | Yes — computes discounts without recording usage |
POST …/promotions/evaluate with dryRun: false | Yes, with the same transactionId — a repeat returns the saved result marked duplicate: true instead of redeeming twice |
POST …/wallet/modify (same targetAmount) | Yes |
POST …/wallet/earn, …/wallet/spend | No — check transactions first |
POST /ingest/events | No — a duplicate event runs your rules again; deduplicate on your side (for example keep orderId in the payload and check before sending) |
POST …/users | Yes — a repeat returns 409 code 2500, treat as success |
Asynchronous events
Section titled “Asynchronous events”POST /ingest/events answers 201 { "id": "evt_…", "status": "accepted" } immediately. Rules run afterwards. Store the event ID, then inspect the run under Workflows in the API reference or poll the affected balance with a deadline. See workflows and debugging.
Generating a client
Section titled “Generating a client”The OpenAPI document (OpenAPI 3, bearerAuth security) can feed any generator, e.g.:
npx @openapitools/openapi-generator-cli generate \ -i https://api.storelayer.io/openapi.json -g python -o ./storelayer-clientExpect to add response types yourself: most endpoints document only the { success, data } envelope with an untyped data. Request bodies and parameters are typed. The spec declares Bearer auth only; /ingest/events additionally accepts X-API-Key, which you don’t need.
Keep useful evidence
Section titled “Keep useful evidence”Log the method, path, status, error.code, Storelayer IDs (evt_…, transaction IDs), and your referenceId. Never log the API key or full customer PII.