การยืนยันตัวตน
คำขอ API ทั้งหมดใช้ API key เป็น bearer token สร้างคีย์ได้ในแอปที่ Developer → API keysคีย์มีสองแบบคือ read-only หรือ read/write.
curl https://savrsoft.com/api/v1/events \ -H "Authorization: Bearer savr_live_your_key_here"
Base URL: https://savrsoft.com/api/v1 · คำตอบทั้งหมดเป็นรูปแบบ JSON ข้อผิดพลาดจะส่งคืนเป็น { "error": "…" } พร้อมสถานะ 4xx/5xx
รหัสสถานะ
| รหัส | เมื่อไหร่ที่คุณจะได้รับ |
|---|---|
200 | อ่านข้อมูลสำเร็จ |
201 | สร้างข้อมูลสำเร็จ เนื้อหาคือ { "id": 42 } — ดึงข้อมูลระเบียนหากคุณต้องการรายละเอียดที่เหลือ |
400 | ขาดฟิลด์ที่จำเป็น เช่น { "error": "name is required" }หรือไม่สามารถอ่านเคอร์เซอร์สำหรับการแบ่งหน้าได้ |
401 | ไม่มี API key, key ผิดรูปแบบ หรือ key ที่ถูกเพิกถอนไปแล้ว |
403 | มีการใช้ read-only key สำหรับการเขียนข้อมูล สร้าง read/write key ได้ที่ Developer → API keys. |
404 | ไม่มีเรคคอร์ดนี้อยู่จริง หรือเป็นของอีกองค์กรหนึ่ง — ทั้งสองกรณีนี้ตั้งใจให้แยกไม่ออกจากกัน และยังใช้ตอบกลับกรณี path หรือ method ที่ไม่รู้จักด้วย |
ขอบเขตของ API นี้
API นี้รองรับการ อ่านและสร้างข้อมูลเท่านั้น, ไม่มี endpoint สำหรับ DELETE, PUT หรือ PATCH
— คำขอที่ใช้ method เหล่านี้จะได้รับ 404 Unknown endpointเช่นเดียวกับ path ที่ไม่รู้จักใดๆ การลบและ
แก้ไขข้อมูลทำผ่านแอปเท่านั้น ดังนั้นการเชื่อมต่อระบบภายนอกจึงไม่สามารถทำลายข้อมูลของผู้จัดเลี้ยงได้ หากต้องการสิทธิ์เขียนข้อมูลมากกว่าการสร้าง
บอกเราว่าคุณกำลังสร้างอะไร.
การแบ่งหน้า (Pagination)
ทุก endpoint ที่แสดงรายการจะถูกแบ่งหน้า ส่งค่า limit (ค่าเริ่มต้น 100, สูงสุด 500) และ
ใช้ next_cursor ต่อไปเรื่อยๆ จนกว่า has_more จะเป็น false:
{
"data": [ ... ],
"has_more": true,
"next_cursor": "eyJuYW1lIjoiQWNtZSIsImlkIjo0Mn0"
}
ส่ง cursor กลับไปตรงๆ เพื่อดึงหน้าถัดไป ให้ถือว่าเป็นค่าที่ไม่ต้องตีความ — รูปแบบการเข้ารหัสอาจเปลี่ยนแปลงได้ในอนาคต:
curl "https://savrsoft.com/api/v1/clients?limit=100&cursor=eyJuYW1lIjoiQWNtZSIsImlkIjo0Mn0" \ -H "Authorization: Bearer savr_live_your_key_here"
การแบ่งหน้าจะยึดตามแถวสุดท้ายที่คุณได้รับ ไม่ใช่ยึดตามค่า offset ดังนั้นแม้จะมีแถวถูกสร้างหรือลบระหว่างที่คุณแบ่งหน้า ก็จะไม่ทำให้คุณข้ามหรือได้รับข้อมูลซ้ำ cursor จะยังคงใช้งานได้ตลอดไป
| พารามิเตอร์ | พฤติกรรม |
|---|---|
limit | 1–500 หากไม่ระบุ เป็นศูนย์ ติดลบ หรือไม่ใช่ตัวเลข จะกลับไปใช้ค่าเริ่มต้น 100 โดยอัตโนมัติ ส่วนค่าที่เกิน 500 จะถูกจำกัดไว้ที่ 500 |
cursor | next_cursor จากหน้าก่อนหน้า ไม่ต้องระบุสำหรับหน้าแรก หาก cursor ไม่สามารถอ่านได้ — เสียหาย หรือมาจาก endpoint อื่น — ระบบจะส่งค่า 400 Invalid cursor แทนที่จะเริ่มต้นใหม่โดยไม่แจ้งเตือน |
การเรียงลำดับ: /events และ /invoices ใหม่สุดก่อน; /recipes และ
/clients เรียงตามชื่อ โดยหากชื่อซ้ำกันจะใช้ id ตัดสิน เพื่อให้การแบ่งหน้ายังคงเสถียรแม้ชื่อจะซ้ำกัน
Endpoints
/v1/meองค์กรของคุณ (id, ชื่อ, สกุลเงิน)
/v1/eventsแสดงรายการอีเวนต์ ใหม่สุดก่อน รองรับการแบ่งหน้า — ดู การแบ่งหน้า ด้านบน
/v1/events/{id}หนึ่งอีเวนต์พร้อมเมนูอาหาร
/v1/events · ต้องมีสิทธิ์ read/writeสร้างอีเวนต์ Body:
{ "name": "Acme holiday party", "event_date": "2026-12-18",
"guests": 120, "price_per_head": 55, "event_type": "corporate" }
/v1/recipes · GET/v1/recipes/{id}แสดงรายการสูตรอาหาร (พร้อมสารก่อภูมิแพ้และป้ายกำกับข้อจำกัดด้านอาหาร) หรือดูสูตรอาหารรายการเดียวพร้อมส่วนผสม รายการนี้แบ่งหน้าและเรียงตามชื่อ
/v1/clients · POST/v1/clientsแสดงรายการลูกค้า (แบ่งหน้า เรียงตามชื่อ) หรือสร้างลูกค้าใหม่ Body สำหรับสร้าง: { "name": "...", "email": "...", "phone": "...", "company": "..." }
/v1/invoices · GET/v1/invoices/{id}แสดงรายการใบแจ้งหนี้พร้อมยอดรวมที่คำนวณแล้ว เรียงจากล่าสุด หรือดูใบแจ้งหนี้รายการเดียวพร้อมรายการย่อย รายการนี้แบ่งหน้า
Webhooks
ลงทะเบียน endpoint ได้ที่ Developer → Webhooksเราจะ POST ข้อมูล JSON เมื่อเกิดอีเวนต์ต่างๆ:
{
"id": "evt_abc123",
"type": "event.inquiry",
"created_at": "2026-07-17T18:20:00.000Z",
"data": { "id": 42, "name": "Corporate lunch — Dana Ruiz", ... }
}
ประเภทอีเวนต์
| ประเภท | เกิดขึ้นเมื่อ |
|---|---|
event.created | มีการสร้างอีเวนต์ |
event.inquiry | มีคำขอจัดเลี้ยงออนไลน์เข้ามาใหม่ |
invoice.sent | ใบแจ้งหนี้ถูกทำเครื่องหมายว่าส่งแล้ว |
invoice.paid | ใบแจ้งหนี้ถูกทำเครื่องหมายว่าชำระแล้ว |
proposal.approved | ลูกค้าอนุมัติข้อเสนอ |
client.created | มีการสร้างลูกค้าใหม่ |
การตรวจสอบลายเซ็น
การส่งข้อมูลแต่ละครั้งจะมีส่วนหัว x-savrsoft-signature : ค่า HMAC-SHA256 ของเนื้อหาคำขอดิบ โดยใช้ signing secret ของ webhook ของคุณเป็นคีย์ คำนวณค่าเดียวกันแล้วเปรียบเทียบ:
// Node.js
const crypto = require('crypto');
const sig = crypto.createHmac('sha256', SIGNING_SECRET)
.update(rawBody).digest('hex');
if (sig !== req.headers['x-savrsoft-signature']) reject();
