API reference
Read and write a store's products, orders, customers, inventory, collections and locations from your own code, and get notified of changes with signed webhooks. The API is included in the Starter and Pro plans.
On this page
Authentication
Create a key in Settings → API and webhooks → Create key. The key starts with stl_ and is shown once : copy it then. It acts as the staff member who created it, in that store only, with their role. Revoke it from the same page.
Send it as a bearer token on every request :
curl https://storlyne.slimbennasrallah.com/api/v1/shop \
-H "Authorization: Bearer stl_…"Conventions
- Base URL :
https://storlyne.slimbennasrallah.com/api/v1. Bodies are JSON; sendContent-Type: application/jsonwithPOSTandPUT. - Money is an integer number of millimes (1 TND = 1000) :
25000is 25,000 DT. Tax rates are basis points (1900= 19 %). - Dates are ISO 8601 strings in UTC, like
2026-11-12T09:51:03.831Z. - A single object comes back as
{ "data": { … } }. Lists are paginated :{ "data": [ … ], "page": 1, "limit": 50, "total": 132 }, with?page=2&limit=100(limit 1 to 250, default 50). - Rate limit : 120 requests a minute per key. Over it you get a 429; wait a few seconds and retry.
- A store whose plan has expired is read-only :
GETworks, writes return 402.
Errors are { "error": "message" } with an HTTP status :
400 | Invalid input; the message names the field |
401 | Missing, invalid or revoked key |
402 | Not in the store's plan, or a plan limit reached (orders a month, products) |
403 | The key owner's role can't do this, or they are no longer staff |
404 | Unknown endpoint or record |
409 | Not enough stock to create an order (code "stock", with issues) |
423 | The store is suspended |
429 | Rate limit reached |
Endpoints
| Method | Path | Notes |
|---|---|---|
GET | /shop | The store : name, handle, address, matricule fiscal (taxId), currency, tax settings, storefront URL, plan |
GET | /products | ?page &limit &status=active|draft|archived &q= (title, handle, vendor, type, tag, SKU, barcode) |
GET | /products/:id | |
POST | /products | Creates a product |
PUT | /products/:id | Send only the fields to change |
DELETE | /products/:id | Needs the admin role |
GET | /orders | ?page &limit &status=open|closed|cancelled|any &financial_status= &fulfillment_status= &since=2026-11-01 &updated_since= |
GET | /orders/:number | The order number, like 1001 |
POST | /orders | Creates an order with source "api" |
GET | /customers | ?page &limit &q= (name, email, phone, tag) &accepts_marketing=true |
GET | /customers/:id | |
POST | /customers | Creates a customer |
PUT | /customers/:id | Send only the fields to change |
GET | /inventory | Stock levels : ?variant_id=a,b,c &location_id= &page &limit |
POST | /inventory/set | { variantId, locationId, available } sets the quantity and logs an adjustment |
GET | /collections | With productIds (manual and smart collections) |
GET | /collections/:id | |
GET | /locations | Stores and warehouses holding stock |
Products
A product has title, handle, descriptionHtml, status (active, draft, archived), vendor, productType, tags, options (up to 3, { name, values }), images ({ url, alt }), variants, collectionIds (manual collections), seoTitle, seoDescription.
Each variant has title, option1..3, sku, barcode, price, compareAtPrice, costPrice, weightGrams, taxable, taxRate (null = store default), requiresShipping, trackInventory, continueSelling, imageUrl, and, when creating it, stock : { "<locationId>": quantity }. Responses include each variant's id and available (stock over all locations).
# list active products
curl "https://storlyne.slimbennasrallah.com/api/v1/products?status=active&limit=20" -H "Authorization: Bearer stl_…"
# one product
curl https://storlyne.slimbennasrallah.com/api/v1/products/PRODUCT_ID -H "Authorization: Bearer stl_…"
# create
curl -X POST https://storlyne.slimbennasrallah.com/api/v1/products \
-H "Authorization: Bearer stl_…" -H "Content-Type: application/json" \
-d '{
"title": "Robe en lin",
"status": "active",
"productType": "Robe",
"options": [{ "name": "Taille", "values": ["S", "M"] }],
"variants": [
{ "option1": "S", "sku": "ROBE-S", "price": 89000, "stock": { "LOCATION_ID": 5 } },
{ "option1": "M", "sku": "ROBE-M", "price": 89000 }
]
}'
# update : only the fields sent change
curl -X PUT https://storlyne.slimbennasrallah.com/api/v1/products/PRODUCT_ID \
-H "Authorization: Bearer stl_…" -H "Content-Type: application/json" \
-d '{ "vendor": "Atelier Yasmine", "tags": ["été", "lin"] }'
# delete
curl -X DELETE https://storlyne.slimbennasrallah.com/api/v1/products/PRODUCT_ID -H "Authorization: Bearer stl_…"Sending variants in a PUT replaces the list : keep a variant by including its id, variants left out are deleted. Change stock with /inventory/set, not with PUT /products.
Orders
An order has number, name (#1001), status (open, closed, cancelled), financialStatus (pending, paid, partially_paid, refunded, partially_refunded, voided), fulfillmentStatus (unfulfilled, partial, fulfilled, returned), confirmationStatus (pending, confirmed, rejected : cash-on-delivery confirmation), paymentMethod, source, customer contact, subtotal, discountTotal, shippingTotal, taxTotal, timbre, giftCardTotal, total, paidTotal, refundedTotal, addresses, shippingLine, discountCodes, lines, fulfillments and refunds.
# unpaid orders since November 1st
curl "https://storlyne.slimbennasrallah.com/api/v1/orders?financial_status=pending&since=2026-11-01" -H "Authorization: Bearer stl_…"
# one order by number
curl https://storlyne.slimbennasrallah.com/api/v1/orders/1001 -H "Authorization: Bearer stl_…"
# create : priced by Storlyne (discounts, VAT, timbre)
curl -X POST https://storlyne.slimbennasrallah.com/api/v1/orders \
-H "Authorization: Bearer stl_…" -H "Content-Type: application/json" \
-d '{
"lines": [{ "variantId": "VARIANT_ID", "quantity": 2 }],
"customer": { "firstName": "Amira", "lastName": "Ben Salah", "phone": "22 123 456", "email": "amira@example.com" },
"shippingAddress": { "firstName": "Amira", "lastName": "Ben Salah", "phone": "22 123 456",
"address1": "12 rue de Marseille", "city": "Tunis", "governorate": "TUN" },
"shippingLine": { "title": "Livraison standard", "price": 7000 },
"paymentMethod": "cod",
"discountCodes": ["BIENVENUE"],
"note": "Appeler avant de livrer"
}'Body fields : lines (required; { variantId, quantity }, or a custom line { title, price, quantity, taxRate? }), customerId or customer { firstName, lastName, email, phone, acceptsMarketing }, email, phone, shippingAddress and billingAddress (firstName, lastName, phone, address1, address2, city, governorate code like TUN or SFA, postalCode, country), shippingRateId or shippingLine { title, price }, discountCodes, giftCardCodes, paymentMethod (cod, bank_transfer, in_store, manual…), markPaid, locationId, note, tags, skipStockCheck, sendConfirmation (default true : emails the customer).
Out of stock items give a 409 with issues. Each order counts towards the plan's orders a month.
Customers
curl "https://storlyne.slimbennasrallah.com/api/v1/customers?q=amira" -H "Authorization: Bearer stl_…"
curl https://storlyne.slimbennasrallah.com/api/v1/customers/CUSTOMER_ID -H "Authorization: Bearer stl_…"
curl -X POST https://storlyne.slimbennasrallah.com/api/v1/customers \
-H "Authorization: Bearer stl_…" -H "Content-Type: application/json" \
-d '{ "firstName": "Amira", "lastName": "Ben Salah", "email": "amira@example.com",
"phone": "22 123 456", "acceptsMarketing": true, "tags": ["vip"],
"addresses": [{ "address1": "12 rue de Marseille", "city": "Tunis", "governorate": "TUN", "isDefault": true }] }'
curl -X PUT https://storlyne.slimbennasrallah.com/api/v1/customers/CUSTOMER_ID \
-H "Authorization: Bearer stl_…" -H "Content-Type: application/json" \
-d '{ "tags": ["vip", "grossiste"] }'Fields : firstName, lastName, email, phone (Tunisian numbers are stored as +216 XX XXX XXX), locale (fr, ar, en), acceptsMarketing, tags, note, addresses. Sending addresses replaces the list (keep one by including its id). Responses add ordersCount, totalSpent and lastOrderAt.
Inventory, collections and locations
# levels of some variants at every location
curl "https://storlyne.slimbennasrallah.com/api/v1/inventory?variant_id=VARIANT_ID" -H "Authorization: Bearer stl_…"
# set the quantity at a location (logged as a correction in the stock history)
curl -X POST https://storlyne.slimbennasrallah.com/api/v1/inventory/set \
-H "Authorization: Bearer stl_…" -H "Content-Type: application/json" \
-d '{ "variantId": "VARIANT_ID", "locationId": "LOCATION_ID", "available": 12 }'
curl https://storlyne.slimbennasrallah.com/api/v1/collections -H "Authorization: Bearer stl_…"
curl https://storlyne.slimbennasrallah.com/api/v1/collections/COLLECTION_ID -H "Authorization: Bearer stl_…"
curl https://storlyne.slimbennasrallah.com/api/v1/locations -H "Authorization: Bearer stl_…"/inventory/set answers { variantId, locationId, available, delta } and sends an inventory/update webhook when the quantity changed. Collections include productIds, for manual and smart collections alike.
Webhooks
Admins add webhooks in Settings → API and webhooks : an HTTPS URL and the topics to receive. Storlyne POSTs a JSON body to the URL after each change. Failed deliveries (network error or a non-2xx answer) are retried 5 times with an increasing delay.
| Topic | When |
|---|---|
orders/create | An order is placed (online store, point of sale, draft, B2B, API) |
orders/updated | An order is edited |
orders/paid | An order becomes paid |
orders/fulfilled | An order is fulfilled |
orders/cancelled | An order is cancelled |
products/create | A product is created (the body is the product, as in the API) |
products/update | A product is updated |
products/delete | A product is deleted ({ id }) |
customers/create | A customer is created (the body is the customer, as in the API) |
customers/update | A customer is updated |
inventory/update | A stock level changes ({ variantId, productId, sku, locationId, available, delta, reason }) |
checkouts/create | A customer starts a checkout |
Each request carries x-storlyne-topic (the topic), x-storlyne-delivery (a unique delivery id, to ignore a retry you already handled) and x-storlyne-hmac-sha256 : the hex HMAC-SHA256 of the raw body, keyed with the webhook's secret. Verify it on the raw body before trusting the request. Node.js (Express) :
import crypto from "node:crypto";
import express from "express";
const app = express();
app.post("/storlyne", express.raw({ type: "application/json" }), (req, res) => {
const expected = crypto.createHmac("sha256", process.env.STORLYNE_WEBHOOK_SECRET).update(req.body).digest("hex");
const given = req.get("x-storlyne-hmac-sha256") ?? "";
if (given.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))) return res.sendStatus(401);
const event = JSON.parse(req.body.toString("utf8"));
console.log(req.get("x-storlyne-topic"), event);
res.sendStatus(200);
});PHP :
$body = file_get_contents('php://input');
$expected = hash_hmac('sha256', $body, getenv('STORLYNE_WEBHOOK_SECRET'));
if (!hash_equals($expected, $_SERVER['HTTP_X_STORLYNE_HMAC_SHA256'] ?? '')) { http_response_code(401); exit; }Answer with a 2xx within 10 seconds; do slow work after answering.