Aller au contenu

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; send Content-Type: application/json with POST and PUT.
  • Money is an integer number of millimes (1 TND = 1000) : 25000 is 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 : GET works, writes return 402.

Errors are { "error": "message" } with an HTTP status :

400Invalid input; the message names the field
401Missing, invalid or revoked key
402Not in the store's plan, or a plan limit reached (orders a month, products)
403The key owner's role can't do this, or they are no longer staff
404Unknown endpoint or record
409Not enough stock to create an order (code "stock", with issues)
423The store is suspended
429Rate limit reached

Endpoints

MethodPathNotes
GET/shopThe 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/productsCreates a product
PUT/products/:idSend only the fields to change
DELETE/products/:idNeeds the admin role
GET/orders?page &limit &status=open|closed|cancelled|any &financial_status= &fulfillment_status= &since=2026-11-01 &updated_since=
GET/orders/:numberThe order number, like 1001
POST/ordersCreates an order with source "api"
GET/customers?page &limit &q= (name, email, phone, tag) &accepts_marketing=true
GET/customers/:id
POST/customersCreates a customer
PUT/customers/:idSend only the fields to change
GET/inventoryStock levels : ?variant_id=a,b,c &location_id= &page &limit
POST/inventory/set{ variantId, locationId, available } sets the quantity and logs an adjustment
GET/collectionsWith productIds (manual and smart collections)
GET/collections/:id
GET/locationsStores 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.

TopicWhen
orders/createAn order is placed (online store, point of sale, draft, B2B, API)
orders/updatedAn order is edited
orders/paidAn order becomes paid
orders/fulfilledAn order is fulfilled
orders/cancelledAn order is cancelled
products/createA product is created (the body is the product, as in the API)
products/updateA product is updated
products/deleteA product is deleted ({ id })
customers/createA customer is created (the body is the customer, as in the API)
customers/updateA customer is updated
inventory/updateA stock level changes ({ variantId, productId, sku, locationId, available, delta, reason })
checkouts/createA 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.

← Back to the documentation