ข้ามไปยังเนื้อหา

ขีดจำกัดการเรียก

มีสองขีดจำกัดที่ปกป้องระบบ และนับแยกกัน คือขีดจำกัดต่อนาที กับโควตาต่อวัน ทั้งคู่นับแยกรายกุญแจ กุญแจที่ออกให้ระบบคนละตัวจึงไม่แย่งโควตากันเอง

ขีดจำกัด ค่าตั้งต้น หน้าต่างเวลา นับกับ
ต่อนาที 60 คำขอต่อนาที 60 วินาทีแบบเลื่อนไปเรื่อย ๆ กุญแจแต่ละใบ
ต่อวัน 10,000 คำขอต่อวัน วันตามปฏิทินไทย รีเซ็ต 00:00 น. เวลาไทย กุญแจแต่ละใบ

นี่คือค่าตั้งต้นที่กุญแจทุกใบเริ่มต้น และปรับขึ้นหรือลงรายกุญแจได้ตอนออกใบ โควตาต่อวันที่ใช้จริงกับกุญแจของคุณจะถูกส่งกลับมาในทุกคำขอที่สำเร็จ ที่ส่วนหัว X-RateLimit-Limit ให้อ่านจากตรงนั้นแทนการเขียนตัวเลขตายไว้ในโค้ด ส่วนเพดานต่อนาทีจะถูกรายงานเป็นช่อง limit ใน body ของคำตอบ 429 rate_limited

สองรายละเอียดที่ตัดสินว่าโควตาพอหรือไม่

  • นับคำขอที่เข้ามา ไม่ใช่คำขอที่สำเร็จ คำขอที่ล้มซ้ำ ๆ ก็กินโควตา ถ้าเจอลูปที่ได้ 400 ให้แก้ ไม่ใช่ปล่อยให้วิ่งต่อ
  • ตัวนับรายวันรีเซ็ตเที่ยงคืนเวลาไทย (UTC+7) ไม่ใช่เที่ยงคืนตามเขตเวลาของคุณ งานที่เริ่ม 23:00 น. ในยุโรป กำลังใช้โควตาของวันถัดไปอยู่แล้ว
ส่วนหัว ติดมากับคำตอบไหน ค่า
X-RateLimit-Limit ทุกคำขอที่ผ่านการตรวจกุญแจและผ่านขีดจำกัดต่อนาทีแล้ว โควตา ต่อวัน ของกุญแจใบนั้น
X-RateLimit-Remaining เช่นเดียวกัน จำนวนคำขอที่เหลือของวันนี้ หลังหักคำขอนี้แล้ว
Retry-After เฉพาะ 429 rate_limited จำนวนวินาทีที่ควรรอก่อนลองใหม่

ส่วนหัวข้างบนบอกสถานะของคำขอที่เพิ่งยิงไป และติดมาเฉพาะคำขอที่มีมันเท่านั้น ส่วน GET /v1/usage ตอบคำถามเดียวกันแบบตรง ๆ เมื่อไหร่ก็ได้ที่ถาม ทั้งเพดานที่กุญแจของคุณใช้จริง ยอดที่ใช้ไปแล้ววันนี้ และอีกหกวันก่อนหน้า

เส้นนี้ไม่รับพารามิเตอร์ใด ๆ รายงานเฉพาะกุญแจที่อยู่ในส่วนหัว Authorization เท่านั้น ไม่มีทางถามยอดของกุญแจใบอื่น

Terminal window
curl -s -H "Authorization: Bearer $SCANFOOD_API_KEY" \
"https://api.scanfood.co/ext/v1/usage"
{
"ok": true,
"data": {
"keyId": "9f3a1c7d5b2e",
"label": "ซิงก์คลังรอบดึก",
"scope": "shop",
"rate": { "perMin": 60, "perDay": 10000 },
"today": { "date": "20260910", "count": 127, "remaining": 9873 },
"days": [
{ "date": "20260904", "count": 96 },
{ "date": "20260905", "count": 101 },
{ "date": "20260906", "count": 0 },
{ "date": "20260907", "count": 88 },
{ "date": "20260908", "count": 143 },
{ "date": "20260909", "count": 132 },
{ "date": "20260910", "count": 127 }
]
},
"serverTime": "2026-09-10T06:45:12.310Z"
}
ช่อง หมายความว่า
keyId กุญแจที่ใช้ยืนยันตัวตน คือส่วนหน้าของกุญแจที่อยู่ก่อนท่อนลับ
label ป้ายชื่อที่บันทึกไว้ตอนออกกุญแจ ถ้าไม่ได้ตั้งไว้จะเป็นสตริงว่าง
scope shop คือร้านเดียว · franchise คือทุกสาขาภายใต้แฟรนไชส์เดียว
rate.perMin เพดานต่อนาทีที่กุญแจใบนี้ใช้จริง
rate.perDay โควตาต่อวันที่กุญแจใบนี้ใช้จริง เลขเดียวกับ X-RateLimit-Limit
today.date วันนี้ตามเวลาไทย รูป YYYYMMDD
today.count จำนวนคำขอที่นับไปแล้ววันนี้ รวมคำขอนี้ด้วย
today.remaining perDay ลบ count และไม่ต่ำกว่าศูนย์
days[] เจ็ดวันไทยล่าสุด เก่าสุดอยู่ต้น วันนี้อยู่ท้าย แต่ละตัวมี date กับ count

