ข้อมูลทดสอบ
ScanFood API อ่านข้อมูลที่มีอยู่แล้วจริง ๆ คือบิลที่ร้านของคุณปิดไปแล้ว และสมาชิกที่แบรนด์ของคุณสมัครไว้ เราไม่มีสำเนาข้อมูลชุดที่สองไว้ให้ซ้อม ⇒ คุณจะพัฒนาและทดสอบบนข้อมูลชุดเดียวกับที่จะใช้จริง
ฟังดูน่ากลัวกว่าความเป็นจริง เพราะทุกเส้นเป็น GET ไม่มีคำขอไหนที่แก้บิลได้
และร่องรอยเดียวที่คำขอทิ้งไว้คือ 1 บรรทัดในสมุดการใช้งานของเรา กับตัวนับโควตาของคุณ +1
ที่เหลือของหน้านี้คือวิธีทำให้สัปดาห์แรกของคุณถูกและน่าเบื่อ
ยังไม่มีระบบทดสอบแยก
หัวข้อที่มีชื่อว่า “ยังไม่มีระบบทดสอบแยก”ไม่มีสภาพแวดล้อมทดสอบ ไม่มีร้านปลอม และไม่มีกุญแจทดสอบที่ต่อไปยังฐานข้อมูลคนละชุด
กุญแจที่แจกให้ใช้งานจริงขึ้นต้นด้วย sf_live_ และอ่านข้อมูลจริงเสมอ
สิ่งที่ปกป้องคุณอยู่แทน sandbox:
| ตัวช่วย | แปลว่าอะไรกับคุณ |
|---|---|
| ทุกเส้นอ่านอย่างเดียว | ไม่มีคำขอไหนที่แก้ ยกเลิก หรือลบอะไรใน ScanFood ได้เลย |
| กุญแจเป็นตัวกำหนดว่ามีอะไรอยู่ | กุญแจเห็นเฉพาะร้านที่ออกให้เท่านั้น · ขอ shopId นอกขอบเขต = 403 ไม่ใช่การแอบดู |
| ข้อมูลอ่อนไหวปิดเป็นค่าตั้งต้น | เบอร์โทร/อีเมลของสมาชิกจะไม่ถูกส่งออก เว้นแต่กุญแจใบนั้นถูกออกมาพร้อมสิทธิ์นี้โดยเฉพาะ |
| มีมิเตอร์คุม ไม่ใช่บิลค่าใช้จ่ายที่บานปลาย | ลูปที่หลุดจะไปชนเพดานต่อนาทีหรือโควตาวันแล้วหยุดเอง ไม่ทำให้เกิดค่าใช้จ่าย |
ขอกุญแจสำหรับทดสอบแยกใบ
หัวข้อที่มีชื่อว่า “ขอกุญแจสำหรับทดสอบแยกใบ”ขอกุญแจจากทีม ScanFood 2 ใบ — ใบหนึ่งสำหรับระบบที่คุณกำลังสร้าง อีกใบสำหรับคนไล่ยิงมือ กุญแจแต่ละใบมีป้ายชื่อของตัวเอง มีเพดานต่อนาทีของตัวเอง มีโควตาวันของตัวเอง และมีประวัติการใช้งานของตัวเอง
ทำไมถึงคุ้มที่จะขอเพิ่มอีกใบ:
- เพิกถอนใบที่ใช้ลองได้ทันทีที่งานเสร็จ โดยไม่กระทบระบบที่วิ่งอยู่
- สมุดการใช้งานแยกกัน ⇒ คำถามว่า “เมื่อคืนใครยิง 4,000 ครั้ง” มีคำตอบ
- โควตาคนละก้อน ⇒ การทดลองกินโควตาของระบบจริงไม่ได้
วิธีทดสอบกับข้อมูลจริงอย่างปลอดภัย
หัวข้อที่มีชื่อว่า “วิธีทดสอบกับข้อมูลจริงอย่างปลอดภัย”5 นิสัย เรียงตามความสำคัญ:
- เริ่มที่
/v1/shops— ไม่ต้องส่งพารามิเตอร์อะไรเลย คืนเฉพาะร้านที่กุญแจเห็น และพิสูจน์ว่ากุญแจใช้ได้ก่อนจะไปเขียนอะไรที่ยากกว่านี้ - ใช้ช่วงวันสั้น ๆ — วันเดียว (
from=20260901&to=20260901) ก็พอเห็นหน้าตาของบิลแล้ว เส้นนี้รับได้ถึง 31 วันต่อคำขอ แต่คุณยังไม่จำเป็นต้องใช้ตอนที่ยังนั่งอ่าน JSON ด้วยตา - ตั้ง
limitให้เล็ก — ค่าตั้งต้นคือ 100 สูงสุด 200 · ตอนสำรวจให้ใช้limit=5จะได้ผลลัพธ์ที่พอดีหน้าจอ และไล่ลูปหน้าถัดไปได้ง่ายกว่า - อ่าน
X-RateLimit-Remainingทุกครั้ง ระหว่างพัฒนา — เป็นสัญญาณเตือนที่ถูกที่สุดที่คุณจะได้ - เลือกร้านที่ไม่พลุกพล่านสำหรับรอบแรก ๆ — ร้านไหนในขอบเขตกุญแจก็ใช้ได้ แต่สาขาที่ไม่ได้อยู่กลางช่วงขายจะเป็นเป้านิ่งกว่าเวลาคุณเทียบผลสองรอบ
รอบทดสอบแรกที่แทบไม่กินโควตา
หัวข้อที่มีชื่อว่า “รอบทดสอบแรกที่แทบไม่กินโควตา”ลำดับนี้ใช้ 4 คำขอ และพาคุณเห็นบิลจริงตั้งแต่ต้นจนจบ
# 0. อย่าให้กุญแจตกค้างใน history ของ shellread -rs SCANFOOD_API_KEY && export SCANFOOD_API_KEY
# 1. กุญแจใบนี้เห็นร้านไหนบ้างcurl -s -H "Authorization: Bearer $SCANFOOD_API_KEY" \ "https://api.scanfood.co/ext/v1/shops"
# 2. ขอบิล 5 ใบของวันเดียว (วันที่เป็น YYYYMMDD ไม่มีขีด)curl -s -H "Authorization: Bearer $SCANFOOD_API_KEY" \ "https://api.scanfood.co/ext/v1/transactions?shopId=SHOP_ID&from=20260901&to=20260901&limit=5"
# 3. ดูบิลใบเดียวแบบเต็มcurl -s -H "Authorization: Bearer $SCANFOOD_API_KEY" \ "https://api.scanfood.co/ext/v1/transactions/TXN_ID"
# 4. วันนี้เหลือโควตาเท่าไหร่curl -s -D - -o /dev/null -H "Authorization: Bearer $SCANFOOD_API_KEY" \ "https://api.scanfood.co/ext/v1/shops" | grep -i x-ratelimitconst KEY = process.env.SCANFOOD_API_KEY;const BASE = 'https://api.scanfood.co/ext/v1';
async function get(path) { const res = await fetch(`${BASE}${path}`, { headers: { Authorization: `Bearer ${KEY}` }, }); const body = await res.json(); if (!res.ok) throw new Error(`${res.status} ${body.error}: ${body.message}`); console.log('โควตาที่เหลือ:', res.headers.get('X-RateLimit-Remaining')); return body;}
const { data: shops } = await get('/shops');const shopId = shops[0].shopId;
const { data: bills } = await get( `/transactions?shopId=${shopId}&from=20260901&to=20260901&limit=5`,);console.log(bills.length, 'บิล');
if (bills.length) console.log(await get(`/transactions/${bills[0].id}`));import os, requests
KEY = os.environ["SCANFOOD_API_KEY"]BASE = "https://api.scanfood.co/ext/v1"SESSION = requests.Session()SESSION.headers["Authorization"] = f"Bearer {KEY}"
def get(path): res = SESSION.get(BASE + path, timeout=30) body = res.json() if not res.ok: raise RuntimeError(f"{res.status_code} {body['error']}: {body['message']}") print("โควตาที่เหลือ:", res.headers.get("X-RateLimit-Remaining")) return body
shops = get("/shops")["data"]shop_id = shops[0]["shopId"]
bills = get(f"/transactions?shopId={shop_id}&from=20260901&to=20260901&limit=5")["data"]print(len(bills), "บิล")
if bills: print(get(f"/transactions/{bills[0]['id']}"))ถ้าขั้นที่ 2 คืน data เป็นลิสต์ว่าง แปลว่าวันนั้นสาขานั้นไม่มีบิล — ลองเปลี่ยนเป็นวันที่คุณรู้ว่าขายดี
ลิสต์ว่างคือคำตอบที่ถูกต้อง ไม่ใช่ข้อผิดพลาด
เฝ้าดูโควตาที่เหลือ
หัวข้อที่มีชื่อว่า “เฝ้าดูโควตาที่เหลือ”มีเพดาน 2 ชั้นทำงานพร้อมกัน และตกคนละแบบ:
| เพดาน | ค่าตั้งต้น | เกินแล้วเห็นอะไร |
|---|---|---|
| คำขอต่อนาที ต่อกุญแจ | 60 | 429 พร้อม "error": "rate_limited" และหัว Retry-After บอกว่าให้รอกี่วินาที |
| คำขอต่อวัน ต่อกุญแจ | 10,000 | 429 พร้อม "error": "quota_exceeded" · ตัวนับรีเซ็ต 00:00 น. เวลาไทย (ICT) |
X-RateLimit-Limit และ X-RateLimit-Remaining พูดถึง โควตาวัน ไม่ใช่เพดานต่อนาที
และจะโผล่เฉพาะกับคำขอที่ผ่านด่านกุญแจและด่านต่อนาทีมาแล้ว ⇒ 401 หรือ 429 ที่เป็น rate_limited จะไม่มี 2 หัวนี้
ตัวนับทั้งคู่นับ คำขอที่รับเข้ามา ไม่ใช่คำขอที่สำเร็จ
ลูปที่ยิงคำขอผิดรูป 500 ครั้ง = กินโควตาวันไป 500
นี่คือเหตุผลที่ดีที่สุดข้อเดียวที่ควรพัฒนาด้วย limit=5 และช่วงวันเดียว
เมื่อพร้อมใช้งานจริง
หัวข้อที่มีชื่อว่า “เมื่อพร้อมใช้งานจริง”ฝั่ง API ไม่มีอะไรเปลี่ยนเลย — กุญแจใบเดิม โฮสต์เดิม คำตอบชุดเดิม ที่เปลี่ยนคือฝั่งคุณ และรายการที่ต้องไล่ให้ครบก่อนขยายช่วงวันแล้วเปิดรอบดึงจริงอยู่ที่ เช็กลิสต์ก่อนขึ้นจริง
2 อย่างที่ควรทำก่อนถึงวันนั้น:
- ดึงย้อนหลังให้ครบก่อน แล้วค่อยสลับเป็นดึงเฉพาะที่เปลี่ยน — ไล่ประวัติด้วย
from/toทีละไม่เกิน 31 วัน แล้วค่อยเริ่มถามupdatedSinceสำหรับบิล · ส่วนสมาชิกยังไม่มีโหมดดึงเฉพาะที่เปลี่ยน ต้อง full sync เสมอ - เลิกใช้กุญแจที่ใช้ลอง — ขอให้เพิกถอนเมื่อสำรวจเสร็จ
การเพิกถอนมีผลภายใน 5 นาที หลังจากนั้นกุญแจใบนั้นจะตอบ
401