موجودی
مخاطب: برنامهنویس سایت فروشگاهی، نرمافزار انبارداری یا حسابداری که میخواهد موجودی پنل دیجیکالا را بخواند، با موجودی خود همگام کند یا فروشهای خود را در آن ثبت کند.
📖 مقدمه
Section titled “📖 مقدمه”دیجیکالا برای هر تنوع چند عدد موجودی جدا نگه میدارد: کالایی که در انبار خود شماست، کالایی که در انبار دیجیکالاست، واحدهایی که برای سفارشها رزرو شدهاند و واحدهایی که در راهاند. سلر ۳۶۰ همهی این اعداد را در یک مدل موجودی واحد کنار هم میگذارد؛ همان مدلی که همهی صفحههای پنل سلر ۳۶۰ از آن استفاده میکنند.
با مسیرهای این صفحه میتوانید:
| کار | مسیر | در دسترس |
|---|---|---|
| خواندن موجودی همهی تنوعها | GET /v1/stock |
فعال |
| خواندن موجودی یک تنوع | GET /v1/stock/{variant_id} |
فعال |
| تاریخچهی روزانهی موجودی | GET /v1/stock/history |
فعال |
| تعیین عدد مطلق موجودی فروشنده | PUT /v1/stock/{variant_id} |
فعال |
| ثبت فروش یا برگشت (تغییر نسبی) | POST /v1/stock/{variant_id}/adjustments |
فعال |
| تعیین موجودی چند تنوع در صف | POST /v1/stock/batch |
فعال |
و با رویدادهای وبهوک stock.changed، stock.out، stock.low و stock.stockout_soon حداکثر حدود ۱۵ ثانیه پس از هر تغییر خبردار شوید.
⚙️ پیشنیازها
Section titled “⚙️ پیشنیازها”دسترسی (Scope)
Section titled “دسترسی (Scope)”| دسترسی | برای |
|---|---|
stock:read |
همهی مسیرهای خواندن این صفحه |
stock:write |
تعیین مطلق، تغییر نسبی و تغییر در صف |
catalog:read |
پیشنهاد میشود: برای خواندن عنوان و کد تنوعها (GET /v1/variants) |
کلید را با کمترین دسترسی لازم بسازید. نرمافزاری که فقط موجودی را نمایش میدهد، به stock:write نیاز ندارد.
فعال بودن ماژول
Section titled “فعال بودن ماژول”ماژول «API و وبهوک» باید برای پنل فعال باشد؛ در غیر این صورت همهی مسیرها با کد 403 و خطای module_inactive پاسخ میدهند. برای پنلی که اشتراک آن به پایان رسیده است، خواندن ممکن است ولی همهی نوشتنها با خطای subscription_inactive رد میشوند.
آدرس پایه
Section titled “آدرس پایه”https://api.seller360.ir🧮 مدل موجودی
Section titled “🧮 مدل موجودی”فیلدها
Section titled “فیلدها”| فیلد | برچسب در پنل | معنی |
|---|---|---|
sellable |
«قابل فروش» | عددی که همین حالا در دیجیکالا فروختنی است. سایت شما باید همین عدد را نشان دهد. |
seller_stock |
«موجودی در انبار فروشنده» | کالایی که در انبار خودتان دارید و در پنل دیجیکالا ثبت کردهاید. |
warehouse_stock |
«موجودی در انبار دیجیکالا» | کالای شما که در انبار دیجیکالاست. |
reserved.digikala |
«رزرو ارسال دیجیکالا» | واحدهای رزروشده برای سفارشهایی که دیجیکالا ارسال میکند. |
reserved.seller |
«رزرو ارسال فروشنده» | واحدهای رزروشده برای سفارشهایی که خودتان ارسال میکنید. |
warehouse_available |
«باقیمانده در انبار دیجیکالا» | انبار دیجیکالا منهای رزرو دیجیکالا. |
reservation_shortfall |
— | رزرو دیجیکالا بیش از موجودی انبار آن؛ این واحدها را شما تامین میکنید. |
seller_available |
«باقیمانده در انبار فروشنده» | موجودی شما منهای کسری رزرو و رزرو ارسال فروشنده. |
sellable_calculated |
«تعداد قابل فروش» | محاسبهی سلر ۳۶۰ از روی اعداد بالا. |
in_transit.orders |
«در راه برای سفارشها» | واحدهای محمولههای سفارش در راه انبار دیجیکالا. |
in_transit.consignment |
«در محمولهی انبارش» | محمولههای انبارش ساختهشده و هنوز دریافتنشده. |
version |
— | شمارهی نسخه؛ با هر تغییر بزرگتر میشود. |
observed_at |
— | زمان آخرین خواندن این اعداد از دیجیکالا. |
stale |
— | true اگر پایش این پنل از حد معمول عقب افتاده باشد. |
رابطهی اعداد
Section titled “رابطهی اعداد”warehouse_available = max(0, warehouse_stock − reserved.digikala)reservation_shortfall = max(0, reserved.digikala − warehouse_stock)seller_available = max(0, seller_stock − reservation_shortfall − reserved.seller)sellable_calculated = warehouse_available + seller_availableیک مثال واقعی
Section titled “یک مثال واقعی”این اعداد روی یک تنوع واقعی (محافظ ولتاژ، کد 84780448) اندازهگیری شده است. خریداری در دیجیکالا کالا را به سبد برد و به درگاه بانک رفت، ولی پرداخت نکرد:
| لحظه | seller_stock |
reserved.digikala |
sellable |
|---|---|---|---|
| پیش از خرید | 39 | 0 | 39 |
| خریدار در درگاه بانک (دیدهشده در پایش ۱۵ ثانیهای) | 39 | 1 | 38 |
«موجودی در انبار فروشنده» تغییر نکرد؛ فقط رزرو بالا رفت و «قابل فروش» یکی کم شد. سایتی که sellable را نشان دهد، از همان لحظه آخرین واحد را به خریدار دیگری نمیفروشد.
🔢 نسخه و تازگی
Section titled “🔢 نسخه و تازگی”- شمارهی نسخه (
version): هر بار یکی از اعداد موجودی یک تنوع تغییر کند، نسخهی آن بزرگتر میشود. پیامهای وبهوک ممکن است تکراری، دیر یا جابهجا برسند؛ عددی را که نسخهاش از نسخهی ذخیرهشدهی شما کوچکتر یا برابر است، نادیده بگیرید. - تازگی: موجودی هر پنل حدود هر ۱۵ ثانیه از دیجیکالا خوانده میشود. زمان آخرین خواندن در
observed_atاست. stale: اگر خواندن یک پنل بیش از حد معمول عقب بماند (مثلا دیجیکالا در دسترس نباشد)، این فیلدtrueمیشود. در این حالت اعداد آخرین مقدار شناختهشدهاند.
// نسخهی کوچکتر یا برابر را نادیده بگیرید$q = $db->prepare("UPDATE products SET sellable = ?, stock_version = ? WHERE variant_id = ? AND stock_version < ?");$q->execute([$s["sellable"], $s["version"], $s["variant_id"], $s["version"]]);⚠️ هشدارها
Section titled “⚠️ هشدارها”📡 مسیرها
Section titled “📡 مسیرها”GET /v1/stock — موجودی همهی تنوعها
Section titled “GET /v1/stock — موجودی همهی تنوعها”موجودی همهی تنوعهای پنل از آخرین پایش. برای همگامسازی کامل اولیه و برای جبران پس از قطعی مناسب است.
GET /v1/stock?page=1&size=100&sellable=1 HTTP/1.1Host: api.seller360.irAuthorization: Bearer s360_live_...پارامترها
| پارامتر | نوع | اجباری | توضیح |
|---|---|---|---|
page |
عدد | خیر | شمارهی صفحه؛ پیشفرض ۱. |
size |
عدد | خیر | تعداد در هر صفحه؛ پیشفرض ۵۰، بیشینه ۵۰۰. |
changed_since_version |
عدد | خیر | فقط تنوعهایی که نسخهی آنها از این عدد بزرگتر است. |
supplier_code |
متن | خیر | فقط تنوعهای این کد فروشنده. |
sellable |
0 یا 1 |
خیر | فقط قابل فروشها (1) یا ناموجودها (0). |
پاسخ نمونه
{ "status": "ok", "data": [ { "variant_id": 84780448, "sku": "PE-5144", "supplier_code": "5144", "sellable": 38, "seller_stock": 39, "warehouse_stock": 0, "reserved": { "total": 1, "digikala": 1, "seller": 0 }, "warehouse_available": 0, "reservation_shortfall": 1, "seller_available": 38, "sellable_calculated": 38, "in_transit": { "orders": 0, "consignment": 0 }, "version": 1842, "observed_at": "2026-11-11T17:43:22+03:30", "stale": false } ], "pager": { "page": 1, "item_per_page": 100, "total_pages": 7, "total_rows": 606 }}GET /v1/stock/{variant_id} — موجودی یک تنوع
Section titled “GET /v1/stock/{variant_id} — موجودی یک تنوع”همان ساختار، برای یک تنوع. اگر تنوع در این پنل نباشد، کد 404 و خطای variant_not_found برمیگردد.
GET /v1/stock/history — تاریخچهی روزانه فعال
Section titled “GET /v1/stock/history — تاریخچهی روزانه ”یک ردیف برای هر تنوع در هر روز (به وقت تهران) که هر شب ثبت میشود. بازهی هر درخواست حداکثر ۹۰ روز است.
| پارامتر | نوع | اجباری | توضیح |
|---|---|---|---|
variant_id |
عدد | خیر | فقط این تنوع. |
from |
تاریخ | بله | اولین روز، شامل همان روز (مثلا 2026-10-13). |
to |
تاریخ | خیر | آخرین روز، شامل همان روز؛ پیشفرض دیروز. |
page، size |
عدد | خیر | صفحهبندی. |
PUT /v1/stock/{variant_id} — تعیین عدد مطلق فعال
Section titled “PUT /v1/stock/{variant_id} — تعیین عدد مطلق ”«موجودی در انبار فروشنده» را در دیجیکالا روی عدد دادهشده میگذارد و پس از ثبت قطعی در دیجیکالا پاسخ میدهد. برای انبارگردانی و اصلاح مناسب است، نه برای ثبت فروش (هشدار ۴).
PUT /v1/stock/84780448 HTTP/1.1Host: api.seller360.irAuthorization: Bearer s360_live_...Idempotency-Key: count-1405-08-21-84780448Content-Type: application/json
{ "seller_stock": 40, "expected_version": 1842 }| فیلد بدنه | نوع | اجباری | توضیح |
|---|---|---|---|
seller_stock |
عدد | بله | عدد تازه؛ ۰ تا ۱۰۰٬۰۰۰. |
expected_version |
عدد | خیر | اگر نسخهی فعلی با این عدد برابر نباشد، تغییر انجام نمیشود. |
پاسخ موفق: کد 200 با ساختار کامل موجودی و نسخهی تازه.
POST /v1/stock/{variant_id}/adjustments — ثبت فروش یا برگشت فعال
Section titled “POST /v1/stock/{variant_id}/adjustments — ثبت فروش یا برگشت ”موجودی فروشنده را به اندازهی quantity کم یا زیاد میکند. بررسی و تغییر در یک قدم و زیر قفل انجام میشود: اگر موجودی کافی نباشد، هیچ تغییری انجام نمیشود. پس اگر دو خریدار همزمان آخرین واحد را بخواهند، فقط یکی موفق میشود.
- پیش از فرستادن خریدار به درگاه یک تغییر با
quantityمنفی وreason: "sale"بفرستید. - اگر خطای
insufficient_stockبرگشت، به خریدار بگویید کالا تمام شده است. - اگر پرداخت لغو شد، همان مقدار را با
quantityمثبت وreason: "return"برگردانید. - اگر پاسخ را دریافت نکردید (قطع اتصال)، همان درخواست را با همان
Idempotency-Keyتکرار کنید؛ موجودی دو بار کم نمیشود. - اگر خطای
digikala_outcome_unknownبرگشت، تکرار با همان کلید همان خطا را برمیگرداند. موجودی را باGET /v1/stock/{variant_id}بخوانید؛ اگر فروش ثبت نشده بود، با یکIdempotency-Keyتازه دوباره بفرستید. برای این خواندن ازGET /v1/stock/{variant_id}?live=1استفاده کنید که موجودی را همان لحظه از دیجیکالا میخواند. فقط به خواندنی اعتماد کنید کهobserved_atآن پس از زمان همان خطا باشد؛ عدد قدیمیتر هنوز نتیجهی تغییر را نشان نمیدهد. اگر چنین خواندنی نرسید، سفارش را برای بررسی دستی نگه دارید و کورکورانه دوباره نفرستید.
| فیلد بدنه | نوع | اجباری | توضیح |
|---|---|---|---|
quantity |
عدد | بله | منفی برای کم کردن، مثبت برای زیاد کردن؛ صفر مجاز نیست. |
reason |
sale، return، correction |
بله | دلیل تغییر. |
reference |
متن | خیر | شناسهی سفارش در سایت شما، برای پیگیری. |
پاسخ نمونه (کد 201)
{ "status": "ok", "data": { "id": "adj_5d0e77", "quantity": -1, "reason": "sale", "reference": "ORD-58213", "created_at": "2026-11-12T10:15:03+03:30", "stock": { "variant_id": 84780448, "sellable": 37, "seller_stock": 38, "version": 1843 } }}خطاهای این مسیر
| کد HTTP | خطا | معنی | تکرار بیخطر است؟ |
|---|---|---|---|
409 |
insufficient_stock |
موجودی کافی نیست؛ هیچ تغییری انجام نشد. | خیر؛ موجودی را بخوانید. |
409 |
stock_write_locked |
تقسیم موجودی در حال اجراست. | بله، پس از Retry-After. |
409 |
idempotency_mismatch |
این کلید یکتا قبلا با بدنهی دیگری استفاده شده است. | خیر؛ کلید تازه بسازید. |
409 |
idempotency_in_progress |
درخواستی با همین کلید در حال انجام است. | بله، چند ثانیه بعد. |
502 |
digikala_refused |
دیجیکالا تغییر را نپذیرفت؛ هیچ تغییری ثبت نشد. | پس از بررسی علت. |
502 |
digikala_outcome_unknown |
پاسخ دیجیکالا نرسید و نتیجه معلوم نیست. | خیر؛ اول موجودی را بخوانید، سپس در صورت نیاز با کلید تازه. |
503 |
digikala_rate_limited |
سقف دیجیکالا برای این پنل پر است. | بله، پس از Retry-After. |
503 |
digikala_unavailable |
دیجیکالا در دسترس نیست و هیچ تغییری ثبت نشد. | بله، پس از Retry-After. |
فهرست همهی خطاها در صفحهی خطاها است.
POST /v1/stock/batch — تعیین موجودی چند تنوع در صف فعال
Section titled “POST /v1/stock/batch — تعیین موجودی چند تنوع در صف ”تا ۱۰۰ تنوع در هر درخواست. پاسخ فوری 202 فقط یعنی «در صف ثبت شد»؛ نتیجه با GET /v1/jobs/{job_id} و رویداد job.completed اعلام میشود. ثبت در دیجیکالا ممکن است چند دقیقه طول بکشد (هشدار ۶).
🔔 رویدادهای وبهوک
Section titled “🔔 رویدادهای وبهوک”| رویداد | چه وقت |
|---|---|
stock.changed |
یکی از اعداد موجودی یک تنوع تغییر کرد؛ حداکثر حدود ۱۵ ثانیه پس از تغییر در دیجیکالا. |
stock.out |
«قابل فروش» تنوع به صفر رسید. |
stock.low |
باقیمانده در انبار دیجیکالا پیش از رسیدن محمولهی تازه تمام میشود (کالا از موجودی شما فروخته میشود ولی نشان «ارسال دیجیکالا» را از دست میدهد). حداکثر یک بار در روز برای هر تنوع. |
stock.stockout_soon |
کل موجودی قابل فروش با سرعت فروش فعلی کمتر از آستانهی روزهای هشدار شما دوام میآورد؛ خرید لازم است. |
نمونهی پیام stock.changed
{ "id": "evt_01HZY3K8", "type": "stock.changed", "created_at": "2026-11-11T17:43:22+03:30", "panel_id": 1, "livemode": true, "data": { "stock": { "variant_id": 84780448, "sku": "PE-5144", "sellable": 38, "seller_stock": 39, "version": 1842 }, "previous": { "sellable": 39, "seller_stock": 39, "version": 1841 } }}پیش از استفاده از هر پیام، امضای آن را بررسی کنید.
💻 نمونهکد: ثبت فروش
Section titled “💻 نمونهکد: ثبت فروش”curl -X POST https://api.seller360.ir/v1/stock/84780448/adjustments \ -H "Authorization: Bearer $S360_API_KEY" \ -H "Idempotency-Key: sale-ORD-58213-84780448" \ -H "Content-Type: application/json" \ -d '{"quantity": -1, "reason": "sale", "reference": "ORD-58213"}'$ch = curl_init("https://api.seller360.ir/v1/stock/84780448/adjustments");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer " . getenv("S360_API_KEY"), "Idempotency-Key: sale-ORD-58213-84780448", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode(["quantity" => -1, "reason" => "sale", "reference" => "ORD-58213"]), CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 20,]);$res = json_decode(curl_exec($ch), true);if (($res["error"]["code"] ?? null) === "insufficient_stock") { // به خریدار بگویید کالا تمام شده است}import os, requests
res = requests.post( "https://api.seller360.ir/v1/stock/84780448/adjustments", headers={ "Authorization": f"Bearer {os.environ['S360_API_KEY']}", "Idempotency-Key": "sale-ORD-58213-84780448", }, json={"quantity": -1, "reason": "sale", "reference": "ORD-58213"}, timeout=20,).json()if res["status"] == "error" and res["error"]["code"] == "insufficient_stock": ... # به خریدار بگویید کالا تمام شده استconst res = await fetch('https://api.seller360.ir/v1/stock/84780448/adjustments', { method: 'POST', headers: { Authorization: `Bearer ${process.env.S360_API_KEY}`, 'Idempotency-Key': 'sale-ORD-58213-84780448', 'Content-Type': 'application/json', }, body: JSON.stringify({ quantity: -1, reason: 'sale', reference: 'ORD-58213' }),})const body = await res.json()if (body.error?.code === 'insufficient_stock') { // به خریدار بگویید کالا تمام شده است}using var http = new HttpClient();var req = new HttpRequestMessage(HttpMethod.Post, "https://api.seller360.ir/v1/stock/84780448/adjustments");req.Headers.Add("Authorization", $"Bearer {Environment.GetEnvironmentVariable("S360_API_KEY")}");req.Headers.Add("Idempotency-Key", "sale-ORD-58213-84780448");req.Content = JsonContent.Create(new { quantity = -1, reason = "sale", reference = "ORD-58213" });var res = await http.SendAsync(req);var body = await res.Content.ReadFromJsonAsync<JsonElement>();