{"openapi":"3.1.0","info":{"title":"NShop Storefront API","version":"1.1.0","summary":"Build a storefront on top of an NShop e-commerce account.","description":"Authenticate with an API key created in NShop (E-commerce add-on, API tab). Every key belongs to one company: the tenant is resolved from the key and never from the request. All successful responses are wrapped in `{ data }` (plus `meta` for paginated lists); errors use `{ error: { code, message, details? } }`."},"servers":[{"url":"https://nshop.naqsh-tech.com","description":"Production"},{"url":"http://localhost:3000","description":"Local development"}],"tags":[{"name":"Meta","description":"Health and discovery."},{"name":"Store","description":"Store configuration, theme, shipping and banner texts."},{"name":"Catalog","description":"Categories, products and best sellers."},{"name":"Orders","description":"Place and track cash-on-delivery orders."}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"paths":{"/api/v1/health":{"get":{"tags":["Meta"],"operationId":"getHealth","summary":"Liveness probe","description":"Public. No API key needed. Rate limited per IP (60/min).","security":[],"responses":{"200":{"description":"Service is up.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Health"}}},"example":{"data":{"status":"ok","version":"v1","time":"2026-10-10T09:00:00.000Z"}}}}},"429":{"description":"Rate limit exceeded. Wait `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Too many requests. Slow down and retry."}}}}}}}},"/api/v1/store":{"get":{"tags":["Store"],"operationId":"getStore","summary":"Store configuration","description":"Public company fields, e-commerce settings and the resolved theme. Cached for 300 s. Requires scope `store:read`.","x-required-scope":"store:read","responses":{"200":{"description":"Store configuration.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Store"}}}}}},"401":{"description":"The API key is missing, invalid, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"The API key is missing, invalid, revoked or expired."}}}}},"403":{"description":"The key lacks the required scope, or e-commerce is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"The key lacks the required scope, or e-commerce is disabled."}}}}},"404":{"description":"Store not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Store not found."}}}}},"429":{"description":"Rate limit exceeded. Wait `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Too many requests. Slow down and retry."}}}}},"500":{"description":"Unexpected error. Quote the X-Request-Id when contacting support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Unexpected error. Quote the X-Request-Id when contacting support."}}}}}}}},"/api/v1/shipping-cities":{"get":{"tags":["Store"],"operationId":"listShippingCities","summary":"Shipping cities and their cost","description":"Use `city_name` as `customer_city` when placing an order. Cached 300 s. Requires `store:read`.","x-required-scope":"store:read","responses":{"200":{"description":"List of shipping cities.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ShippingCity"}}}},"example":{"data":[{"id":"6f1c1c1e-5a40-4e0d-9d52-1d1a0b8f3a11","city_name":"Ramallah","shipping_cost":20,"sort_order":0}]}}}},"401":{"description":"The API key is missing, invalid, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"The API key is missing, invalid, revoked or expired."}}}}},"403":{"description":"The key lacks the required scope, or e-commerce is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"The key lacks the required scope, or e-commerce is disabled."}}}}},"429":{"description":"Rate limit exceeded. Wait `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Too many requests. Slow down and retry."}}}}},"500":{"description":"Unexpected error. Quote the X-Request-Id when contacting support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Unexpected error. Quote the X-Request-Id when contacting support."}}}}}}}},"/api/v1/banner-statements":{"get":{"tags":["Store"],"operationId":"listBannerStatements","summary":"Banner statements","description":"Active announcement texts in display order. Cached 300 s. Requires `store:read`.","x-required-scope":"store:read","responses":{"200":{"description":"Banner statements.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BannerStatement"}}}}}}},"401":{"description":"The API key is missing, invalid, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"The API key is missing, invalid, revoked or expired."}}}}},"403":{"description":"The key lacks the required scope, or e-commerce is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"The key lacks the required scope, or e-commerce is disabled."}}}}},"429":{"description":"Rate limit exceeded. Wait `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Too many requests. Slow down and retry."}}}}},"500":{"description":"Unexpected error. Quote the X-Request-Id when contacting support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Unexpected error. Quote the X-Request-Id when contacting support."}}}}}}}},"/api/v1/categories":{"get":{"tags":["Catalog"],"operationId":"listCategories","summary":"Categories","description":"Active categories, flat by default or nested with `tree=true`. Cached 60 s. Requires `catalog:read`.","x-required-scope":"catalog:read","parameters":[{"name":"tree","in":"query","required":false,"schema":{"type":"string","enum":["true","false"],"default":"false"},"description":"Return a nested tree (`children`)."}],"responses":{"200":{"description":"Categories.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Category"}}}}}}},"400":{"description":"Invalid query.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_error","message":"Invalid query."}}}}},"401":{"description":"The API key is missing, invalid, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"The API key is missing, invalid, revoked or expired."}}}}},"403":{"description":"The key lacks the required scope, or e-commerce is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"The key lacks the required scope, or e-commerce is disabled."}}}}},"429":{"description":"Rate limit exceeded. Wait `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Too many requests. Slow down and retry."}}}}},"500":{"description":"Unexpected error. Quote the X-Request-Id when contacting support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Unexpected error. Quote the X-Request-Id when contacting support."}}}}}}}},"/api/v1/products":{"get":{"tags":["Catalog"],"operationId":"listProducts","summary":"List products","description":"Active products with live price (offers applied) and an informational stock count, assembled in one database call. Paginate with `page` or `offset`; filter with `category_id`, `q`, `ids` or `updated_since`. Cached 60 s. Requires `catalog:read`.","x-required-scope":"catalog:read","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":24}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1},"description":"1-based page number."},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0},"description":"Alternative to `page`."},{"name":"category_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"q","in":"query","schema":{"type":"string","maxLength":100},"description":"Search text. Case-insensitive substring match on name, SKU and barcode."},{"name":"updated_since","in":"query","schema":{"type":"string","format":"date-time"},"description":"Full ISO-8601 datetime, e.g. `2026-10-10T08:00:00Z` or `2026-09-21T06:45:26+00:00`. A date-only value such as `2026-09-21` returns 400 `validation_error`. Returns only products whose `updated_at` is on or after it (name-sorted like any listing). Use it for incremental sync. Products that were unpublished or deleted simply stop appearing and are not reported, so also do a full refresh periodically. An invalid value returns 400 `validation_error`."},{"name":"ids","in":"query","schema":{"type":"string"},"description":"Comma-separated product UUIDs (max 100)."}],"responses":{"200":{"description":"A page of products.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Product"}},"meta":{"$ref":"#/components/schemas/PageMeta"}}},"example":{"data":[],"meta":{"total":0,"limit":24,"page":1,"has_more":false}}}}},"400":{"description":"Invalid query.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_error","message":"Invalid query."}}}}},"401":{"description":"The API key is missing, invalid, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"The API key is missing, invalid, revoked or expired."}}}}},"403":{"description":"The key lacks the required scope, or e-commerce is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"The key lacks the required scope, or e-commerce is disabled."}}}}},"429":{"description":"Rate limit exceeded. Wait `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Too many requests. Slow down and retry."}}}}},"500":{"description":"Unexpected error. Quote the X-Request-Id when contacting support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Unexpected error. Quote the X-Request-Id when contacting support."}}}}}}}},"/api/v1/products/{id}":{"get":{"tags":["Catalog"],"operationId":"getProduct","summary":"Get one product","description":"Accepts the product id (UUID). Cached 60 s. Requires `catalog:read`.","x-required-scope":"catalog:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The product.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Product"}}}}}},"400":{"description":"Invalid id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_error","message":"Invalid id."}}}}},"401":{"description":"The API key is missing, invalid, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"The API key is missing, invalid, revoked or expired."}}}}},"403":{"description":"The key lacks the required scope, or e-commerce is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"The key lacks the required scope, or e-commerce is disabled."}}}}},"404":{"description":"Product not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Product not found."}}}}},"429":{"description":"Rate limit exceeded. Wait `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Too many requests. Slow down and retry."}}}}},"500":{"description":"Unexpected error. Quote the X-Request-Id when contacting support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Unexpected error. Quote the X-Request-Id when contacting support."}}}}}}}},"/api/v1/best-sellers":{"get":{"tags":["Catalog"],"operationId":"listBestSellers","summary":"Best-selling products","description":"Ranked by units sold in the last `days` days (cancelled and refunded orders excluded). Each product carries `units_sold`. Cached 60 s. Requires `catalog:read`.","x-required-scope":"catalog:read","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":50,"default":10}},{"name":"days","in":"query","schema":{"type":"integer","minimum":1,"maximum":365,"default":90}}],"responses":{"200":{"description":"Ranked products.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/Product"},{"type":"object","properties":{"units_sold":{"type":"integer"}}}]}}}}}}},"400":{"description":"Invalid query.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_error","message":"Invalid query."}}}}},"401":{"description":"The API key is missing, invalid, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"The API key is missing, invalid, revoked or expired."}}}}},"403":{"description":"The key lacks the required scope, or e-commerce is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"The key lacks the required scope, or e-commerce is disabled."}}}}},"429":{"description":"Rate limit exceeded. Wait `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Too many requests. Slow down and retry."}}}}},"500":{"description":"Unexpected error. Quote the X-Request-Id when contacting support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Unexpected error. Quote the X-Request-Id when contacting support."}}}}}}}},"/api/v1/orders":{"post":{"tags":["Orders"],"operationId":"createOrder","summary":"Place an order","description":"Creates a cash-on-delivery order. Send only product ids and quantities: prices, discounts and tax are never accepted (unknown fields are rejected). Every line is priced server-side from live data including active offers, shipping comes from the city's configured cost. Stock is not checked at this endpoint today: the shop reviews orders in NShop. Rate limited to 20/min. Requires `orders:write`.","x-required-scope":"orders:write","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderRequest"},"example":{"customer_name":"Ahmad Saleh","customer_phone":"0599123456","customer_city":"Ramallah","customer_address":"Al-Masyoun, Street 5, building 12","order_note":"Call before delivery","items":[{"product_id":"6f1c1c1e-5a40-4e0d-9d52-1d1a0b8f3a11","quantity":2}]}}}},"responses":{"201":{"description":"Order created. Replays of the same `Idempotency-Key` return the stored response with `Idempotent-Replayed: true`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/OrderCreated"}}},"example":{"data":{"id":"6f1c1c1e-5a40-4e0d-9d52-1d1a0b8f3a11","order_number":"ORD-2026-A1B2C3","status":"pending","subtotal":100,"shipping_amount":20,"discount_amount":0,"total_amount":120}}}}},"400":{"description":"Validation failed (`details.issues` lists the fields).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_error","message":"Validation failed (`details.issues` lists the fields)."}}}}},"401":{"description":"The API key is missing, invalid, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"The API key is missing, invalid, revoked or expired."}}}}},"403":{"description":"The key lacks the required scope, or e-commerce is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"The key lacks the required scope, or e-commerce is disabled."}}}}},"409":{"description":"`conflict` or `idempotency_conflict`. (`insufficient_stock` is reserved for when stock enforcement is enabled; it is not returned today.)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"`conflict` or `idempotency_conflict`. (`insufficient_stock` is reserved for when stock enforcement is enabled; it is not returned today.)"}}}}},"422":{"description":"A product is not available for sale.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"product_not_available","message":"A product is not available for sale."}}}}},"429":{"description":"Rate limit exceeded. Wait `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Too many requests. Slow down and retry."}}}}},"500":{"description":"Unexpected error. Quote the X-Request-Id when contacting support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Unexpected error. Quote the X-Request-Id when contacting support."}}}}},"502":{"description":"The order could not be placed. Safe to retry with the same Idempotency-Key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"The order could not be placed. Safe to retry with the same Idempotency-Key."}}}}}}}},"/api/v1/orders/{orderNumber}":{"get":{"tags":["Orders"],"operationId":"getOrder","summary":"Track an order","description":"Requires the last four digits of the customer's phone (`phone_last4`). A wrong or missing match answers exactly like an unknown order number (404), so order numbers cannot be probed. The phone is returned masked. Requires `orders:read`.","x-required-scope":"orders:read","parameters":[{"name":"orderNumber","in":"path","required":true,"schema":{"type":"string","minLength":3,"maxLength":64}},{"name":"phone_last4","in":"query","required":true,"schema":{"type":"string","pattern":"^\\d{4}$"}}],"responses":{"200":{"description":"The order and its items.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Order"}}}}}},"400":{"description":"Invalid query.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_error","message":"Invalid query."}}}}},"401":{"description":"The API key is missing, invalid, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"The API key is missing, invalid, revoked or expired."}}}}},"403":{"description":"The key lacks the required scope, or e-commerce is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_scope","message":"The key lacks the required scope, or e-commerce is disabled."}}}}},"404":{"description":"Order not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Order not found."}}}}},"429":{"description":"Rate limit exceeded. Wait `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Too many requests. Slow down and retry."}}}}},"500":{"description":"Unexpected error. Quote the X-Request-Id when contacting support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Unexpected error. Quote the X-Request-Id when contacting support."}}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"nsk_live_…","description":"`Authorization: Bearer nsk_live_…`"},"apiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Alternative to the Bearer header."}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"8-128 chars of `A-Z a-z 0-9 . _ : -`. Retries with the same key and body return the original response; the same key with a different body returns 409 `idempotency_conflict`.","schema":{"type":"string","minLength":8,"maxLength":128,"pattern":"^[A-Za-z0-9._:-]+$"}}},"headers":{"RequestId":{"description":"Unique id of this request. Quote it when contacting support.","schema":{"type":"string","format":"uuid"}},"RateLimitLimit":{"description":"Requests allowed per minute for this key and route.","schema":{"type":"integer"}},"RateLimitRemaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"RateLimitReset":{"description":"Unix time (seconds) when the window resets.","schema":{"type":"integer"}},"RetryAfter":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"IdempotentReplayed":{"description":"`true` when the response is a replay of an earlier request.","schema":{"type":"string","enum":["true"]}}},"schemas":{"Error":{"type":"object","description":"Every non-2xx response has this shape.","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Stable machine-readable code. Branch on this, not on `message`. `insufficient_stock` is reserved for when stock enforcement is enabled and is not returned today.","enum":["invalid_api_key","insufficient_scope","ecommerce_disabled","rate_limited","validation_error","not_found","conflict","insufficient_stock","product_not_available","idempotency_conflict","internal_error"],"example":"validation_error"},"message":{"type":"string","description":"Human-readable English text. May change; do not parse it.","example":"The API key is invalid, revoked or expired."},"details":{"type":"object","additionalProperties":true,"description":"Extra context, e.g. `product_id`, `required_scope` or `issues` (validation failures).","example":{"product_id":"6f1c1c1e-5a40-4e0d-9d52-1d1a0b8f3a11"}}}}}},"PageMeta":{"type":"object","description":"Pagination info returned next to `data` on list endpoints.","required":["total","limit","page","has_more"],"properties":{"total":{"type":"integer","description":"Total matching rows across all pages.","example":11},"limit":{"type":"integer","description":"Page size used.","example":24},"page":{"type":"integer","description":"1-based page number returned.","example":1},"has_more":{"type":"boolean","description":"`true` when another page exists.","example":false}}},"Health":{"type":"object","description":"Liveness probe response.","properties":{"status":{"type":"string","example":"ok"},"version":{"type":"string","example":"v1"},"time":{"type":"string","format":"date-time","description":"Server time (UTC).","example":"2026-10-10T09:00:00.000Z"}}},"Company":{"type":"object","description":"Public storefront identity of the company (`Store.company`). Only these whitelisted fields are ever returned; internal data is never exposed.","properties":{"name":{"type":"string","description":"Legal/business name.","example":"Al-Baraka Store"},"ecommerce_store_name":{"type":["string","null"],"description":"Display name of the online store. Prefer this over `name` in the UI.","example":"Al-Baraka Online"},"ecommerce_description":{"type":["string","null"],"description":"Short store description for the home page and SEO."},"ecommerce_logo_url":{"type":["string","null"],"format":"uri","description":"Logo image URL."},"ecommerce_banner_url":{"type":["string","null"],"format":"uri","description":"Main hero banner image URL."},"ecommerce_small_banner_2_url":{"type":["string","null"],"format":"uri","description":"Secondary banner image URL."},"ecommerce_small_banner_3_url":{"type":["string","null"],"format":"uri","description":"Third banner image URL."},"ecommerce_whatsapp":{"type":["string","null"],"description":"Store WhatsApp number for customer chat.","example":"970599123456"},"ecommerce_social_links":{"type":"object","additionalProperties":true,"description":"Social profile links keyed by network (e.g. `facebook`, `instagram`).","example":{"instagram":"https://instagram.com/albaraka"}},"ecommerce_contact_info":{"type":["object","null"],"additionalProperties":true,"description":"Contact details (phone, email, address) as configured by the shop."},"ecommerce_policies":{"type":"object","additionalProperties":true,"description":"Policy texts (shipping, returns, privacy) as configured by the shop."},"ecommerce_storefront_url":{"type":["string","null"],"format":"uri","description":"Public URL of the storefront."},"ecommerce_currency":{"type":["string","null"],"description":"Currency code for the online store. Use this for display; fall back to `currency` when null.","example":"ILS"},"currency":{"type":["string","null"],"description":"The company's accounting currency code (ILS, USD or JOD).","example":"ILS"},"ecommerce_language":{"type":["string","null"],"description":"Storefront language code.","example":"ar"},"ecommerce_theme":{"type":["string","null"],"description":"Name of the selected theme (matches `Theme.name`).","example":"modern"}}},"Settings":{"type":["object","null"],"description":"E-commerce settings row (`Store.settings`). `null` when the shop has not saved any settings yet.","properties":{"theme_config":{"type":["object","null"],"additionalProperties":true,"description":"Per-store theme overrides (colors, fonts, layout options)."},"payment_methods":{"type":["object","array","null"],"description":"Payment options the shop configured. Orders created through the API are cash on delivery."},"shipping_options":{"type":["object","array","null"],"description":"Shipping options configured by the shop. Costs per city come from `/shipping-cities`."},"currency":{"type":["string","null"],"example":"ILS"},"language":{"type":["string","null"],"example":"ar"},"timezone":{"type":["string","null"],"description":"IANA timezone of the shop.","example":"Asia/Hebron"},"business_hours":{"type":["object","null"],"additionalProperties":true,"description":"Opening hours by weekday."},"social_links":{"type":["object","null"],"additionalProperties":true,"description":"Social links configured in settings."},"seo_settings":{"type":["object","null"],"additionalProperties":true,"description":"SEO defaults (title, description, keywords)."},"updated_at":{"type":["string","null"],"format":"date-time","description":"Last time the settings changed."}}},"Theme":{"type":["object","null"],"description":"The resolved theme (`Store.theme`). `null` when the store has no active theme.","properties":{"name":{"type":"string","description":"Theme identifier (matches `Company.ecommerce_theme`).","example":"modern"},"display_name":{"type":"string","description":"Human-readable theme name.","example":"Modern"},"description":{"type":["string","null"]},"config":{"type":"object","additionalProperties":true,"description":"Theme defaults (colors, typography, layout)."},"preview_images":{"type":["array","null"],"items":{"type":"string","format":"uri"},"description":"Preview screenshots."},"is_premium":{"type":["boolean","null"],"description":"Whether the theme is a premium theme."}}},"Store":{"type":"object","description":"Everything needed to brand and configure a storefront, in one call.","properties":{"company":{"$ref":"#/components/schemas/Company"},"settings":{"$ref":"#/components/schemas/Settings"},"theme":{"$ref":"#/components/schemas/Theme"}}},"ShippingCity":{"type":"object","description":"A city the shop delivers to, with its delivery cost.","properties":{"id":{"type":"string","format":"uuid","example":"6f1c1c1e-5a40-4e0d-9d52-1d1a0b8f3a11"},"city_name":{"type":"string","description":"Send this exact value (case-insensitive) as `customer_city` when ordering.","example":"Ramallah"},"shipping_cost":{"type":"number","description":"Delivery cost in the shop currency. Added to the order total by the server.","example":20},"sort_order":{"type":["integer","null"],"description":"Display order (ascending).","example":0}}},"BannerStatement":{"type":"object","description":"A short announcement line shown above the store.","properties":{"id":{"type":"string","format":"uuid","example":"6f1c1c1e-5a40-4e0d-9d52-1d1a0b8f3a11"},"body":{"type":"string","description":"The text to display.","example":"Free delivery over 200 ILS"},"sort_order":{"type":["integer","null"],"description":"Display order (ascending).","example":0}}},"Category":{"type":"object","description":"A product category. With `tree=true`, children are nested.","properties":{"id":{"type":"string","format":"uuid","example":"6f1c1c1e-5a40-4e0d-9d52-1d1a0b8f3a11"},"name":{"type":"string","example":"Skin care"},"description":{"type":["string","null"]},"parent_id":{"type":["string","null"],"format":"uuid","description":"Parent category id, or null for a top-level category."},"level":{"type":["integer","null"],"description":"Depth in the tree (0 = top level).","example":0},"sort_order":{"type":["integer","null"],"description":"Display order among siblings (ascending).","example":1},"icon_url":{"type":["string","null"],"format":"uri","description":"Category icon image URL."},"children":{"type":"array","items":{"$ref":"#/components/schemas/Category"},"description":"Present only with `tree=true`."}}},"Offer":{"type":"object","description":"The best active promotion applied to a product. The discount is already included in `Product.price`.","properties":{"id":{"type":"string","format":"uuid","example":"6f1c1c1e-5a40-4e0d-9d52-1d1a0b8f3a11"},"kind":{"type":"string","enum":["percent","fixed"],"description":"`percent`: `value` is a percentage off the list price. `fixed`: `value` is an amount off the list price (never below zero).","example":"percent"},"value":{"type":"number","description":"Percent or amount, depending on `kind`.","example":15},"ends_at":{"type":["string","null"],"format":"date-time","description":"When the offer ends; null for open-ended offers. Use it for countdowns.","example":"2026-10-31T21:00:00.000Z"}}},"Product":{"type":"object","description":"A sellable product as it should appear in the storefront: live price with offers applied and an informational stock count.","properties":{"id":{"type":"string","format":"uuid","description":"Product id. Use it as `items[].product_id` when ordering.","example":"6f1c1c1e-5a40-4e0d-9d52-1d1a0b8f3a11"},"name":{"type":"string","example":"Vitamin C cream"},"sku":{"type":["string","null"],"description":"Stock keeping unit.","example":"CRM-001"},"barcode":{"type":["string","null"],"example":"6290000000012"},"description":{"type":["string","null"],"description":"Product description (may be null)."},"category_id":{"type":["string","null"],"format":"uuid"},"category_name":{"type":["string","null"],"example":"Skin care"},"images":{"type":"array","items":{"type":"string","format":"uri"},"description":"Public image URLs, main image first. Empty array when the product has no images.","example":["https://assets.example.com/p/cream-1.jpg"]},"price":{"type":"number","description":"Effective unit price AFTER the best active offer. This is what the customer pays per unit; show and charge this.","example":42.5},"compare_at_price":{"type":["number","null"],"description":"Original list price, set only when an offer applies (use it for a crossed-out price). Null when there is no offer.","example":50},"offer":{"oneOf":[{"$ref":"#/components/schemas/Offer"},{"type":"null"}],"description":"The offer that produced `price`, or null."},"in_stock":{"type":"boolean","description":"Informational, for display: `true` when `stock_quantity` is greater than 0.","example":true},"stock_quantity":{"type":"integer","description":"Informational, for display: the sum of (quantity - reserved_stock) over all of the company's shops and warehouses, never negative. The API does not currently reject orders that exceed stock, so do not rely on it as a guarantee.","example":18},"updated_at":{"type":"string","format":"date-time","description":"Latest change of the product or of its storefront cache entry. Use it with `updated_since` to sync incrementally.","example":"2026-09-21T06:45:26.062544+00:00"}}},"CreateOrderRequest":{"type":"object","description":"Body of `POST /api/v1/orders`. Unknown fields (for example `unit_price`, `total_price`, `discount_amount`, `tax_amount`, `payment_method`) are rejected with 400.","additionalProperties":false,"required":["customer_name","customer_phone","customer_city","customer_address","items"],"properties":{"customer_name":{"type":"string","minLength":2,"maxLength":200,"description":"Customer full name. Control characters are stripped and the value is trimmed.","example":"Ahmad Saleh"},"customer_phone":{"type":"string","description":"Palestinian mobile. Accepts 05XXXXXXXX, +9705XXXXXXXX, +9725XXXXXXXX, 009705XXXXXXXX and Arabic digits; stored normalized as 05XXXXXXXX.","example":"0599123456"},"customer_phone2":{"type":"string","description":"Optional second phone, same rule as `customer_phone`.","example":"0569123456"},"customer_email":{"type":"string","format":"email","maxLength":320,"description":"Optional, validated as an email address.","example":"ahmad@example.com"},"customer_city":{"type":"string","description":"Must match a `city_name` from `/api/v1/shipping-cities` (case and spacing insensitive) when the store configured cities. Shipping is charged from that city's cost.","example":"Ramallah"},"customer_address":{"type":"string","minLength":5,"maxLength":2000,"description":"Delivery address; at least 5 non-space characters.","example":"Al-Masyoun, Street 5, building 12"},"order_note":{"type":"string","maxLength":2000,"description":"Optional note for the shop.","example":"Call before delivery"},"items":{"type":"array","minItems":1,"maxItems":50,"description":"1 to 50 order lines. Send ids and quantities only.","items":{"type":"object","additionalProperties":false,"required":["product_id","quantity"],"properties":{"product_id":{"type":"string","format":"uuid","description":"`Product.id`.","example":"6f1c1c1e-5a40-4e0d-9d52-1d1a0b8f3a11"},"quantity":{"type":"integer","minimum":1,"maximum":9999,"description":"Units to buy (1-9999).","example":2}}}}}},"OrderStatus":{"type":"string","description":"Order lifecycle status. Orders created through this API start as `pending`. The shop owner changes the status in NShop; the API never does. Stock is adjusted by the shop in NShop for now: the API and the status changes do not reserve, deduct or release stock automatically.","enum":["pending","confirmed","processing","shipped","delivered","cancelled","refunded"],"example":"pending"},"PaymentStatus":{"type":"string","description":"Payment state, maintained by the shop. API orders are cash on delivery and start as `pending`.","enum":["pending","paid","failed","refunded"],"example":"pending"},"OrderCreated":{"type":"object","description":"Returned by `POST /api/v1/orders` (HTTP 201).","properties":{"id":{"type":"string","format":"uuid","example":"6f1c1c1e-5a40-4e0d-9d52-1d1a0b8f3a11"},"order_number":{"type":"string","description":"Customer-facing order number. Show it on the confirmation page and use it to track the order.","example":"ORD-2026-A1B2C3"},"status":{"$ref":"#/components/schemas/OrderStatus"},"subtotal":{"type":"number","description":"List-price total of the lines before offers and shipping.","example":100},"shipping_amount":{"type":"number","description":"Shipping charged, taken from the city's cost.","example":20},"discount_amount":{"type":"number","description":"Promotional saving computed by the server from active offers (never client supplied).","example":0},"total_amount":{"type":"number","description":"Amount the customer pays on delivery: lines after offers + shipping. The source of truth for the confirmation page.","example":120}}},"OrderItem":{"type":"object","description":"A line of an order, snapshotted at purchase time (later price changes do not alter it).","properties":{"product_id":{"type":["string","null"],"format":"uuid","description":"Null if the product was later deleted."},"product_name":{"type":"string","example":"Vitamin C cream"},"product_sku":{"type":["string","null"],"example":"CRM-001"},"quantity":{"type":"integer","example":2},"unit_price":{"type":"number","description":"Unit price charged (offer applied).","example":42.5},"total_price":{"type":"number","description":"`unit_price` x `quantity`.","example":85},"product_image_url":{"type":["string","null"],"format":"uri"}}},"Order":{"type":"object","description":"Returned by `GET /api/v1/orders/{orderNumber}`.","properties":{"order_number":{"type":"string","example":"ORD-2026-A1B2C3"},"customer_name":{"type":"string","example":"Ahmad Saleh"},"customer_phone_masked":{"type":["string","null"],"description":"All but the last four digits replaced by `*`.","example":"******3456"},"customer_address":{"type":["object","string","null"],"additionalProperties":true,"description":"Delivery address as stored (includes the city)."},"subtotal":{"type":"number","example":100},"shipping_amount":{"type":"number","example":20},"tax_amount":{"type":"number","description":"Always 0 for API orders.","example":0},"discount_amount":{"type":"number","example":0},"total_amount":{"type":"number","example":120},"status":{"$ref":"#/components/schemas/OrderStatus"},"payment_method":{"type":"string","description":"`cash` for API orders.","example":"cash"},"payment_status":{"$ref":"#/components/schemas/PaymentStatus"},"shipping_method":{"type":["string","null"],"description":"Set by the shop when it ships the order."},"tracking_number":{"type":["string","null"],"description":"Carrier tracking number, filled in by the shop."},"notes":{"type":["string","null"],"description":"The customer's order note."},"created_at":{"type":"string","format":"date-time","example":"2026-10-10T09:15:00.000Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-10-10T10:02:00.000Z"},"items":{"type":"array","items":{"$ref":"#/components/schemas/OrderItem"}}}}}}}