ให้ ERP / โปรแกรมบัญชี / POS ของคุณดึงข้อมูล ออเดอร์รวมทุกช่องทาง · สินค้า · สต๊อก · ลูกค้า จาก istatus ได้โดยตรงด้วย API key เดียว — ไม่ต้องไปสมัครขอ API กับ Shopee / Lazada / TikTok เอง
Shopee ─┐ ┌─ ERP / โปรแกรมบัญชี / POS ของคุณ Lazada ─┼─ istatus (เชื่อมร้านของคุณอยู่แล้ว) ──┼─ GET /api/v1/... TikTok ─┤ รวมออเดอร์ / สินค้า / สต๊อก └─ Authorization: Bearer <API key> POS ────┘
GET /api/v1/orders — ออเดอร์รวมทุกช่อง พร้อมรายการสินค้าในบิลGET /api/v1/products — สินค้า + ราคาขาย (ปลีก/ส่ง/สมาชิก)GET /api/v1/stock — ยอดสต๊อกคงเหลือ (ค่าเดียวกับหน้าเว็บ istatus)GET /api/v1/customers — ลูกค้าที่ร้านบันทึกไว้GET /api/v1/ping — เช็คว่า key ใช้ได้ + แพ็ก + วันทดลองคงเหลือPOST /api/v1/orders — ส่งออเดอร์จากระบบของคุณเข้า istatus (ตัดสต๊อก + ลงบัญชีให้เหมือนขายหน้าร้าน)PUT /api/v1/stock/{sku} — ตั้งยอดสต๊อกคงเหลือ (ระบบออกใบปรับสต๊อกให้อัตโนมัติ)GET /api/v1/orders/held — ดูออเดอร์ที่ค้างห้องรอ เพราะโควตาออเดอร์ของแพ็กเต็ม
ฝั่งเขียนเปิดให้เป็นรายร้าน — ถ้าร้านคุณยังไม่ได้เปิด เส้นเขียนจะตอบ 404 (ทักเราเพื่อขอเปิด)
ist_ และแสดงเต็มครั้งเดียว — คัดลอกเก็บทันที (ระบบเก็บเป็นรหัสย่อยทางเดียว ดูซ้ำไม่ได้ · ออกได้สูงสุด 3 ใบต่อร้าน)export ISTATUS_API_KEY="ist_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"curl -s https://istatus.net/api/v1/ping \
-H "Authorization: Bearer $ISTATUS_API_KEY"
ถ้า key ใช้ได้จะตอบ:
{ "ok": true, "plan": "pro", "trialDaysLeft": null, "shopName": "ร้านของคุณ" }curl -s "https://istatus.net/api/v1/orders?from=2026-08-01&limit=20" \
-H "Authorization: Bearer $ISTATUS_API_KEY"ทุกคำขอต้องแนบ header:
Authorization: Bearer <API key ของร้าน>
401 { "code": "INVALID_KEY" }ISTATUS_API_KEY ให้เป็นแบบอย่างที่ถูกX-Trial-Days-Left บอกจำนวนวันที่เหลือ403 พร้อม code = PLAN_REQUIRED (แพ็กไม่ถึงเกณฑ์)
หรือ TRIAL_EXPIRED (ช่วงทดลองหมด) — key ยังอยู่ อัปเกรดแพ็กแล้วใช้ต่อได้ทันทีไม่ต้องออกใหม่429 { "code": "RATE_LIMITED" }Retry-After: <วินาที> —
โค้ดของคุณต้องรอตามค่านี้แล้วยิงใหม่ (ตัวอย่างโค้ดทุกภาษาด้านล่างทำให้แล้ว)from เป็นเวลาที่ดึงรอบก่อนพอlimit ได้สูงสุด 100 ต่อหน้า (ไม่ส่ง = 50)nextCursor — ถ้าไม่ใช่ null ให้ส่งกลับเป็นพารามิเตอร์ ?cursor= เพื่อดึงหน้าถัดไป วนจนได้ null = หมดแล้วcursor เป็น token ทึบ (มีลายเซ็นกันแก้) — ใช้ค่าที่ระบบให้เท่านั้น ประกอบเอง/แก้เองจะได้ 422 INVALID_CURSOR25000 = 250.00 บาท · หาร 100 ก่อนแสดงผลเสมอvatRate เป็นหน่วยต่อ 10000 — 700 = 7.00%Z) — เวลาไทย = +7 ชั่วโมง · พารามิเตอร์ from/to เป็นวันที่ YYYY-MM-DD ตีความตามเขตเวลาระบบPOST/PUT) และ
GET /api/v1/orders/held จะตอบ 404 — ทักทีมงานเพื่อขอเปิดexternalOrderNo: ส่งเลขออเดอร์ในระบบคุณมาเสมอ —
ยิงซ้ำเลขเดิมจะได้ 200 พร้อมผลเดิม ไม่เกิดบิลซ้ำ (เน็ตหลุดกลางทางแล้วยิงใหม่ได้อย่างปลอดภัย) ·
เลขบิลใน istatus = API-<externalOrderNo> · เลขนี้ผูกตลอดชีพ: ถ้าเจ้าของร้านยกเลิก (void) บิลไปแล้ว ยิงเลขเดิมซ้ำจะได้ { "status": "voided" } ไม่สร้างใหม่ — อยากบันทึกใหม่จริงต้องใช้ externalOrderNo ใหม่202 ไม่ใช่ error: ถ้าโควตาออเดอร์ของแพ็กเดือนนี้เต็ม ระบบรับออเดอร์ไว้ในห้องรอ
({ "status": "held" }) แล้วบันทึกเข้าให้เองเมื่ออัปเกรดแพ็กหรือขึ้นเดือนใหม่ —
ห้ามยิงซ้ำ ห้ามถือว่าล้มเหลว · ดูคิวได้ที่ GET /api/v1/orders/held และเจ้าของร้านเห็นป้ายเตือนในแอป ·
ห้องรอรับได้ 5,000 ใบ/ร้าน — เกินแล้วใบใหม่ได้ 409 HELD_FULL (ของที่รับไว้แล้วไม่หาย)rejected = ยังไม่เป็นยอดขาย —
โค้ดของคุณควรเช็ค rejectedCount จาก GET /api/v1/ping หรือ rejectedRows จาก
GET /api/v1/orders/held เป็นประจำ แก้ตาม lastError แล้วส่ง externalOrderNo เดิมมาใหม่quarantined — ไม่บันทึก ไม่ลบ)
เห็นได้ใน quarantinedRows ของ GET /api/v1/orders/held · ส่ง externalOrderNo เดิมด้วย key ที่ใช้งานได้ ระบบจะโอนใบกลับเข้าคิวให้อัตโนมัติsku + qty — ระบบคิดราคาจากราคาขายในแคตตาล็อกของร้าน
(ตามระดับราคาของลูกค้า ถ้าส่ง customerId) · ไม่รับราคา/ส่วนลดจากภายนอก และส่ง field ที่ไม่รู้จักมาจะได้
422 VALIDATION ทันที (ไม่ตัดทิ้งเงียบ)409 INSUFFICIENT_STOCK พร้อมบอก SKU ที่ขาด (กติกาเดียวกับการขายหน้าร้าน) —
ทั้งบิลไม่ถูกบันทึกแม้แต่รายการเดียวPUT /api/v1/stock/{sku} เป็นการตั้งยอดคงเหลือ (absolute) ไม่ใช่บวก/ลบ — ระบบออกใบปรับสต๊อกและลงบัญชีให้
· ยอดตรงอยู่แล้วจะไม่สร้างใบเปล่า (delta: 0, adjusted: false)
· แนะนำส่ง expectedQty = ยอดที่คุณอ่านไปล่าสุด — ถ้าหน้าร้านขายไประหว่างนั้นจนยอดจริงไม่ตรง จะได้ 409 STALE_STOCK แทนการทับยอดด้วยข้อมูลเก่า (ไม่งั้นของที่ขายไปแล้วจะถูกคืนเข้าสต๊อกเงียบ ๆ)channel เป็น shopee / lazada / tiktok / woocommerce
ระบบจำกัดข้อมูลย้อนหลัง 90 วัน ตามนโยบายคุ้มครองข้อมูลของแพลตฟอร์ม —
response จะบอก "mpLimited": true, "retentionDays": 90{ "code": "RATE_LIMITED", "message": "ยิงถี่เกินกำหนด — ดู header Retry-After แล้วลองใหม่" }
ให้โค้ดของคุณตัดสินใจจาก code (คงที่ เครื่องอ่านได้) — message เป็นภาษาไทยสำหรับคน อาจเปลี่ยนถ้อยคำได้
| HTTP | code | ความหมาย | โค้ดของคุณควรทำ |
|---|---|---|---|
| 401 | INVALID_KEY | key ผิด / ถูกเพิกถอน / ไม่ได้ส่ง | หยุด — ตรวจ key ใน env / ออกใบใหม่ |
| 403 | PLAN_REQUIRED | แพ็กยังไม่ถึง Pro และไม่อยู่ในช่วงทดลอง | หยุด — อัปเกรดแพ็กในแอป istatus |
| 403 | TRIAL_EXPIRED | ช่วงทดลอง 30 วันหมดแล้ว | หยุด — อัปเกรดแพ็ก (key เดิมใช้ต่อได้) |
| 422 | INVALID_PARAM | พารามิเตอร์ผิดรูปแบบ (เช่น from ไม่ใช่ YYYY-MM-DD) | แก้พารามิเตอร์ — อย่ายิงซ้ำแบบเดิม |
| 422 | INVALID_CURSOR | cursor ไม่ใช่ค่าที่ระบบให้ | เริ่มดึงใหม่จากหน้าแรก (ไม่ส่ง cursor) |
| 429 | RATE_LIMITED | ยิงถี่เกินฟิวส์ต่อร้าน | รอตาม header Retry-After แล้วยิงใหม่ |
| 404 | — | ร้านนี้ยังไม่ได้เปิดฝั่งเขียน (เส้น POST/PUT/orders/held) | หยุด — ทักทีมงานเพื่อขอเปิด |
| 404 | NOT_FOUND | ไม่พบ SKU ที่ระบุใน PUT /stock/{sku} | ตรวจ SKU กับ GET /api/v1/products |
| 409 | INSUFFICIENT_STOCK | สต๊อกไม่พอ — ทั้งบิลไม่ถูกบันทึก | เติมสต๊อกแล้วยิงใหม่ (เลขเดิมได้ ไม่ซ้ำ) |
| 409 | HELD_FULL | ห้องรอของร้านเต็ม (5,000 ใบ) — ใบนี้ยังไม่ถูกรับ | หยุด — แจ้งเจ้าของร้านอัปเกรดแพ็กเพื่อปล่อยคิว แล้วส่งเลขเดิมใหม่ |
| 409 | STALE_STOCK | PUT /stock: ยอดจริงไม่ตรง expectedQty (มีการขาย/ปรับระหว่างนั้น) | อ่านยอดใหม่จาก GET /stock แล้วส่งอีกครั้ง — ไม่มีอะไรถูกแตะ |
| 409 | IN_PROGRESS | POST /orders: เลขนี้กำลังถูกบันทึกโดยคำขออื่นพร้อมกัน (เช่น ยิงซ้ำเร็วเกินไป) | รอ 2 วินาทีแล้วส่งเลขเดิมซ้ำ — จะได้ผลจริง (created / held / voided) |
| 503 | RETRY | เลขใบปรับสต๊อกชนกัน (พบยากมาก) — ยอดยังไม่ถูกแตะ | ยิง PUT /stock ใบเดิมซ้ำได้ทันที |
| 422 | VALIDATION | body ผิดรูปแบบ / มี field ที่ไม่รู้จัก | แก้ body ตามข้อความ — อย่ายิงซ้ำแบบเดิม |
| 422 | UNKNOWN_SKU | ไม่มี SKU นี้ในร้าน (หรือถูกปิดใช้งาน) | สร้าง/เปิดสินค้าในแอปก่อน แล้วยิงใหม่ |
| 422 | BUNDLE_SKU | ตั้งสต๊อกให้สินค้าจัดชุดโดยตรงไม่ได้ (ยอดคำนวณจากสินค้าฐาน) | ตั้งยอดที่สินค้าฐานแทน |
| 500 | INTERNAL | ข้อผิดพลาดฝั่งระบบ | รอครู่แล้วลองใหม่ — ถ้าค้างนานติดต่อเรา |
201 = บันทึกแล้ว · 202 = เข้าห้องรอ (สำเร็จ ไม่ต้องยิงซ้ำ) ·
200 = เลขนี้เคยส่งแล้ว ได้ผลเดิม — ทั้งสามอย่างนี้ถือว่า "ส่งสำเร็จ" ในโค้ดของคุณ
ทุกภาษาเป็นโครงเดียวกัน: อ่าน key จาก env → ยิง GET → โดน 429 = รอตาม Retry-After แล้วยิงใหม่อัตโนมัติ →
error อื่นโยนพร้อม code → มีฟังก์ชัน ไล่ cursor จนหมดทุกหน้า ให้พร้อม
· ตัวอย่างรายเมนูใน อ้างอิง API เรียกใช้ฟังก์ชันจากโครงนี้
# istatus-pull.sh — ต้องมี curl + jq
# เก็บ key ใน env เท่านั้น: export ISTATUS_API_KEY="ist_..." (ห้ามพิมพ์ key ลงไฟล์สคริปต์/git)
BASE="https://istatus.net"
# ดึงรายการแบบไล่ cursor จนหมด + รอเองเมื่อโดนฟิวส์ 429 (อ่าน Retry-After)
pull_all() { # ใช้: pull_all /api/v1/orders "from=2026-08-01"
local path="$1" query="$2" cursor="" res http wait
while :; do
res=$(curl -s -w '\n%{http_code}' -D /tmp/istatus-h -G "$BASE$path" \
-H "Authorization: Bearer $ISTATUS_API_KEY" \
--data-urlencode "limit=100" \
${query:+--data-urlencode "$query"} \
${cursor:+--data-urlencode "cursor=$cursor"})
http=${res##*$'\n'}; res=${res%$'\n'*}
if [ "$http" = "429" ]; then # ฟิวส์ความถี่ — รอตาม Retry-After แล้วยิงใหม่
wait=$(grep -i '^retry-after' /tmp/istatus-h | tr -dc '0-9'); sleep "${wait:-1}"; continue
fi
if [ "$http" != "200" ]; then echo "$res" | jq -r '"ERROR " + .code + ": " + .message' >&2; return 1; fi
echo "$res" | jq -c '.rows[]' # หนึ่งบรรทัดต่อหนึ่งรายการ — เอาไป pipe ต่อได้
cursor=$(echo "$res" | jq -r '.nextCursor // empty')
[ -z "$cursor" ] && break # nextCursor = null → หมดแล้ว
done
}
<?php
// istatus-client.php — PHP 8.0+ (ext-curl)
// เก็บ key ใน env เท่านั้น (.env / ตั้งใน server config) — ห้าม hardcode ในโค้ด
const ISTATUS_BASE = 'https://istatus.net';
function istatus_get(string $path, array $params = []): array {
$key = getenv('ISTATUS_API_KEY') ?: throw new RuntimeException('ตั้ง env ISTATUS_API_KEY ก่อน');
$params = array_filter($params, fn($v) => $v !== null && $v !== '');
$url = ISTATUS_BASE . $path . ($params ? '?' . http_build_query($params) : '');
for ($attempt = 0; $attempt < 5; $attempt++) {
$retryAfter = 1;
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key],
// อ่านค่า Retry-After จาก header ไว้ใช้ตอนโดนฟิวส์ 429
CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$retryAfter) {
if (stripos($line, 'Retry-After:') === 0) $retryAfter = max(1, (int) trim(substr($line, 12)));
return strlen($line);
},
]);
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status === 429) { sleep($retryAfter); continue; } // ฟิวส์ความถี่ — รอแล้วยิงใหม่
$body = json_decode((string) $raw, true) ?? [];
if ($status >= 400) { // ตัดสินใจจาก code เครื่องอ่านได้
throw new RuntimeException(($body['code'] ?? "HTTP $status") . ': ' . ($body['message'] ?? 'error'));
}
return $body;
}
throw new RuntimeException('RATE_LIMITED: ยิงซ้ำ 5 ครั้งแล้วยังติดฟิวส์ — ลดความถี่การเรียก');
}
// ไล่ทุกหน้าด้วย cursor จนหมด (ใช้กับทุก endpoint ที่เป็นรายการ)
function istatus_get_all(string $path, array $params = []): array {
$rows = []; $cursor = null;
do {
$page = istatus_get($path, array_merge($params, ['limit' => 100, 'cursor' => $cursor]));
$rows = array_merge($rows, $page['rows']);
$cursor = $page['nextCursor'] ?? null;
} while ($cursor);
return $rows;
}
// istatus-client.mjs — Node.js 18+ (ใช้ fetch ในตัว ไม่ต้องลงแพ็กเกจ)
// เก็บ key ใน env เท่านั้น: export ISTATUS_API_KEY=ist_... (ห้าม hardcode ในโค้ด/คอมมิตลง git)
const BASE = 'https://istatus.net';
const KEY = process.env.ISTATUS_API_KEY;
if (!KEY) throw new Error('ตั้ง env ISTATUS_API_KEY ก่อน');
export async function istatusGet(path, params = {}) {
const url = new URL(path, BASE);
for (const [k, v] of Object.entries(params)) if (v != null && v !== '') url.searchParams.set(k, v);
for (let attempt = 0; attempt < 5; attempt++) {
const res = await fetch(url, { headers: { Authorization: `Bearer ${KEY}` } });
if (res.status === 429) { // ฟิวส์ความถี่ — รอตาม Retry-After แล้วยิงใหม่
const wait = Number(res.headers.get('retry-after')) || 1;
await new Promise(r => setTimeout(r, wait * 1000));
continue;
}
const body = await res.json();
if (!res.ok) throw new Error(`${body.code}: ${body.message}`); // ตัดสินใจจาก code เครื่องอ่านได้
return body;
}
throw new Error('RATE_LIMITED: ยิงซ้ำ 5 ครั้งแล้วยังติดฟิวส์ — ลดความถี่การเรียก');
}
// ไล่ทุกหน้าด้วย cursor จนหมด (ใช้กับทุก endpoint ที่เป็นรายการ)
export async function istatusGetAll(path, params = {}) {
const rows = [];
let cursor = null;
do {
const page = await istatusGet(path, { ...params, limit: 100, cursor });
rows.push(...page.rows);
cursor = page.nextCursor;
} while (cursor);
return rows;
}
# istatus_client.py — Python 3.9+ (pip install requests)
# เก็บ key ใน env เท่านั้น: export ISTATUS_API_KEY=ist_... (ห้าม hardcode ในโค้ด/คอมมิตลง git)
import os, time, requests
BASE = "https://istatus.net"
KEY = os.environ["ISTATUS_API_KEY"] # ไม่มี env = ให้พังตั้งแต่เริ่ม ดีกว่ายิงด้วย key ว่าง
def istatus_get(path: str, params: dict | None = None) -> dict:
for _attempt in range(5):
res = requests.get(
BASE + path,
params={k: v for k, v in (params or {}).items() if v is not None},
headers={"Authorization": f"Bearer {KEY}"},
timeout=30,
)
if res.status_code == 429: # ฟิวส์ความถี่ — รอตาม Retry-After แล้วยิงใหม่
time.sleep(int(res.headers.get("Retry-After", "1")))
continue
body = res.json()
if not res.ok: # ตัดสินใจจาก code เครื่องอ่านได้
raise RuntimeError(f"{body.get('code')}: {body.get('message')}")
return body
raise RuntimeError("RATE_LIMITED: ยิงซ้ำ 5 ครั้งแล้วยังติดฟิวส์ — ลดความถี่การเรียก")
def istatus_get_all(path: str, params: dict | None = None) -> list:
"""ไล่ทุกหน้าด้วย cursor จนหมด (ใช้กับทุก endpoint ที่เป็นรายการ)"""
rows, cursor = [], None
while True:
page = istatus_get(path, {**(params or {}), "limit": 100, "cursor": cursor})
rows += page["rows"]
cursor = page.get("nextCursor")
if not cursor:
return rows
// IstatusClient.cs — .NET 6+
// เก็บ key ใน env เท่านั้น (ห้าม hardcode ในโค้ด/คอมมิตลง git)
using System.Net;
using System.Net.Http.Headers;
using System.Text.Json;
public static class IstatusClient
{
static readonly HttpClient Http = new() { BaseAddress = new Uri("https://istatus.net") };
static readonly string Key = Environment.GetEnvironmentVariable("ISTATUS_API_KEY")
?? throw new InvalidOperationException("ตั้ง env ISTATUS_API_KEY ก่อน");
public static async Task<JsonElement> GetAsync(string path, Dictionary<string, string?>? query = null)
{
var qs = query == null ? "" : string.Join("&", query
.Where(kv => !string.IsNullOrEmpty(kv.Value))
.Select(kv => $"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value!)}"));
var url = path + (qs.Length > 0 ? "?" + qs : "");
for (var attempt = 0; attempt < 5; attempt++)
{
using var req = new HttpRequestMessage(HttpMethod.Get, url);
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Key);
using var res = await Http.SendAsync(req);
if (res.StatusCode == HttpStatusCode.TooManyRequests)
{ // ฟิวส์ความถี่ — รอตาม Retry-After แล้วยิงใหม่
await Task.Delay(res.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(1));
continue;
}
var body = JsonSerializer.Deserialize<JsonElement>(await res.Content.ReadAsStringAsync());
if (!res.IsSuccessStatusCode) // ตัดสินใจจาก code เครื่องอ่านได้
throw new Exception($"{body.GetProperty("code").GetString()}: {body.GetProperty("message").GetString()}");
return body;
}
throw new Exception("RATE_LIMITED: ยิงซ้ำ 5 ครั้งแล้วยังติดฟิวส์ — ลดความถี่การเรียก");
}
// ไล่ทุกหน้าด้วย cursor จนหมด (ใช้กับทุก endpoint ที่เป็นรายการ)
public static async Task<List<JsonElement>> GetAllAsync(string path, Dictionary<string, string?>? query = null)
{
var rows = new List<JsonElement>();
string? cursor = null;
do
{
var q = new Dictionary<string, string?>(query ?? new()) { ["limit"] = "100", ["cursor"] = cursor };
var page = await GetAsync(path, q);
rows.AddRange(page.GetProperty("rows").EnumerateArray().Select(e => e.Clone()));
var next = page.GetProperty("nextCursor");
cursor = next.ValueKind == JsonValueKind.String ? next.GetString() : null;
} while (cursor != null);
return rows;
}
}
POST / PUT
ต่อจากโครงหลักด้านบน — ต่างกันตรงที่ฝั่งเขียนต้องอ่านรหัสสถานะด้วย:
201 บันทึกแล้ว · 202 เข้าห้องรอ (โควตาเต็ม — สำเร็จ ห้ามยิงซ้ำ) ·
200 เลขนี้เคยส่งแล้ว (กันบิลซ้ำให้อัตโนมัติ)
# ── ส่งข้อมูลเข้า (POST/PUT) — ต่อจากโครงหลักด้านบน ──
# สำคัญ: ต้องดู "รหัสสถานะ" ด้วย ไม่ใช่ดูแต่ body — 201 บันทึกแล้ว · 202 เข้าห้องรอ (สำเร็จ!) · 200 เลขนี้เคยส่งแล้ว
istatus_send() { # ใช้: istatus_send POST /api/v1/orders '{"externalOrderNo":"PO-1","items":[{"sku":"A-001","qty":2}]}'
local method="$1" path="$2" data="$3" res http wait
while :; do
res=$(curl -s -w '\n%{http_code}' -D /tmp/istatus-h -X "$method" "$BASE$path" \
-H "Authorization: Bearer $ISTATUS_API_KEY" -H 'Content-Type: application/json' -d "$data")
http=${res##*$'\n'}; res=${res%$'\n'*}
if [ "$http" = "429" ]; then # ฟิวส์ความถี่ (เขียน ~2/วิ) — รอตาม Retry-After แล้วยิงใหม่
wait=$(grep -i '^retry-after' /tmp/istatus-h | tr -dc '0-9'); sleep "${wait:-1}"; continue
fi
break
done
case "$http" in
201) echo "created $res";; # บันทึกเป็นบิลจริงแล้ว
202) echo "held $res";; # โควตาแพ็กเต็ม → เข้าห้องรอ ระบบจะบันทึกให้เอง — ห้ามยิงซ้ำ
200) echo "already $res";; # เลขนี้เคยส่งแล้ว (idempotent) = ไม่มีบิลซ้ำ
*) echo "ERROR $http $(echo "$res" | jq -r '.code + ": " + .message')" >&2; return 1;;
esac
}
// ── ส่งข้อมูลเข้า (POST/PUT) — ต่อจากโครงหลักด้านบน ──
// คืน status มาด้วยเสมอ เพราะ 201 (บันทึกแล้ว) / 202 (เข้าห้องรอ) / 200 (เคยส่งแล้ว) คนละความหมาย แต่ "สำเร็จ" ทั้งหมด
function istatus_send(string $method, string $path, array $payload): array {
$key = getenv('ISTATUS_API_KEY') ?: throw new RuntimeException('ตั้ง env ISTATUS_API_KEY ก่อน');
for ($attempt = 0; $attempt < 5; $attempt++) {
$retryAfter = 1;
$ch = curl_init(ISTATUS_BASE . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$retryAfter) {
if (stripos($line, 'Retry-After:') === 0) $retryAfter = max(1, (int) trim(substr($line, 12)));
return strlen($line);
},
]);
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status === 429) { sleep($retryAfter); continue; }
$body = json_decode((string) $raw, true) ?? [];
if ($status >= 400) {
throw new RuntimeException(($body['code'] ?? "HTTP $status") . ': ' . ($body['message'] ?? 'error'));
}
return ['status' => $status, 'body' => $body];
}
throw new RuntimeException('RATE_LIMITED: ยิงซ้ำ 5 ครั้งแล้วยังติดฟิวส์ — ลดความถี่การเรียก');
}
// ── ส่งข้อมูลเข้า (POST/PUT) — ต่อจากโครงหลักด้านบน ──
// คืน { status, body } เสมอ: 201 บันทึกแล้ว · 202 เข้าห้องรอ (สำเร็จ ห้ามยิงซ้ำ) · 200 เลขนี้เคยส่งแล้ว
export async function istatusSend(method, path, payload) {
for (let attempt = 0; attempt < 5; attempt++) {
const res = await fetch(new URL(path, BASE), {
method,
headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (res.status === 429) { // ฟิวส์ความถี่ (เขียน ~2/วิ)
const wait = Number(res.headers.get('retry-after')) || 1;
await new Promise(r => setTimeout(r, wait * 1000));
continue;
}
const body = await res.json();
if (!res.ok) throw new Error(`${body.code}: ${body.message}`);
return { status: res.status, body };
}
throw new Error('RATE_LIMITED: ยิงซ้ำ 5 ครั้งแล้วยังติดฟิวส์ — ลดความถี่การเรียก');
}
# ── ส่งข้อมูลเข้า (POST/PUT) — ต่อจากโครงหลักด้านบน ──
# คืน (status, body): 201 บันทึกแล้ว · 202 เข้าห้องรอ (สำเร็จ ห้ามยิงซ้ำ) · 200 เลขนี้เคยส่งแล้ว
def istatus_send(method: str, path: str, payload: dict) -> tuple:
for _attempt in range(5):
res = requests.request(
method, BASE + path, json=payload,
headers={"Authorization": f"Bearer {KEY}"}, timeout=30,
)
if res.status_code == 429: # ฟิวส์ความถี่ (เขียน ~2/วิ)
time.sleep(int(res.headers.get("Retry-After", "1")))
continue
body = res.json()
if not res.ok:
raise RuntimeError(f"{body.get('code')}: {body.get('message')}")
return res.status_code, body
raise RuntimeError("RATE_LIMITED: ยิงซ้ำ 5 ครั้งแล้วยังติดฟิวส์ — ลดความถี่การเรียก")
// ── ส่งข้อมูลเข้า (POST/PUT) — เพิ่มเมธอดนี้ในคลาส IstatusClient เดิม ──
// คืน (Status, Body): 201 บันทึกแล้ว · 202 เข้าห้องรอ (สำเร็จ ห้ามยิงซ้ำ) · 200 เลขนี้เคยส่งแล้ว
public static async Task<(int Status, JsonElement Body)> SendAsync(HttpMethod method, string path, object payload)
{
for (var attempt = 0; attempt < 5; attempt++)
{
using var req = new HttpRequestMessage(method, path)
{
Content = new StringContent(JsonSerializer.Serialize(payload),
System.Text.Encoding.UTF8, "application/json"),
};
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Key);
using var res = await Http.SendAsync(req);
if (res.StatusCode == HttpStatusCode.TooManyRequests)
{ // ฟิวส์ความถี่ (เขียน ~2/วิ) — รอตาม Retry-After แล้วยิงใหม่
await Task.Delay(res.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(1));
continue;
}
var body = JsonSerializer.Deserialize<JsonElement>(await res.Content.ReadAsStringAsync());
if (!res.IsSuccessStatusCode)
throw new Exception($"{body.GetProperty("code").GetString()}: {body.GetProperty("message").GetString()}");
return ((int)res.StatusCode, body);
}
throw new Exception("RATE_LIMITED: ยิงซ้ำ 5 ครั้งแล้วยังติดฟิวส์ — ลดความถี่การเรียก");
}
Base URL: https://istatus.net · ทุกเส้นต้องแนบ Authorization: Bearer ·
เส้นอ่านเป็น GET · เส้นเขียน (POST /orders, PUT /stock/{sku}) เปิดให้เป็นรายร้าน — ปิดอยู่ = 404 ·
ทุก response เป็น JSON (UTF-8) · error ที่เป็นไปได้ทุกเส้น: 401 / 403 / 422 / 429 (ดูตาราง error)
เช็คว่า key ใช้ได้ + แพ็กปัจจุบัน + วันทดลองคงเหลือ — ใช้เป็นคำขอแรกตอนตั้งค่าระบบ และใช้เช็คสุขภาพการเชื่อมต่อ
พารามิเตอร์: ไม่มี
{
"ok": true,
"plan": "starter",
"trialDaysLeft": 22,
"shopName": "ร้านตัวอย่าง"
}
| field | ชนิด | ความหมาย |
|---|---|---|
ok | boolean | true เสมอเมื่อมาถึงขั้นนี้ (key + สิทธิ์ผ่านแล้ว) |
plan | string | แพ็กปัจจุบัน: free | starter | pro | business | scale |
trialDaysLeft | integer | null | วันทดลองที่เหลือ — null = แพ็ก Pro ขึ้นไป (ไม่ใช่โหมดทดลอง) |
shopName | string | null | ชื่อร้าน (ไว้ยืนยันว่า key ตรงร้าน) |
curl -s https://istatus.net/api/v1/ping \
-H "Authorization: Bearer $ISTATUS_API_KEY"
// ใช้ istatus_get() จากโครงหลักด้านบน
print_r(istatus_get('/api/v1/ping'));
// ใช้ istatusGet() จากโครงหลักด้านบน
console.log(await istatusGet('/api/v1/ping'));
# ใช้ istatus_get() จากโครงหลักด้านบน
print(istatus_get("/api/v1/ping"))
// ใช้ IstatusClient จากโครงหลักด้านบน
var ping = await IstatusClient.GetAsync("/api/v1/ping");
Console.WriteLine(ping.GetProperty("shopName").GetString());
รายการออเดอร์รวมทุกช่องทาง (POS / Shopee / Lazada / TikTok / WooCommerce ฯลฯ) เรียงจากใหม่ไปเก่า —
แต่ละออเดอร์แนบ items[] (รายการสินค้าในบิล) มาให้ครบในคำขอเดียว
| พารามิเตอร์ | ชนิด | ความหมาย |
|---|---|---|
from | date | วันที่เริ่ม YYYY-MM-DD (นับตั้งแต่ต้นวัน) |
to | date | วันที่สิ้นสุด YYYY-MM-DD (รวมทั้งวัน) |
channel | string | กรองช่องทาง: pos, shopee, lazada, tiktok, woocommerce, web |
channel=api | string | เฉพาะออเดอร์ที่ระบบของคุณส่งเข้ามาเองผ่าน POST /orders — ทุกแถวมี field origin (api / pos / shopee …) ให้แยก "ของที่ ERP ส่ง" ออกจากขายหน้าร้านจริง กัน sync กลับซ้ำ |
status | string | กรองสถานะ เช่น paid (ขายแล้ว), voided (ยกเลิกบิล) |
cursor | string | ค่า nextCursor จากหน้าก่อน |
limit | integer | จำนวนต่อหน้า 1-100 (ไม่ส่ง = 50) |
channel เป็นช่อง marketplace = ข้อมูลย้อนหลังไม่เกิน 90 วัน (response มี mpLimited: true) — ไม่กรอง channel หรือกรอง pos ไม่ติดข้อจำกัดนี้{
"rows": [
{
"id": "0d3f2a10-1111-4222-8333-944455556666",
"orderNo": "POS-1756600000000-123",
"channel": "pos",
"origin": "pos",
"status": "paid",
"priceType": "retail",
"total": 25000, // สตางค์ = 250.00 บาท (ยอดที่ลูกค้าจ่ายจริง รวม VAT)
"vat": 1636, // VAT ในบิล (สตางค์)
"vatRate": 700, // 7.00% (หน่วยต่อ 10000)
"vatMode": "inclusive",
"discount": 0,
"shippingFee": 0,
"shippingMethod": null,
"currency": "THB",
"customerId": null,
"trackingNo": null,
"carrier": null,
"note": null,
"pointsEarned": 10,
"pointsUsed": 0,
"fulfillmentStage": null,
"issuedAt": null, // วันที่เอกสาร (ถ้าลงย้อนหลัง)
"shippedAt": null,
"deliveredAt": null,
"createdAt": "2026-08-31T04:10:22.000Z",
"items": [
{ "productId": "7b1c0000-2222-4333-8444-955566667777",
"sku": "A-001", "name": "สินค้าตัวอย่าง", "qty": 2, "price": 12500 }
]
}
],
"nextCursor": null, // ไม่ null = ส่งกลับเป็น ?cursor= เพื่อดึงหน้าถัดไป
"mpLimited": false,
"retentionDays": null
}
# หน้าเดียว
curl -s -G https://istatus.net/api/v1/orders \
-H "Authorization: Bearer $ISTATUS_API_KEY" \
--data-urlencode "from=2026-08-01" --data-urlencode "to=2026-08-31" \
--data-urlencode "status=paid" --data-urlencode "limit=100"
# ทุกหน้า (ไล่ cursor + รอเมื่อโดน 429) — ใช้ pull_all จากโครงหลักด้านบน
pull_all /api/v1/orders "from=2026-08-01" | jq -r '[.orderNo, .channel, (.total/100)] | @tsv'
// ออเดอร์ที่ขายแล้วของเดือนสิงหา — ทุกหน้า (istatus_get_all ไล่ cursor + รอ 429 ให้เอง)
$orders = istatus_get_all('/api/v1/orders', [
'from' => '2026-08-01', 'to' => '2026-08-31', 'status' => 'paid',
]);
foreach ($orders as $o) {
printf("%s %s %.2f บาท\n", $o['orderNo'], $o['channel'], $o['total'] / 100);
foreach ($o['items'] as $it) {
printf(" - %s x%d @ %.2f\n", $it['sku'], $it['qty'], $it['price'] / 100);
}
}
// ออเดอร์ที่ขายแล้วของเดือนสิงหา — ทุกหน้า (istatusGetAll ไล่ cursor + รอ 429 ให้เอง)
const orders = await istatusGetAll('/api/v1/orders', {
from: '2026-08-01', to: '2026-08-31', status: 'paid',
});
for (const o of orders) {
console.log(o.orderNo, o.channel, (o.total / 100).toFixed(2), 'บาท');
for (const it of o.items) console.log(' -', it.sku, 'x' + it.qty, '@', (it.price / 100).toFixed(2));
}
# ออเดอร์ที่ขายแล้วของเดือนสิงหา — ทุกหน้า (istatus_get_all ไล่ cursor + รอ 429 ให้เอง)
orders = istatus_get_all("/api/v1/orders", {
"from": "2026-08-01", "to": "2026-08-31", "status": "paid",
})
for o in orders:
print(o["orderNo"], o["channel"], o["total"] / 100, "บาท")
for it in o["items"]:
print(" -", it["sku"], "x", it["qty"], "@", it["price"] / 100)
// ออเดอร์ที่ขายแล้วของเดือนสิงหา — ทุกหน้า (GetAllAsync ไล่ cursor + รอ 429 ให้เอง)
var orders = await IstatusClient.GetAllAsync("/api/v1/orders", new()
{
["from"] = "2026-08-01", ["to"] = "2026-08-31", ["status"] = "paid",
});
foreach (var o in orders)
{
Console.WriteLine($"{o.GetProperty("orderNo").GetString()} {o.GetProperty("channel").GetString()} {o.GetProperty("total").GetInt64() / 100.0:F2} บาท");
}
รายการสินค้า + ราคาขาย เรียงตาม SKU (A→Z) — คืนทั้งสินค้าที่เปิดและปิดใช้งานพร้อม flag active
(ระบบที่ sync ต้องเห็นตัวที่ถูกปิดด้วย จะได้ไม่ค้างในระบบคุณ) · ไม่มีข้อมูลต้นทุน
| พารามิเตอร์ | ชนิด | ความหมาย |
|---|---|---|
q | string | ค้นหาจาก SKU / ชื่อสินค้า (บางส่วน) หรือบาร์โค้ด (ตรงตัว) |
cursor | string | ค่า nextCursor จากหน้าก่อน |
limit | integer | จำนวนต่อหน้า 1-100 (ไม่ส่ง = 50) |
{
"rows": [
{
"id": "7b1c0000-2222-4333-8444-955566667777",
"sku": "A-001",
"name": "สินค้าตัวอย่าง",
"category": "ทั่วไป",
"barcode": "8850000000001",
"price": 12500, // ราคาปลีก (สตางค์) = 125.00 บาท
"priceWholesale": 11000, // ราคาส่ง (null = ไม่ตั้ง)
"priceVip": null, // ราคาสมาชิก
"stock": 42, // ยอดคงเหลือรวม
"weightG": 500, // น้ำหนัก (กรัม)
"dimensions": "20x15x10", // กว้างxยาวxสูง (ซม.)
"description": null,
"imageUrl": "/uploads/img-xxxx.jpg", // ต่อท้าย https://istatus.net เอง
"active": true // false = ถูกปิดใช้งาน (เทียบเท่าลบ)
}
],
"nextCursor": null
}
# ค้นหาสินค้า
curl -s -G https://istatus.net/api/v1/products \
-H "Authorization: Bearer $ISTATUS_API_KEY" \
--data-urlencode "q=A-0" --data-urlencode "limit=100"
# sync สินค้าทั้งร้าน (ทุกหน้า)
pull_all /api/v1/products "" | jq -r '[.sku, .name, (.price/100), .stock, .active] | @tsv'
// sync สินค้าทั้งร้านเข้าฐานคุณ — ทุกหน้า
$products = istatus_get_all('/api/v1/products');
foreach ($products as $p) {
if (!$p['active']) { /* ปิดใช้งานฝั่งคุณด้วย */ continue; }
printf("%s %s %.2f บาท คงเหลือ %d\n", $p['sku'], $p['name'], $p['price'] / 100, $p['stock']);
}
// sync สินค้าทั้งร้านเข้าฐานคุณ — ทุกหน้า
const products = await istatusGetAll('/api/v1/products');
for (const p of products) {
if (!p.active) continue; // ตัวที่ถูกปิด — ปิดใช้งานฝั่งคุณด้วย
console.log(p.sku, p.name, (p.price / 100).toFixed(2), 'บาท คงเหลือ', p.stock);
}
# sync สินค้าทั้งร้านเข้าฐานคุณ — ทุกหน้า
products = istatus_get_all("/api/v1/products")
for p in products:
if not p["active"]:
continue # ตัวที่ถูกปิด — ปิดใช้งานฝั่งคุณด้วย
print(p["sku"], p["name"], p["price"] / 100, "บาท คงเหลือ", p["stock"])
// sync สินค้าทั้งร้านเข้าฐานคุณ — ทุกหน้า
var products = await IstatusClient.GetAllAsync("/api/v1/products");
foreach (var p in products.Where(p => p.GetProperty("active").GetBoolean()))
{
Console.WriteLine($"{p.GetProperty("sku").GetString()} {p.GetProperty("name").GetString()} {p.GetProperty("price").GetInt64() / 100.0:F2} บาท");
}
ยอดสต๊อกคงเหลือต่อสินค้า (ค่าเดียวกับที่เห็นบนหน้าเว็บ istatus) เรียงตาม SKU — เฉพาะสินค้าที่เปิดใช้งาน · ไม่มีมูลค่า/ต้นทุน · เหมาะกับงาน sync สต๊อกเป็นรอบ
| พารามิเตอร์ | ชนิด | ความหมาย |
|---|---|---|
sku | string | ดูเฉพาะ SKU นี้ (ตรงตัว) |
cursor | string | ค่า nextCursor จากหน้าก่อน |
limit | integer | จำนวนต่อหน้า 1-100 (ไม่ส่ง = 50) |
{
"rows": [
{ "productId": "7b1c0000-2222-4333-8444-955566667777",
"sku": "A-001", "name": "สินค้าตัวอย่าง", "stock": 42 }
],
"nextCursor": null
}
# เช็ค SKU เดียว
curl -s -G https://istatus.net/api/v1/stock \
-H "Authorization: Bearer $ISTATUS_API_KEY" \
--data-urlencode "sku=A-001"
# สต๊อกทั้งร้าน (ทุกหน้า)
pull_all /api/v1/stock "" | jq -r '[.sku, .stock] | @tsv'
// เช็ค SKU เดียว
$page = istatus_get('/api/v1/stock', ['sku' => 'A-001']);
echo ($page['rows'][0]['stock'] ?? 'ไม่พบ SKU นี้') . "\n";
// สต๊อกทั้งร้าน — ทุกหน้า
$stock = istatus_get_all('/api/v1/stock');
// เช็ค SKU เดียว
const page = await istatusGet('/api/v1/stock', { sku: 'A-001' });
console.log(page.rows[0]?.stock ?? 'ไม่พบ SKU นี้');
// สต๊อกทั้งร้าน — ทุกหน้า
const stock = await istatusGetAll('/api/v1/stock');
# เช็ค SKU เดียว
page = istatus_get("/api/v1/stock", {"sku": "A-001"})
print(page["rows"][0]["stock"] if page["rows"] else "ไม่พบ SKU นี้")
# สต๊อกทั้งร้าน — ทุกหน้า
stock = istatus_get_all("/api/v1/stock")
// เช็ค SKU เดียว
var page = await IstatusClient.GetAsync("/api/v1/stock", new() { ["sku"] = "A-001" });
var rows = page.GetProperty("rows");
Console.WriteLine(rows.GetArrayLength() > 0 ? rows[0].GetProperty("stock").GetInt32().ToString() : "ไม่พบ SKU นี้");
// สต๊อกทั้งร้าน — ทุกหน้า
var stock = await IstatusClient.GetAllAsync("/api/v1/stock");
ลูกค้าที่ร้านบันทึกเอง (ข้อมูลที่ร้านเป็นเจ้าของ) เรียงจากใหม่ไปเก่า — ไม่รวมผู้ซื้อฝั่ง marketplace
| พารามิเตอร์ | ชนิด | ความหมาย |
|---|---|---|
q | string | ค้นหาจากชื่อหรือเบอร์โทร (บางส่วน) |
cursor | string | ค่า nextCursor จากหน้าก่อน |
limit | integer | จำนวนต่อหน้า 1-100 (ไม่ส่ง = 50) |
{
"rows": [
{
"id": "5e9a0000-3333-4444-8555-966677778888",
"name": "สมชาย ใจดี",
"phone": "0812345678",
"taxId": null, // เลขผู้เสียภาษี (สำหรับใบกำกับ)
"address": "99/1 ถ.ตัวอย่าง ต.ในเมือง อ.เมือง จ.ขอนแก่น 40000",
"branch": null, // สาขา (สำหรับใบกำกับภาษี)
"contact": null,
"points": 120, // แต้มสะสมคงเหลือ
"note": null,
"shippingMethod": null,
"priceTierId": null, // ระดับราคาที่ผูกกับลูกค้า
"createdAt": "2026-07-15T09:30:00.000Z"
}
],
"nextCursor": null
}
# ค้นหาลูกค้า
curl -s -G https://istatus.net/api/v1/customers \
-H "Authorization: Bearer $ISTATUS_API_KEY" \
--data-urlencode "q=สมชาย"
# ลูกค้าทั้งร้าน (ทุกหน้า)
pull_all /api/v1/customers "" | jq -r '[.name, .phone] | @tsv'
// ค้นหาลูกค้าจากเบอร์โทร
$page = istatus_get('/api/v1/customers', ['q' => '0812345678']);
// ลูกค้าทั้งร้าน — ทุกหน้า
$customers = istatus_get_all('/api/v1/customers');
// ค้นหาลูกค้าจากเบอร์โทร
const page = await istatusGet('/api/v1/customers', { q: '0812345678' });
// ลูกค้าทั้งร้าน — ทุกหน้า
const customers = await istatusGetAll('/api/v1/customers');
# ค้นหาลูกค้าจากเบอร์โทร
page = istatus_get("/api/v1/customers", {"q": "0812345678"})
# ลูกค้าทั้งร้าน — ทุกหน้า
customers = istatus_get_all("/api/v1/customers")
// ค้นหาลูกค้าจากเบอร์โทร
var page = await IstatusClient.GetAsync("/api/v1/customers", new() { ["q"] = "0812345678" });
// ลูกค้าทั้งร้าน — ทุกหน้า
var customers = await IstatusClient.GetAllAsync("/api/v1/customers");
ส่งออเดอร์จากระบบของคุณเข้า istatus — บันทึกผ่านเส้นทางเดียวกับการขายหน้าร้าน (ตัดสต๊อก + ลงบัญชีอัตโนมัติ)
404 · ฟิวส์ความถี่ฝั่งเขียน ~2 คำขอ/วินาที/ร้าน
| field | ชนิด | จำเป็น | ความหมาย |
|---|---|---|---|
externalOrderNo | string (≤60) | ใช่ | เลขออเดอร์ในระบบของคุณ — กุญแจกันบันทึกซ้ำ · เลขบิลใน istatus = API-<ค่านี้> |
items[] | array (1-100) | ใช่ | รายการสินค้า: { "sku": "A-001", "qty": 2 } — ห้าม SKU ซ้ำในบิลเดียว (รวม qty มาแทน) |
customerId | uuid | ไม่ | ลูกค้าในร้าน (id จาก GET /api/v1/customers) — ใช้ระดับราคาของลูกค้ารายนั้น |
shipping | integer | ไม่ | ค่าขนส่งที่คิดกับลูกค้า (สตางค์) |
shippingMethod | string | ไม่ | วิธีจัดส่ง |
note | string (≤500) | ไม่ | หมายเหตุบนบิล |
issuedAt | date-time | ไม่ | วันที่เอกสาร (ISO 8601) — ใช้แสดงบนเอกสาร |
trackingNo · carrier | string | ไม่ | เลขพัสดุ / ขนส่ง |
recipientName · recipientPhone · recipientProvince · recipientPostal | string | ไม่ | ข้อมูลผู้รับ (ใช้พิมพ์ใบปะหน้า) |
422 VALIDATION พร้อมบอกชื่อ field ที่เกิน (ไม่ตัดทิ้งเงียบ)
201 — บันทึกเป็นบิลจริงแล้ว
{ "status": "created", "orderNo": "API-PO-2026-0042" }
202 — โควตาออเดอร์ของแพ็กเดือนนี้เต็ม จึงรับเข้าห้องรอ (สำเร็จแล้ว ไม่ใช่ error — ห้ามยิงซ้ำ) ระบบจะบันทึกเข้าให้เองเมื่ออัปเกรดแพ็กหรือขึ้นเดือนใหม่
{ "status": "held", "heldCount": 3 }
200 — เคยส่ง externalOrderNo นี้แล้ว คืนผลเดิม ไม่เกิดบิลซ้ำ
{ "status": "created", "orderNo": "API-PO-2026-0042" }
409 — สต๊อกไม่พอ (ทั้งบิลไม่ถูกบันทึก)
{ "code": "INSUFFICIENT_STOCK", "message": "สต๊อกไม่พอสำหรับ SKU \"A-001\" — เติมสต๊อกแล้วยิงใหม่" }
409 — ห้องรอของร้านเต็ม (5,000 ใบ) ใบนี้ยังไม่ถูกรับ — อัปเกรดแพ็กเพื่อปล่อยคิว แล้วส่งเลขเดิมมาใหม่
{ "code": "HELD_FULL", "message": "ห้องรอของร้านเต็ม (5,000 ใบ) — ..." }
# ส่งออเดอร์เข้า istatus — "PO-2026-0042" คือเลขออเดอร์ในระบบของคุณ (ใช้กันบันทึกซ้ำ)
istatus_send POST /api/v1/orders '{
"externalOrderNo": "PO-2026-0042",
"items": [{"sku": "A-001", "qty": 2}, {"sku": "B-020", "qty": 1}],
"note": "ส่งด่วน"
}'
# ผลลัพธ์: created / held / already — ทั้งสามแบบ = ส่งสำเร็จ ไม่ต้องยิงซ้ำ
# เน็ตหลุดไม่รู้ผล? ยิงซ้ำ payload เดิมได้เลย — เลขเดิม = บิลเดิม ไม่มีทางเกิดบิลซ้ำ
$r = istatus_send('POST', '/api/v1/orders', [
'externalOrderNo' => 'PO-2026-0042', // เลขในระบบคุณ = กุญแจกันบันทึกซ้ำ
'items' => [['sku' => 'A-001', 'qty' => 2]], // ราคาใช้ของแคตตาล็อกในร้าน
'note' => 'ส่งด่วน',
]);
if ($r['status'] === 202) {
// โควตาออเดอร์ของแพ็กเดือนนี้เต็ม → เข้าห้องรอแล้ว ระบบบันทึกให้เองเมื่ออัปแพ็ก/ขึ้นเดือนใหม่
error_log('เข้าห้องรอ — คิวรวม ' . $r['body']['heldCount'] . ' ใบ (ห้ามยิงซ้ำ)');
} else {
echo $r['body']['orderNo']; // API-PO-2026-0042 (201 = สร้างใหม่ · 200 = เคยส่งแล้ว)
}
const r = await istatusSend('POST', '/api/v1/orders', {
externalOrderNo: 'PO-2026-0042', // เลขในระบบคุณ = กุญแจกันบันทึกซ้ำ
items: [{ sku: 'A-001', qty: 2 }], // ราคาใช้ของแคตตาล็อกในร้าน
note: 'ส่งด่วน',
});
if (r.status === 202) {
// โควตาแพ็กเดือนนี้เต็ม → เข้าห้องรอ ระบบบันทึกให้เองเมื่ออัปแพ็ก/ขึ้นเดือนใหม่ (ห้ามยิงซ้ำ)
console.log('เข้าห้องรอ — คิวรวม', r.body.heldCount, 'ใบ');
} else {
console.log('บันทึกแล้ว', r.body.orderNo); // API-PO-2026-0042
}
status, body = istatus_send("POST", "/api/v1/orders", {
"externalOrderNo": "PO-2026-0042", # เลขในระบบคุณ = กุญแจกันบันทึกซ้ำ
"items": [{"sku": "A-001", "qty": 2}], # ราคาใช้ของแคตตาล็อกในร้าน
"note": "ส่งด่วน",
})
if status == 202:
# โควตาแพ็กเดือนนี้เต็ม → เข้าห้องรอ ระบบบันทึกให้เองเมื่ออัปแพ็ก/ขึ้นเดือนใหม่ (ห้ามยิงซ้ำ)
print("เข้าห้องรอ — คิวรวม", body["heldCount"], "ใบ")
else:
print("บันทึกแล้ว", body["orderNo"]) # API-PO-2026-0042
var (status, body) = await IstatusClient.SendAsync(HttpMethod.Post, "/api/v1/orders", new
{
externalOrderNo = "PO-2026-0042", // เลขในระบบคุณ = กุญแจกันบันทึกซ้ำ
items = new[] { new { sku = "A-001", qty = 2 } },
note = "ส่งด่วน",
});
if (status == 202)
// โควตาแพ็กเดือนนี้เต็ม → เข้าห้องรอ ระบบบันทึกให้เองเมื่ออัปแพ็ก/ขึ้นเดือนใหม่ (ห้ามยิงซ้ำ)
Console.WriteLine($"เข้าห้องรอ — คิวรวม {body.GetProperty("heldCount").GetInt32()} ใบ");
else
Console.WriteLine($"บันทึกแล้ว {body.GetProperty("orderNo").GetString()}");
rows = คิวออเดอร์ที่ค้างห้องรอ (โควตาแพ็กเต็ม หรือรอเติมสต๊อก) เรียงตามลำดับที่ระบบจะปล่อยเข้า (100 รายการแรก) —
ไม่ต้องทำอะไรกับคิวนี้ ระบบปล่อยเข้าเองเมื่ออัปเกรดแพ็ก/ขึ้นเดือนใหม่/เติมสต๊อก ·
rejectedRows = ใบที่ระบบลองปล่อยแล้วไม่ผ่านจนครบเพดาน (เช่น SKU ไม่มีในร้าน) — ยังไม่เป็นยอดขาย
แก้ตาม lastError แล้วส่ง externalOrderNo เดิมมาใหม่ (ใหม่สุดก่อน · 100 รายการ)
พารามิเตอร์: ไม่มี
{
"rows": [
{
"id": "7b1c0000-2222-4333-8444-955566667777",
"orderNo": "API-PO-2026-0042",
"status": "held",
"attemptCount": 0, // จำนวนครั้งที่ระบบลองปล่อยแล้ว payload ไม่ผ่าน — โควตาเต็ม/สต๊อกไม่พอ ไม่นับ
"lastError": null, // เหตุผลที่ปล่อยไม่ผ่านครั้งล่าสุด · "insufficient stock: <sku>" = รอเติมสต๊อก
"createdAt": "2026-08-31T10:00:00+07:00"
}
],
"heldCount": 1,
"heldStockCount": 0, // ในจำนวน heldCount มีกี่ใบที่ติดสต๊อกไม่พอ
"rejectedRows": [
{
"id": "7b1c0000-2222-4333-8444-955566668888",
"orderNo": "API-PO-2026-0039",
"status": "rejected",
"attemptCount": 5,
"lastError": "product not found/inactive: OLD-SKU", // แก้ตามนี้แล้วส่ง externalOrderNo เดิมมาใหม่
"createdAt": "2026-08-30T09:00:00+07:00"
}
],
"rejectedCount": 1
}# ดูคิวออเดอร์ที่ค้างห้องรอ (โควตาเต็ม) — เรียงตามลำดับที่ระบบจะปล่อยเข้า
curl -s https://istatus.net/api/v1/orders/held \
-H "Authorization: Bearer $ISTATUS_API_KEY" | jq
$held = istatus_get('/api/v1/orders/held');
echo $held['heldCount'] . ' ใบรอเข้า';
const held = await istatusGet('/api/v1/orders/held');
console.log(held.heldCount, 'ใบรอเข้า');
held = istatus_get("/api/v1/orders/held")
print(held["heldCount"], "ใบรอเข้า")
var held = await IstatusClient.GetAsync("/api/v1/orders/held");
Console.WriteLine($"{held.GetProperty("heldCount").GetInt32()} ใบรอเข้า");
ตั้งยอดคงเหลือของ SKU นี้ (absolute — ไม่ใช่บวก/ลบ) · ระบบออกใบปรับสต๊อกและลงบัญชีให้อัตโนมัติเหมือนกดปรับจากหน้าเว็บ
404) · SKU ที่มี / หรืออักขระพิเศษ ต้อง URL-encode ก่อนใส่ใน path ·
สินค้าจัดชุด (bundle) ปรับตรงไม่ได้ — ยอดคำนวณจากสินค้าฐาน (422 BUNDLE_SKU)
| field | ชนิด | จำเป็น | ความหมาย |
|---|---|---|---|
qty | integer ≥ 0 | ใช่ | ยอดคงเหลือใหม่ |
note | string (≤200) | ไม่ | หมายเหตุบนใบปรับสต๊อก |
{
"status": "ok",
"sku": "A-001",
"stock": 42, // ยอดคงเหลือหลังปรับ
"delta": 5, // ผลต่างที่ปรับ (+เพิ่ม / -ลด / 0 = ยอดตรงอยู่แล้ว)
"adjusted": true, // false = ยอดตรงอยู่แล้ว ระบบไม่สร้างใบเปล่า
"adjustNo": "API-ADJ-1756600000000-123"
}# ตั้งยอดคงเหลือของ A-001 เป็น 42 ชิ้น (absolute ไม่ใช่บวก/ลบ)
istatus_send PUT /api/v1/stock/A-001 '{"qty": 42, "note": "sync จาก ERP"}'
$r = istatus_send('PUT', '/api/v1/stock/' . rawurlencode('A-001'), [
'qty' => 42, // ยอดคงเหลือใหม่ (ไม่ใช่ผลต่าง)
'note' => 'sync จาก ERP',
]);
echo $r['body']['adjusted'] ? "ปรับ {$r['body']['delta']} ชิ้น" : 'ยอดตรงอยู่แล้ว';
const r = await istatusSend('PUT', `/api/v1/stock/${encodeURIComponent('A-001')}`, {
qty: 42, // ยอดคงเหลือใหม่ (ไม่ใช่ผลต่าง)
note: 'sync จาก ERP',
});
console.log(r.body.adjusted ? `ปรับ ${r.body.delta} ชิ้น` : 'ยอดตรงอยู่แล้ว');
from urllib.parse import quote
status, body = istatus_send("PUT", "/api/v1/stock/" + quote("A-001", safe=""),
{"qty": 42, "note": "sync จาก ERP"}) # ยอดคงเหลือใหม่ (ไม่ใช่ผลต่าง)
print(f"ปรับ {body['delta']} ชิ้น" if body["adjusted"] else "ยอดตรงอยู่แล้ว")
var (_, body) = await IstatusClient.SendAsync(HttpMethod.Put,
"/api/v1/stock/" + Uri.EscapeDataString("A-001"),
new { qty = 42, note = "sync จาก ERP" }); // ยอดคงเหลือใหม่ (ไม่ใช่ผลต่าง)
Console.WriteLine(body.GetProperty("adjusted").GetBoolean()
? $"ปรับ {body.GetProperty("delta").GetInt32()} ชิ้น" : "ยอดตรงอยู่แล้ว");
กดปุ่ม Authorize แล้ววาง API key ของร้านคุณ (ไม่ต้องพิมพ์คำว่า Bearer) จากนั้นกด Try it out ที่ endpoint ไหนก็ได้ — เห็นข้อมูลร้านตัวเองจริงก่อนเขียนโค้ดแม้แต่บรรทัดเดียว
กำลังโหลด Swagger UI…