Full REST API for integrating with QuickShop stores.
Orders, products, inventory, customers, Storefront and mobile - all available via API.
https://my-quickshop.com/api/v1In the merchant admin go to Settings → API Keys and create a new key with the scopes you need.
Every request requires an X-API-Key header containing your key.
Read the reference below and start shipping integrations.
curl -X GET "https://my-quickshop.com/api/v1/orders" \
-H "X-API-Key: qs_live_xxxxxxxxxxxxxxxxxxxx"Every call requires an API key
orders:readorders:writeproducts:readproducts:writecustomers:readinventory:readinventory:writediscounts:readdiscounts:writeanalytics:readwebhooks:readwebhooks:writestorefront:readcustomer:readcustomer:writemobile:writeX-RateLimit-Remaining - calls left in the current windowX-RateLimit-Reset - when the window resetsunauthorized- Missing or invalid API keyforbidden- Missing scope for this resourcenot_found- Resource not foundinvalid_request- Bad request payloadrate_limited- Rate limit exceeded/api/v1/ordersList orders/api/v1/orders/{id}Get order details/api/v1/orders/{id}Update order/api/v1/orders/{id}/edit-itemsEdit order line items/api/v1/orders/{id}/fulfillMark order as fulfilled/api/v1/orders/{id}/cancelCancel order/api/v1/productsList products/api/v1/productsCreate product/api/v1/products/{id}Get product details/api/v1/products/{id}Update product/api/v1/categoriesList categories/api/v1/categoriesCreate category/api/v1/categories/{id}Get category details/api/v1/categories/{id}Update category/api/v1/categories/{id}Delete category/api/v1/inventory/{id}Get inventory level/api/v1/inventory/{id}Adjust inventory/api/v1/customersList customers/api/v1/discountsList discounts and coupons/api/v1/discountsCreate discount/coupon/api/v1/discounts/{id}Get discount details/api/v1/discounts/{id}Update discount/api/v1/discounts/{id}Delete discount/api/v1/analyticsAggregated stats and reports/api/v1/webhooksList webhooks/api/v1/webhooksCreate webhook/api/v1/webhooks/{id}Get webhook details/api/v1/webhooks/{id}Update webhook/api/v1/webhooks/{id}Delete webhook/api/v1/webhooks/{id}/deliveriesDelivery history (status, response, errors)What we POST to the URL configured via POST /api/v1/webhooks when an event fires in the store.
QuickShop sends a POST with Content-Type: application/json. The top-level field is always event (not type), along with a timestamp and a data object whose contents depend on the event type.
{
"event": "order.created",
"timestamp": "2026-04-23T12:00:00.000Z",
"data": {
/* fields per event type - see table below */
}
}Content-Type: application/jsonX-Webhook-Signature: sha256=<hmac> (HMAC-SHA256 of the raw body using the secret - verify it on your end).X-Webhook-Event: <event> and X-Webhook-Test: true, and the body has "test": true at the top level.Integration testing: the Webhooks page in the merchant admin has a Test ▾ dropdown listing every event the webhook is subscribed to. Picking an event POSTs a synthetic payload that matches production exactly (including the HMAC signature) - so you can verify your endpoint, parser, and signature check without making a real sale. The only difference is the "test": true flag.
Delivery logs: GET /api/v1/webhooks/{id}/deliveries returns recent delivery attempts. Each entry includes: request_body (the exact JSON we POSTed), status_code, response_body, error, duration_ms, and event_type - so you can verify exactly what was sent without merchant dashboard access.
data is not the full REST model of the order or product - for full details (line items, addresses, etc.) call the API with your X-API-Key, e.g. GET /api/v1/orders/{id} once you have the order id.
| event | primary data fields |
|---|---|
| order.created | orderId, orderNumber, customerEmail, customerName, total, itemCount, couponCode (optional), timestamp |
| order.paid | All fields from order.created plus paymentMethod. Fired after a successful checkout, manual “Mark as paid” from the dashboard, or remaining-balance collection. |
| order.updated | orderId, orderNumber, changes (map: field → { before, after }), source (admin/api/system), timestamp. Fired on address, status, items, notes, or amount edits - inspect changes to see what moved. |
| order.cancelled | orderId, orderNumber, customerEmail, customerName, cancelReason, timestamp |
| product.low_stock | productName, inventory |
| product.out_of_stock | productName, inventory (0) |
You can subscribe to all events with *in the webhook's events array (via the admin UI or the API).
Compute HMAC-SHA256 over the raw request body using the webhook secret. Compare it (constant-time) against the value in X-Webhook-Signature after the sha256= prefix.
// Node.js (Express)
import crypto from 'node:crypto';
app.post('/quickshop/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.header('X-Webhook-Signature') || '';
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.QUICKSHOP_WEBHOOK_SECRET)
.update(req.body) // raw Buffer, NOT JSON.stringify
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.status(401).end('bad signature');
}
const payload = JSON.parse(req.body.toString('utf8'));
if (payload.test === true) {
// synthetic test delivery - ack but skip side-effects
return res.status(200).end();
}
switch (payload.event) {
case 'order.paid': /* ... */ break;
case 'order.updated': /* inspect payload.data.changes */ break;
case 'order.cancelled': /* ... */ break;
}
res.status(200).end();
});Public endpoints for the storefront and mobile apps. Customer authentication uses a Customer Session.
/api/storefront/{slug}/configStore config (name, logo, colors, currency)/api/storefront/{slug}/app-configMobile app config (JSON)/api/storefront/{slug}/app-configSave mobile config (Admin)/api/storefront/{slug}/productsProduct catalog (filter, sort, paginate)/api/storefront/{slug}/products/{productSlug}Product details + variants + images/api/storefront/{slug}/categoriesList categories (with product counts)/api/customer/ordersAuthenticated customer order history/api/customer/orders/{orderNumber}Order details + shipment tracking/api/customer/updateUpdate customer profile/api/customer/addressesList saved addresses/api/customer/addressesAdd address/api/customer/addressesDelete address/api/customer/wishlistGet wishlist/api/customer/wishlistAdd to wishlist/api/customer/wishlistToggle (add/remove)/api/customer/wishlist/{productId}Check if product is wishlisted/api/mobile/device/registerRegister device for push notifications/api/mobile/notifications/preferencesGet notification preferences/api/mobile/notifications/preferencesUpdate notification preferencesThe Storefront API uses a Customer Session (email-OTP based) for authenticated customer endpoints. Public endpoints like config, products, and categories require no auth. Customer endpoints (customer/*) require a session token.
const API_KEY = 'qs_live_xxxx';
const BASE_URL = 'https://my-quickshop.com/api/v1';
async function getOrders() {
const response = await fetch(`${BASE_URL}/orders`, {
headers: {
'X-API-Key': API_KEY,
},
});
const { data, meta } = await response.json();
return data;
}
async function updateInventory(productId, adjustment) {
const response = await fetch(`${BASE_URL}/inventory/${productId}`, {
method: 'PATCH',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'product',
adjustment,
}),
});
return response.json();
}import requests
API_KEY = 'qs_live_xxxx'
BASE_URL = 'https://my-quickshop.com/api/v1'
HEADERS = {'X-API-Key': API_KEY}
def get_orders(page=1, limit=50):
response = requests.get(
f'{BASE_URL}/orders',
headers=HEADERS,
params={'page': page, 'limit': limit}
)
return response.json()['data']
def update_order_status(order_id, status):
response = requests.patch(
f'{BASE_URL}/orders/{order_id}',
headers=HEADERS,
json={'status': status}
)
return response.json()// Create a new product, optionally downloading images to our CDN
const response = await fetch('https://my-quickshop.com/api/v1/products', {
method: 'POST',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: "Sample product",
slug: "sample-product",
description: "Product description",
price: "99.90",
compare_price: "149.90",
inventory: 100,
track_inventory: true,
category_ids: ["cat_123"],
// Images - external URLs
images: [
{ url: "https://example.com/image1.jpg", alt: "Primary image", is_primary: true },
{ url: "https://example.com/image2.jpg" }
],
// download_images: true => download, convert to WebP, and re-host on Vercel Blob
// download_images: false (default) => keep the URL as-is
download_images: true
})
});
// Response
{
"success": true,
"data": {
"id": "prod_xxx",
"name": "Sample product",
"slug": "sample-product",
"images": [
{ "id": "img_1", "url": "https://xxx.blob.vercel-storage.com/...", "is_primary": true }
]
}
}💡 With download_images: true, images are fetched, converted to WebP, and uploaded to Vercel Blob.
🎥 Video (media_type: "video") is stored as a URL - any public source works (CDN / R2 / YouTube etc.).
// Edit order items: add, remove, or change quantity
const response = await fetch(
`${BASE_URL}/orders/${orderId}/edit-items`,
{
method: 'POST',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
// Change quantity of an existing item
update_quantity: [
{ item_id: "order-item-uuid", new_quantity: 3 }
],
// Add a new item to the order
add: [
{
product_id: "product-uuid",
variant_id: "variant-uuid", // optional
name: "Product Name",
variant_title: "M / Red", // optional
quantity: 2,
price: 49.90
}
],
// Remove items from the order
remove: ["order-item-uuid-to-remove"]
})
}
);
// Response
{
"data": {
"id": "order-uuid",
"order_number": 1042,
"old_total": 199.60,
"new_total": 249.50,
"price_difference": 49.90, // positive = charge more, negative = refund
"subtotal": 259.50,
"discount_amount": 10.00, // stays frozen (not recalculated)
"total": 249.50,
"line_items": [
{ "id": "...", "name": "...", "quantity": 3, "price": 49.90, "total": 149.70 }
]
}
}Only unfulfilled orders can be edited. Discounts remain frozen - only item totals change.
Inventory is automatically adjusted (both product and variant levels).
const STORE = 'my-store';
const BASE = 'https://my-quickshop.com/api';
// Fetch store config
const config = await fetch(
`${BASE}/storefront/${STORE}/config`
).then(r => r.json());
// Fetch products with filters + paging
const products = await fetch(
`${BASE}/storefront/${STORE}/products?` +
`page=1&limit=20&sort=newest`
).then(r => r.json());
// Fetch a single product
const product = await fetch(
`${BASE}/storefront/${STORE}/products/my-product`
).then(r => r.json());// Add to wishlist
await fetch('/api/customer/wishlist', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': sessionCookie,
},
body: JSON.stringify({
productId: 'prod_xxx'
}),
});
// Fetch customer orders
const orders = await fetch(
'/api/customer/orders?page=1&limit=10',
{ headers: { 'Cookie': sessionCookie } }
).then(r => r.json());
// Update profile
await fetch('/api/customer/update', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Cookie': sessionCookie,
},
body: JSON.stringify({
firstName: 'Daniel',
lastName: 'Cohen',
phone: '+972501234567',
}),
});{
"data": [
{
"id": "uuid",
"order_number": "1001",
"status": "processing",
"total": 480.00,
"created_at": "2026-01-06T10:00:00Z"
}
],
"meta": {
"pagination": {
"page": 1,
"limit": 50,
"total": 150,
"total_pages": 3,
"has_next": true,
"has_prev": false
}
}
}{
"error": {
"code": "not_found",
"message": "Order not found"
}
}Create a developer account, generate an API key, and start shipping integrations.