สามข้อที่ควรรู้ก่อนเอาตัวเลขไปใช้ต่อ

  • today.count รวมคำขอที่เพิ่งยิงไปด้วย เพราะนับตอนคำขอเข้ามา ดังนั้น today.remaining จึงเท่ากับส่วนหัว X-RateLimit-Remaining ของคำตอบเดียวกันนั้นพอดี และการถามยอดใช้ก็กินโควตาหนึ่งคำขอเหมือนการเรียกเส้นอื่น
  • days มีเจ็ดรายการเสมอ วันที่ไม่มีการเรียกเลยจะคืนมาเป็น 0 ไม่ใช่หายไปจากลิสต์ กราฟหรือรายงานจึงไม่ต้องมาเติมช่องว่างเอง
  • นี่คือที่เดียวที่บอก perMin ได้โดยไม่ต้องไปชนมัน ไม่มีส่วนหัวใดบอกเพดานต่อนาที นอกจากทางนี้ก็เหลือแค่ช่อง limit ใน body ของคำตอบ 429 rate_limited

สถานะ 429 ใช้ร่วมกันสองสถานการณ์ ซึ่งต้องรับมือคนละแบบ ให้แตกสาขาที่ช่อง error

error หมายความว่า ทำอย่างไร
rate_limited ยิงถี่เกินไปในหนึ่งนาทีที่ผ่านมา รอตาม Retry-After วินาที แล้วลองใหม่ อย่าลดเวลารอ ใน body ยังมี retryAfterSec limit และ windowSec
quota_exceeded โควตาของวันหมดแล้ว หยุดรอบนี้ไปก่อนจนถึงวันถัดไป หรือขอเพิ่มโควตา ลองใหม่ก่อน 00:00 น. เวลาไทย ไม่ช่วยอะไร
{
"ok": false,
"error": "rate_limited",
"message": "Too many requests, retry in about 37 seconds",
"retryAfterSec": 37,
"limit": 60,
"windowSec": 60
}

ตัวช่วยลองใหม่ที่รองรับทั้งสองกรณี

Terminal window
# ปล่อยให้ curl เป็นคนรอและลองใหม่ให้
curl -s --retry 5 --retry-delay 5 --retry-all-errors \
-H "Authorization: Bearer $SCANFOOD_API_KEY" \
"https://api.scanfood.co/ext/v1/shops"

ขีดจำกัดนี้เหลือเฟือสำหรับการซิงก์ แต่คับสำหรับการไล่ดูดทั้งฐาน คิดเลขสักหน่อยก่อนเขียนตัวตั้งเวลา

  • ขอทีละหน้าเต็ม limit=200 คือค่าสูงสุด วันที่มี 600 บิล ใช้ 4 คำขอถ้าขอ 200 แต่ใช้ 7 คำขอถ้าขอ 100 คือหน้าเต็ม 3 หน้า (หรือ 6 หน้า) บวกอีกหนึ่งคำขอปิดท้าย เพราะหน้าที่กลับมาเต็มพอดีต้องยิงอีกหนึ่งครั้งจึงจะเห็น nextCursor: null
  • หนึ่งคำขอต่อหนึ่งร้าน แฟรนไชส์ 20 สาขา ดึงทุก 15 นาที เท่ากับ 20 × 4 × 24 = 1,920 คำขอต่อวันก่อนนับการแบ่งหน้า ซึ่งยังสบายใน 10,000 แต่กลุ่มที่ใหญ่กว่านี้ควรคิดเลขก่อน
  • ดึงเฉพาะส่วนที่เปลี่ยน updatedSince คืนเฉพาะสิ่งที่เปลี่ยนตั้งแต่รอบก่อน ช่วงสิบห้านาทีที่ร้านเงียบจึงเสียแค่คำขอเดียวต่อร้าน
  • ดึงย้อนหลังอย่างมีแผน ประวัติดึงได้ครั้งละไม่เกิน 31 วัน หนึ่งปีต่อหนึ่งร้าน จึงอย่างน้อย 12 คำขอบวกการแบ่งหน้า ให้รันครั้งเดียวนอกเวลาเร่งด่วน ไม่ใช่รันทุกครั้งที่ deploy
  • กระจายสาขาออกจากกัน ยิง 20 ร้านพร้อมกันในวินาทีเดียวคือการยิงถี่ แต่ 20 ร้านเดียวกันที่กระจายทั้งนาทีไม่ใช่

ถ้างานที่จำเป็นจริง ๆ ยังไม่พอ ให้ขอเพิ่มโควตา ดีกว่าแตกระบบเดียวไปใช้หลายกุญแจ เพราะกุญแจหลายใบทำให้ย้อนดูการใช้งานไม่ได้