เอกสาร API ของ SMM Panel
ทุกฟังก์ชันของ SMM Panel นี้เรียกใช้ผ่าน API ได้ 2 ชุดแยกกัน ทั้งสองชุดใช้บัญชี ยอดเงินคงเหลือ และแคตตาล็อกบริการเดียวกัน ต่างกันที่รูปแบบและความสามารถ
ทุกฟังก์ชันของ SMM Panel นี้เรียกใช้ผ่าน API ได้ 2 ชุดแยกกัน ทั้งสองชุดใช้บัญชี ยอดเงินคงเหลือ และแคตตาล็อกบริการเดียวกัน ต่างกันที่รูปแบบและความสามารถ
ทำไมถึงมี API 2 ชุด?
API สำหรับตัวแทนจำหน่ายแบบคลาสสิก (v2) ที่ใช้กันทั่ววงการจะส่งฟอร์มไปยัง Endpoint เดียว และตอบกลับด้วย HTTP 200 เสมอ ซึ่งตรงกับรูปแบบที่ซอฟต์แวร์พาเนลสำเร็จรูปต้องการพอดี เราจึงคงไว้ตามเดิม แต่นักพัฒนาที่เขียนระบบของตัวเองมักติดข้อจำกัดของ API ชุดนี้ซ้ำ ๆ: แยกแยะข้อผิดพลาดไม่ได้ แคตตาล็อกมาเป็นก้อนเดียว และต้องคอยเช็กสถานะคำสั่งซื้อซ้ำไปเรื่อย ๆ เราจึงสร้าง v3 ขึ้นมาเพื่อนักพัฒนากลุ่มนี้
เปรียบเทียบ
| คุณสมบัติ | เดิม (v2) | ใหม่ (v3) |
|---|---|---|
| รูปแบบ | Endpoint เดียว ส่งแบบฟอร์ม ใช้พารามิเตอร์ action | REST แบบอิงทรัพยากร (resource) ส่ง body เป็น JSON |
| สถานะ HTTP | 200 เสมอ แม้คำขอจะล้มเหลว | โค้ดจริง (400, 401, 402, 404, 409, 429, 502) |
| ข้อผิดพลาด | ข้อความอิสระ | type + code ที่คงที่ + message ตามภาษาที่เลือก + param + doc_url |
| สถานะคำสั่งซื้อ | มีแค่ข้อความตามภาษา | ค่าคงที่สำหรับโปรแกรม พร้อมป้ายข้อความสำหรับแสดงผลแยกต่างหาก |
| คำอธิบายบริการ | ไม่มี | คำอธิบาย 16 ภาษา เวลาเฉลี่ย แพลตฟอร์ม และหมวดหมู่ |
| ฟิลด์คำสั่งซื้อ | ต้องเดาเอาจากชื่อประเภท | แต่ละบริการเผยแพร่สคีมาฟิลด์ของตัวเอง |
| หน่วยราคา | ไม่ระบุ (ต้นเหตุของราคาผิด 1,000 เท่าในบริการแบบแพ็กเกจ) | ระบุชัดเจนว่า per_1000 หรือ per_order |
| แคตตาล็อก | ทุกบริการมาในการตอบกลับเดียว | มีตัวกรอง พร้อมการแบ่งหน้าแบบ cursor |
| กันคำสั่งซื้อซ้ำ | ไม่มี | Idempotency-Key |
| การอัปเดตสถานะ | ต้องคอยเช็กตลอดเวลา (polling) | Webhook ที่มีลายเซ็น หรือสตรีมเหตุการณ์ |
| สคีมา | ไม่มี | OpenAPI 3.1 |
| ภาษา | ภาษาอังกฤษและภาษาตุรกี (URL แยกกัน) | 16 ภาษา (ผ่าน header หรือพารามิเตอร์) |
| คุณภาพบริการ | ไม่มี | คะแนน ความเชื่อมั่น และหลักฐานรายบริการ พร้อมรายการคัดสรรที่จัดอันดับแล้ว |
ควรใช้ API ชุดไหน?
เลือก API เดิม หากคุณใช้ซอฟต์แวร์พาเนลสำเร็จรูป บอท หรือพาเนลตัวแทนจำหน่าย ส่วนใหญ่แค่เปลี่ยน URL ของ API และคีย์ ก็เริ่มใช้งานได้ภายในไม่กี่นาที
เลือก v3 หากคุณเขียนแอปพลิเคชัน หน้าร้าน หรือระบบอัตโนมัติของตัวเอง การจัดการข้อผิดพลาด การกันคำสั่งซื้อซ้ำ และการแจ้งเตือนมีมาให้ในตัว ทั้งยังสร้างฟอร์มสั่งซื้อได้โดยตรงจากสคีมาของบริการ
เริ่มต้นใช้งาน
- 1สร้างคีย์ API ในแท็บคีย์ API
- 2ดึงรายการบริการ แล้วดู id และสคีมาฟิลด์ของบริการที่ต้องการ
- 3ตรวจสอบคำสั่งซื้อด้วย preview ก่อน แล้วจึงสร้างจริง
- 4ลงทะเบียน Webhook หรืออ่านสตรีมเหตุการณ์ เพื่อติดตามการเปลี่ยนสถานะ
REST API ที่ออกแบบมาสำหรับนักพัฒนาที่สร้างระบบของตัวเอง มีพาธแบบอิงทรัพยากร โค้ดสถานะ HTTP จริง ข้อผิดพลาดที่เครื่องอ่านได้ และการแจ้งเตือนที่มีลายเซ็น
Base URL
ทุกพาธจะต่อท้าย URL นี้ และเวอร์ชันอยู่ในพาธ หากวันหนึ่งจำเป็นต้องมีการเปลี่ยนแปลงที่ไม่เข้ากันกับของเดิม (breaking change) เราจะเผยแพร่พาธใหม่ (v4) โดยพาธนี้ยังทำงานต่อได้ตามเดิมทุกอย่าง ทุกการตอบกลับจะมีวันที่เผยแพร่ของสัญญา API (contract) อยู่ใน header X-Api-Version
https://panelfollows.com/api/v3การยืนยันตัวตน
ส่งคีย์ API เป็น Bearer token ใน header Authorization หรือจะใช้ header X-Api-Key แทนก็ได้
GET https://panelfollows.com/api/v3/account
Authorization: Bearer pf_live_...คีย์เดิมของคุณใช้กับ v3 ได้ด้วย จึงลองใช้งานได้ทันที แต่ในระบบใช้งานจริง (production) ควรใช้คีย์ v3 เพราะตั้งชื่อกำกับได้ เพิกถอนแยกทีละคีย์ได้ และไม่มีการเก็บเป็นข้อความธรรมดา (plain text)
เริ่มต้นอย่างรวดเร็ว
curl https://panelfollows.com/api/v3/services?limit=5 \
-H "Authorization: Bearer YOUR_API_KEY"ภาษา
เลือกภาษาของการตอบกลับด้วย header Accept-Language หรือพารามิเตอร์ ?lang= หากส่งมาทั้งคู่ ระบบจะใช้ค่าจากพารามิเตอร์ ชื่อบริการ คำอธิบายบริการ ชื่อหมวดหมู่ ป้ายสถานะคำสั่งซื้อ ป้ายฟิลด์คำสั่งซื้อ และข้อความแสดงข้อผิดพลาด จะส่งกลับมาเป็นภาษานั้นทั้งหมด
ค่าสำหรับโปรแกรมไม่เปลี่ยนตามภาษา: error.code / order.status / service.type และ currency มีค่าเหมือนเดิมเสมอ ให้โค้ดตัดสินใจจากค่าเหล่านี้ แล้วนำข้อความไปแสดงให้ผู้ใช้ของคุณ
Accept-Language: tr
# veya
GET https://panelfollows.com/api/v3/services?lang=trรูปแบบคำขอและการตอบกลับ
body ของคำขอเป็น JSON (application/json) และรับ form-urlencoded ด้วยสำหรับการทดสอบอย่างรวดเร็ว การตอบกลับเป็น JSON: ทรัพยากรเดี่ยวเป็นออบเจ็กต์ธรรมดา ส่วนรายการจะอยู่ในซองข้อมูล (envelope) ที่มี data / has_more / next_cursor ทุกออบเจ็กต์มีฟิลด์ object ที่บอกประเภทของตัวเอง
จำนวนเงินเป็นสตริงทศนิยม ("1.2340") ไม่ใช่ float กรุณาแปลงเป็นชนิดข้อมูลทศนิยม (decimal) ในฝั่งของคุณ เพื่อไม่ให้เศษทศนิยมหาย สกุลเงินคือ USD
ค่าเวลา (timestamp) ใช้รูปแบบ RFC 3339 (2026-08-21T00:24:45.255Z)
ข้อผิดพลาด
เมื่อคำขอล้มเหลว ระบบจะส่งโค้ดสถานะ HTTP จริง พร้อม body ที่มีออบเจ็กต์ error หนึ่งตัว โค้ดของคุณควรตัดสินใจจาก error.code เพราะค่านี้คงที่และไม่เปลี่ยนตามภาษา
HTTP/1.1 400 Bad Request
Content-Type: application/json
X-Request-Id: req_0c858d8af7f65eca001b2f5a
{
"object": "error",
"error": {
"type": "invalid_request_error",
"code": "quantity_out_of_range",
"message": "Miktar, bu servisin izin verdiği aralığın dışında.",
"param": "quantity",
"doc_url": "https://panelfollows.com/api-docs#error-quantity_out_of_range",
"request_id": "req_0c858d8af7f65eca001b2f5a"
}
}| type | ประเภทกว้าง ๆ: ลองใหม่ได้หรือไม่ และเป็นความผิดพลาดฝั่งคุณหรือเปล่า |
| code | ค่าคงที่สำหรับโปรแกรม ใช้ค่านี้ตัดสินใจในโค้ด |
| message | ข้อความสำหรับคนอ่าน ในภาษาที่คุณเลือก |
| param | ชื่อฟิลด์ที่มีปัญหา (ถ้ามี) |
| doc_url | ลิงก์ไปยังหัวข้อที่ตรงกันในเอกสารนี้ |
| request_id | รหัสอ้างอิงเดียวที่ต้องแจ้งเมื่อติดต่อทีมซัพพอร์ต |
ขีดจำกัดคำขอ (Rate limit)
จำกัด 600 คำขอต่อนาทีต่อคีย์ และ 900 คำขอต่อนาทีต่อ IP ทุกการตอบกลับมี header RateLimit-Limit / RateLimit-Remaining และ RateLimit-Reset เพื่อให้คุณชะลอความถี่ลงได้ก่อนชนขีดจำกัด หากเกินขีดจำกัดจะได้รับโค้ด 429 พร้อม header Retry-After
การแบ่งหน้า (Pagination)
รายการใช้การแบ่งหน้าแบบ cursor ส่ง limit เพื่อกำหนดขนาดหน้า (สูงสุด 500) และส่ง starting_after เป็น id ของรายการสุดท้ายในหน้าก่อนหน้า เรียกต่อไปเรื่อย ๆ จนกว่า has_more จะเป็น false โดย next_cursor คือ cursor สำหรับการเรียกครั้งถัดไป หาก limit อยู่นอกช่วง ระบบจะไม่ปรับค่าให้เองแบบเงียบ ๆ แต่จะตอบกลับเป็นข้อผิดพลาด เพราะการปรับค่าแบบเงียบ ๆ ทำให้ไคลเอนต์เข้าใจผิดว่าดึงข้อมูลมาครบแล้ว
กันคำสั่งซื้อซ้ำ (Idempotency-Key)
ใส่ header Idempotency-Key ที่เป็นค่าสุ่มเมื่อสร้างคำสั่งซื้อ หากการเชื่อมต่อหลุดแล้วคุณส่งซ้ำด้วยคีย์เดิม ระบบจะไม่สร้างคำสั่งซื้อที่สอง แต่จะส่งการตอบกลับครั้งแรกกลับมาอีกครั้งพร้อม header Idempotent-Replay: true ระบบเก็บบันทึกไว้ 24 ชั่วโมง
หากส่งคีย์เดิมพร้อม body ที่ต่างออกไป จะได้ 409 idempotency_key_reuse ซึ่งแทบทุกครั้งหมายความว่าการสร้างคีย์ฝั่งไคลเอนต์มีปัญหา คำขอที่ล้มเหลวไม่ได้ทำให้คีย์นั้นใช้ไม่ได้ แก้ปัญหาแล้วส่งใหม่ด้วยคีย์เดิมได้เลย
การสร้างคำสั่งซื้อ
curl -X POST https://panelfollows.com/api/v3/orders \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8f14e45f-ea3a-4b1c-9c1e-2b0d5c6a7e91" \
-d '{
"service": 1234,
"link": "https://instagram.com/username",
"quantity": 1000
}'สคีมาฟิลด์ของบริการ
ทุกบริการเผยแพร่ฟิลด์ที่ต้องใช้ไว้ในอาร์เรย์ fields ได้แก่ ชื่อ ประเภท จำเป็นหรือไม่ ขีดจำกัด รวมถึงป้ายชื่อและคำอธิบายในภาษาของคุณ หากฟิลด์ใดมี determines_quantity เป็น true จำนวนจะคำนวณจากจำนวนบรรทัดในฟิลด์นั้น
// Servisin kendi alan şemasından formu OTOMATİK üretmek:
// hiçbir servis tipini koda gömmeniz gerekmez.
const res = await fetch("https://panelfollows.com/api/v3/services/1234", {
headers: { Authorization: "Bearer YOUR_API_KEY", "Accept-Language": "tr" },
});
const service = await res.json();
for (const field of service.fields) {
renderInput({
name: field.name,
label: field.label, // kullanıcının dilinde
hint: field.description, // kullanıcının dilinde
required: field.required,
type: field.type, // url | integer | string | text_lines
min: field.min,
max: field.max,
// true ise miktarı bu alanın satır sayısı belirler
countsLines: field.determines_quantity === true,
});
}ออบเจ็กต์บริการ (service)
{
"object": "service",
"id": 1234,
"name": "Instagram Takipçi | Türk | 30 gün telafi",
"description": "Gerçek hesaplardan Türk takipçi. Başlangıç 0-1 saat.",
"type": "default",
"platform": "instagram",
"category": { "slug": "instagram-takipci", "name": "Instagram Takipçi" },
"pricing": {
"rate": "1.2340",
"currency": "USD",
"unit": "per_1000",
"unit_note": "Fiyat 1000 adet içindir."
},
"limits": { "min": 100, "max": 100000 },
"features": { "refill": true, "cancel": false, "dripfeed": true },
"average_time_seconds": 4320,
"fields": [
{
"name": "link",
"type": "url",
"required": true,
"label": "Bağlantı",
"description": "Gönderimin yapılacağı profilin herkese açık adresi."
},
{
"name": "quantity",
"type": "integer",
"required": true,
"label": "Miktar",
"description": "Kaç adet gönderileceği.",
"min": 100,
"max": 100000
}
],
"is_active": true,
"updated_at": "2026-08-20T09:15:00.000Z"
}ดูตัวอย่างคำสั่งซื้อ (Dry run)
POST /orders/preview ตรวจสอบคำสั่งซื้อและคำนวณยอดที่จะเรียกเก็บโดยไม่สร้างคำสั่งซื้อจริง ใช้แสดงราคาให้ลูกค้าเห็น และเช็กล่วงหน้าว่ายอดเงินคงเหลือพอหรือไม่ ระบบจะไม่ตัดเงินและไม่ติดต่อผู้ให้บริการใด ๆ
สั่งซื้อหลายรายการในครั้งเดียว (Batch)
POST /orders/batch รับคำสั่งซื้อได้สูงสุด 50 รายการต่อการเรียกหนึ่งครั้ง ระบบประมวลผลตามลำดับ และแต่ละรายการรายงานผลของตัวเอง หากรายการหนึ่งล้มเหลว รายการที่เหลือก็ยังสร้างตามปกติ และคุณจะเห็นชัดเจนว่ารายการไหนล้มเหลวเพราะอะไร
แนบออบเจ็กต์ที่เกี่ยวข้องมาในการตอบกลับ
ส่ง include=service ใน Endpoint ของคำสั่งซื้อ แล้วออบเจ็กต์บริการจะแนบมาในการตอบกลับด้วย ไม่ต้องส่งคำขอที่สอง
คุณภาพบริการและรายการบริการที่ดีที่สุด
ทุกชั่วโมงระบบจะวัดผลแต่ละบริการ: คำสั่งซื้อของเราเองจบลงอย่างไร (เสร็จสิ้น ยกเลิก ค้าง หรือถูกปฏิเสธ) ลูกค้าขอเติมยอดหรือเปิดทิกเก็ตบ่อยแค่ไหน การส่งงานใช้เวลาจริงเท่าไร และต้นทางยังมีบริการนี้อยู่หรือไม่ GET /services/top นำผลการวัดเหล่านี้มาจัดเป็นรายการคัดสรรตามอันดับ ส่วน include=quality จะแนบรายงานเดียวกันเข้ากับออบเจ็กต์บริการใดก็ได้
คะแนน (0-100) คำนวณแบบถ่วงน้ำหนักจาก 5 องค์ประกอบ: ความน่าเชื่อถือ (42%: อัตราสำเร็จโดยประมาณ) ความพึงพอใจ (14%: คิดจากอัตราการร้องเรียน) ความเร็ว (28%: คิดแบบลอการิทึมจากเวลาส่งงานที่ใช้คำนวณ (effective delivery time) โดย 5 นาทีได้คะแนนเต็ม และ 48 ชั่วโมงได้ 0) คะแนนจากระบบตรวจสุขภาพบริการเอง (8%) และปริมาณหลักฐาน (8%) มีโบนัสเล็กน้อยให้บริการที่รับประกันการเติมยอด มาจากต้นทางที่เชื่อถือได้ และอยู่ในแคตตาล็อกมานาน บริการที่อยู่ระหว่างตรวจสอบ ถูกระบบตรวจสุขภาพติดธงไว้ หรือมาจากต้นทางที่อยู่ในช่วงทดลอง (probation) ยังได้รับการคำนวณคะแนน แต่จะไม่ถูกนำไปจัดอันดับ
กรองตามแพลตฟอร์ม slug ของหมวดหมู่ หรือ shelf (เช่น followers / likes / views ซึ่งใช้ค่าเดียวกันทุกแพลตฟอร์ม) เรียงตามคะแนน ความเร็ว ความน่าเชื่อถือ ราคา หรือจำนวนคำสั่งซื้อ และใช้ group_by=category (หรือ platform) คู่กับ per_group เพื่อรับรายการคัดสรรแยกตามหมวดหมู่ได้ในการเรียกครั้งเดียว ตรงกับสิ่งที่หน้าร้านต้องใช้เพื่อติดป้าย 'แนะนำ' ให้บริการในทุกหมวดหมู่
# Instagram takipçi kategorisinde en iyi 5 servis
curl "https://panelfollows.com/api/v3/services/top?category=instagram-followers&limit=5" -H "Authorization: Bearer YOUR_API_KEY"
# Her kategori için kanıtlı en iyi 3 servis (tek istek)
curl "https://panelfollows.com/api/v3/services/top?group_by=category&per_group=3&min_confidence=medium" -H "Authorization: Bearer YOUR_API_KEY"| score / grade | 0-100 และ A (85 ขึ้นไป) B (70 ขึ้นไป) C (55 ขึ้นไป) หรือ D |
| confidence | none / low / medium / high: บอกว่ามีคำสั่งซื้อของเราเองรองรับคะแนนนี้มากน้อยแค่ไหน |
| badges | proven (คำสั่งซื้อ 5 รายการขึ้นไป และขอบล่างของอัตราสำเร็จ 60% ขึ้นไป) popular (คำสั่งซื้อ 20 รายการขึ้นไป) fast (ส่งงานภายใน 1 ชั่วโมง) trusted_source (ต้นทางที่เชื่อถือได้) new (อยู่ในแคตตาล็อกไม่ถึง 14 วัน) |
| components | reliability / satisfaction / speed / health / evidence แต่ละค่าอยู่ระหว่าง 0-1 |
| evidence.basis | service_orders (คำสั่งซื้อของบริการนั้นเอง) peer_services (เริ่มจากบริการอื่นของต้นทางเดียวกัน) หรือ none |
| evidence.delivery_source | measured (ค่ามัธยฐานที่เราวัดเองจากคำสั่งซื้อที่เสร็จสิ้นตั้งแต่ 3 รายการขึ้นไป) หรือ claimed (เวลาที่ต้นทางประกาศไว้ ซึ่งระบบจะหักคะแนนและจำกัดเพดานไว้) |
| measured_at | เวลาที่ระบบตรวจสุขภาพวัดบริการนี้ครั้งล่าสุด คะแนนอัปเดตทุกชั่วโมง |
รายการ Endpoint
| เมธอด | Endpoint | รายละเอียด |
|---|---|---|
| GET | /api/v3 | Discovery document: version, endpoints, limits and event types. |
| GET | /api/v3/openapi.json | OpenAPI 3.1 schema for this API. |
| GET | /api/v3/account | Account balance, currency and current rate-limit window. |
| PATCH | /api/v3/account | Set the low balance alert threshold that triggers account.low_balance. |
| GET | /api/v3/services | List services with filters and cursor pagination. |
| GET | /api/v3/services/top | Best services ranked by the quality score; optionally one shortlist per category or platform. |
| GET | /api/v3/services/{id} | Retrieve one service, including its order field schema. |
| GET | /api/v3/categories | List categories with platform, shelf and active service counts. |
| GET | /api/v3/platforms | List platform keys usable as the ?platform= filter, with counts. |
| POST | /api/v3/orders | Create an order. Supports the Idempotency-Key header. |
| POST | /api/v3/orders/preview | Validate an order and compute its charge without creating it. |
| POST | /api/v3/orders/batch | Create up to 50 orders in one call; each item reports its own result. |
| GET | /api/v3/orders | List your orders, newest first. |
| GET | /api/v3/orders/{id} | Retrieve one order. |
| POST | /api/v3/orders/{id}/cancel | Request cancellation. Only for services whose features.cancel is true. |
| POST | /api/v3/orders/{id}/refill | Request a refill for a completed order. |
| GET | /api/v3/refills | List your refill requests, newest first. |
| GET | /api/v3/refills/{id} | Retrieve one refill; refreshes its status from the provider. |
| GET | /api/v3/events | Read your event stream oldest-first; the polling alternative to webhooks. |
| GET | /api/v3/webhooks | List your webhook endpoints. |
| POST | /api/v3/webhooks | Register a webhook endpoint. The signing secret is returned once. |
| GET | /api/v3/webhooks/{id} | Retrieve one webhook endpoint. |
| PATCH | /api/v3/webhooks/{id} | Update a webhook endpoint's url, events, description or active state. |
| DELETE | /api/v3/webhooks/{id} | Delete a webhook endpoint and its delivery log. |
| POST | /api/v3/webhooks/{id}/test | Send a test event to this endpoint, ignoring its event filter. |
| POST | /api/v3/webhooks/{id}/rotate_secret | Generate a new signing secret. The old one stops working immediately. |
| GET | /api/v3/webhooks/{id}/deliveries | Delivery log for one endpoint: attempts, response codes and errors. |
สคีมา OpenAPI
ข้อกำหนดของทุก Endpoint ในรูปแบบที่เครื่องอ่านได้ นำไฟล์นี้ไปใช้กับตัวสร้างไคลเอนต์ (openapi-generator หรือ Kiota) หรือ Postman แล้วจะได้ไคลเอนต์พร้อมใช้ในภาษาโปรแกรมที่คุณใช้อยู่
https://panelfollows.com/api/v3/openapi.jsonคำถามที่พบบ่อย
คีย์เดิมใช้กับ v3 ได้ไหม?
ได้ ไม่ต้องสร้างคีย์ใหม่เพื่อทดลองใช้ แต่ในระบบใช้งานจริงควรเปลี่ยนมาใช้คีย์ v3 เพราะเพิกถอนได้และไม่มีการเก็บเป็นข้อความธรรมดา
ทำไมราคาถึงเป็นสตริง?
ตัวเลขแบบ floating point ทำให้เศษของจำนวนเงินทศนิยมคลาดเคลื่อน การส่งค่าเป็นสตริงแล้วให้ฝั่งคุณแปลงเป็นชนิดข้อมูลทศนิยม (decimal) ช่วยตัดปัญหาการปัดเศษไม่ตรงกันแบบนี้ออกไปทั้งหมด
ทำไมฟิลด์ unit ถึงสำคัญ?
บริการส่วนใหญ่คิดราคาต่อ 1,000 หน่วย (per_1000) แต่บริการแบบแพ็กเกจขายเป็นชิ้นเดียว (per_order) โดย rate ครอบคลุมทั้งแพ็กเกจ การเชื่อมต่อที่ไม่แยกสองกรณีนี้เคยคำนวณราคาแพ็กเกจผิดไป 1,000 เท่า
สร้างคำสั่งซื้อแล้วแต่สถานะค้างอยู่ที่ pending เกิดอะไรขึ้น?
การส่งต่อไปยังผู้ให้บริการอาจล่าช้า ระบบกันยอดเงินไว้แล้วและคำสั่งซื้อไม่ได้หายไปไหน ทีมงานของเราจะส่งใหม่ให้โดยอัตโนมัติ ฟิลด์ processing_delayed ใช้บอกสถานะนี้
ทุกบริการรองรับการยกเลิกและการเติมยอดไหม?
ไม่ใช่ทุกบริการ ตรวจสอบ features.cancel และ features.refill ในออบเจ็กต์บริการ หากเรียก Endpoint ยกเลิกหรือเติมยอดกับบริการที่ไม่รองรับ จะได้โค้ด 400
ใช้ API ทั้งสองชุดพร้อมกันได้ไหม?
ได้ บัญชีเดียวกัน ยอดเงินเดียวกัน คำสั่งซื้อชุดเดียวกัน คำสั่งซื้อที่สั่งผ่าน v2 อ่านผ่าน v3 ได้
API มาตรฐานสำหรับตัวแทนจำหน่ายแบบคลาสสิกที่ใช้กันทั่ววงการ และเป็นรูปแบบที่ซอฟต์แวร์พาเนลสำเร็จรูปต้องการ
Endpoint
POST https://panelfollows.com/api/v2
POST https://panelfollows.com/api/v2/trการยืนยันตัวตน
ทุกคำขอต้องมีพารามิเตอร์ key เก็บคีย์เป็นความลับ และสร้างคีย์ใหม่ทันทีหากคีย์รั่วไหล
รูปแบบคำขอและการตอบกลับ
ส่งคำขอด้วย POST แบบฟอร์ม (application/x-www-form-urlencoded) และการตอบกลับเป็น JSON แม้คำขอล้มเหลวก็ยังได้ HTTP 200 โดยมี { "error": "..." } อยู่ใน body
รองรับอยู่แล้วแต่ไม่เคยระบุในเอกสารมาก่อน: เรียกด้วย GET ได้ และส่ง body เป็น application/json ได้
ขีดจำกัดคำขอ (Rate limit)
จำกัด 240 คำขอต่อนาทีต่อคีย์ และ 300 คำขอต่อนาทีต่อ IP ระบบจะปฏิเสธคำขอที่เกินด้วยโค้ด 429
Action และพารามิเตอร์
| action | พารามิเตอร์ | รายละเอียด |
|---|---|---|
| services | key, action | แสดงรายการบริการที่เปิดใช้งานทั้งหมด (id ชื่อ หมวดหมู่ rate ขั้นต่ำ/สูงสุด เติมยอด ยกเลิก และทยอยส่ง) |
| add | key, action, service, link, quantity[, runs, interval, comments, username, posts, min, max] | สร้างคำสั่งซื้อ service คือ id ของบริการในแคตตาล็อก หากต้องการทยอยส่ง (Drip-feed) ให้เพิ่ม runs และ interval ส่วนบริการประเภทพิเศษให้ส่งฟิลด์ที่ตรงกับประเภทนั้น |
| status | key, action, order | orders | สถานะคำสั่งซื้อ ใช้ order สำหรับรายการเดียว หรือใช้ orders ที่คั่นด้วยเครื่องหมายจุลภาคสำหรับหลายรายการ |
| balance | key, action | ยอดเงินคงเหลือและสกุลเงินของบัญชี |
| refill | key, action, order | orders | สร้างคำขอเติมยอด ส่งตรงไปยังผู้ให้บริการ |
| refill_status | key, action, refill | refills | ตรวจสอบสถานะการเติมยอด |
| cancel | key, action, orders | ยกเลิกคำสั่งซื้อ ใช้ได้เฉพาะบริการที่ผู้ให้บริการรองรับการยกเลิก |
ตัวอย่าง
curl -X POST https://panelfollows.com/api/v2 \
-d "key=YOUR_API_KEY" \
-d "action=add" \
-d "service=1234" \
-d "link=https://instagram.com/username" \
-d "quantity=1000"
# Yanıt: { "order": 23501 }การตอบกลับเป็นภาษาตุรกี
เติม /tr ต่อท้าย URL เพื่อรับชื่อบริการ หมวดหมู่ สถานะคำสั่งซื้อ และข้อความแสดงข้อผิดพลาดเป็นภาษาตุรกี พารามิเตอร์ action และโครงสร้างการตอบกลับเหมือนเดิมทุกอย่าง และคีย์ของคุณใช้ได้กับทั้งสอง URL ฟิลด์ทางเทคนิค (type / refill_status / currency) ยังคงเป็นภาษาอังกฤษเพื่อให้เข้ากันได้กับมาตรฐาน
ย้ายไปใช้ v3
การย้ายเป็นทางเลือก ไม่บังคับ หากตัดสินใจย้าย ตรรกะทางธุรกิจส่วนใหญ่ใช้ต่อได้ เพราะชื่อพารามิเตอร์ไม่เปลี่ยน สิ่งที่ต่างคือช่องทางรับส่งข้อมูลและวิธีอ่านข้อผิดพลาด
- 1ย้ายคีย์จากฟิลด์ key ใน body ไปไว้ใน header Authorization: Bearer
- 2เรียกพาธของทรัพยากรแทน action=... (POST /orders แทน add)
- 3ตรวจความล้มเหลวจากสถานะ HTTP และ error.code แทนการเช็กว่า "มีฟิลด์ error หรือไม่"
- 4เทียบสถานะคำสั่งซื้อกับค่าสำหรับเครื่อง ไม่ใช่ข้อความที่แสดงผล
- 5เพิ่ม Idempotency-Key ตอนสร้างคำสั่งซื้อ
- 6เปลี่ยนจากการเช็กสถานะซ้ำ ๆ (polling) มาใช้ Webhook
คีย์ให้สิทธิ์เข้าถึงบัญชีของคุณได้ทั้งหมด อย่าแชร์ให้ใคร อย่าฝังไว้ในโค้ดฝั่งไคลเอนต์ และอย่า commit ขึ้น repository สาธารณะเด็ดขาด
คีย์ v3
สร้างคีย์ได้ตามต้องการ ตั้งชื่อกำกับแต่ละคีย์ และเพิกถอนแยกกันได้ ฝั่งเราเก็บไว้เพียงค่าแฮช (cryptographic digest) ของคีย์เท่านั้น
สร้างบัญชีฟรีคีย์เดิม
คีย์เดียวที่ใช้กับ API สำหรับตัวแทนจำหน่ายแบบคลาสสิก (v2) และใช้กับ v3 ได้ด้วย เมื่อสร้างคีย์ใหม่ ค่าเดิมจะใช้ไม่ได้ทันที
ความปลอดภัย
- เก็บคีย์ไว้ในตัวแปรสภาพแวดล้อม (environment variable) ไม่ใช่ในซอร์สโค้ด
- อย่าใส่คีย์ในโค้ดที่รันบนเบราว์เซอร์ ให้ส่งคำขอผ่านเซิร์ฟเวอร์ของคุณเองแทน
- สร้างคีย์แยกสำหรับแต่ละระบบ เพื่อให้การเพิกถอนคีย์หนึ่งไม่กระทบระบบอื่น
- หากสงสัยว่าคีย์รั่วไหล ให้นำคีย์ใหม่ไปติดตั้งใช้งานก่อน แล้วจึงเพิกถอนคีย์เดิม
เมื่อสถานะคำสั่งซื้อเปลี่ยน เราจะส่งการแจ้งเตือนที่มีลายเซ็นไปยังเซิร์ฟเวอร์ของคุณ จึงไม่ต้องคอยเช็กสถานะเอง
ทำไมต้องใช้ Webhook?
การเช็กสถานะซ้ำ ๆ (polling) ทั้งช้าและสิ้นเปลือง การถามสถานะคำสั่งซื้อหลายพันรายการทุกนาทีทำให้ขีดจำกัดคำขอหมดเร็ว และคุณก็ยังรู้ความเปลี่ยนแปลงช้าไปหลายนาทีอยู่ดี ด้วย Webhook ความเปลี่ยนแปลงจะมาถึงคุณทันทีที่เกิดขึ้น
การตั้งค่า
- 1เตรียม URL แบบ https ที่เข้าถึงได้สาธารณะ (ระบบไม่รับที่อยู่ในเครือข่ายภายในหรือเครือข่ายส่วนตัว)
- 2เพิ่ม URL ด้านล่าง แล้วเก็บรหัสลับสำหรับลงลายเซ็น (signing secret) ที่แสดงเพียงครั้งเดียว
- 3ตรวจสอบลายเซ็นฝั่งคุณ แล้วตอบกลับด้วย 2xx
- 4ใช้ปุ่มทดสอบเพื่อยืนยันว่าเส้นทางทั้งหมดทำงานครบตั้งแต่ต้นจนจบ
ข้อมูลที่เราส่ง
POST /hooks/pf HTTP/1.1
Content-Type: application/json
Webhook-Id: evt_7f910fba7cd042ef9d9069ba5c074fa0
Webhook-Timestamp: 1787261223
Webhook-Signature: t=1787261223,v1=9c1e2b0d5c6a7e91...
{
"object": "event",
"id": "evt_7f910fba7cd042ef9d9069ba5c074fa0",
"type": "order.completed",
"created_at": "2026-08-21T00:27:03.531Z",
"data": {
"previous_status": "in_progress",
"order": {
"object": "order",
"id": 23501,
"status": "completed",
"status_label": "Tamamlandı",
"service": 1234,
"quantity": 1000,
"start_count": 4210,
"remains": 0,
"charge": "1.2340",
"currency": "USD"
}
}
}การตรวจสอบลายเซ็น
ทุกคำขอมี header Webhook-Signature โดย t คือ timestamp และ v1 คือลายเซ็น ลายเซ็นคือค่า HMAC-SHA256 ของสตริง "<timestamp>.<raw body>" ที่คำนวณด้วยรหัสลับของคุณ
- 1แยกค่า t และ v1 ออกจาก header
- 2ตรวจว่า t เก่าไม่เกิน 5 นาที เพื่อป้องกันการส่งซ้ำ (replay)
- 3คำนวณ HMAC-SHA256 ของ "<t>.<raw body>" ด้วยรหัสลับของคุณ
- 4เปรียบเทียบกับ v1 แบบ constant time และปฏิเสธคำขอหากไม่ตรงกัน
ตัวอย่างการตรวจสอบ
import crypto from "node:crypto";
import express from "express";
const app = express();
// ÖNEMLİ: imza HAM gövde üzerinden hesaplanır. JSON'a çevirip yeniden
// dizeye dönüştürürseniz boşluklar değişir ve imza tutmaz.
app.post("/hooks/pf", express.raw({ type: "application/json" }), (req, res) => {
const raw = req.body.toString("utf8");
const header = req.get("Webhook-Signature") ?? "";
const m = /t=(\d+),v1=([0-9a-f]+)/.exec(header);
if (!m) return res.sendStatus(400);
const [, timestamp, signature] = m;
// Tekrar saldırısına karşı: 5 dakikadan eski damgayı reddet.
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.sendStatus(400);
const expected = crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(`${timestamp}.${raw}`, "utf8")
.digest("hex");
const ok =
expected.length === signature.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
if (!ok) return res.sendStatus(401);
const event = JSON.parse(raw);
// 2xx dönmezseniz gönderim artan aralıklarla tekrar denenir.
res.sendStatus(200);
if (event.type === "order.completed") {
// ... siparişi kendi sisteminizde tamamlandı olarak işaretleyin
}
});การส่งซ้ำ
ระบบส่งครั้งแรกทันทีที่เกิดเหตุการณ์ หากการตอบกลับไม่ใช่ 2xx หรือเชื่อมต่อไม่สำเร็จ ระบบจะส่งซ้ำหลังจาก 1 นาที 5 นาที 30 นาที 2 ชั่วโมง และ 6 ชั่วโมง ครบ 6 ครั้งแล้วยังไม่สำเร็จ ระบบจะทำเครื่องหมายการส่งนั้นว่าล้มเหลวและแสดงไว้ในบันทึกการส่ง
ประเภทเหตุการณ์
เลือกรับเฉพาะประเภทที่สนใจ หรือรับทั้งหมดก็ได้ การเปลี่ยนสถานะหนึ่งครั้งสร้างเหตุการณ์เพียงหนึ่งรายการ โดยใช้ประเภทที่ตรงกับสถานะใหม่มากที่สุด
| order.created | สร้างคำสั่งซื้อแล้ว |
| order.processing | ผู้ให้บริการเริ่มดำเนินการคำสั่งซื้อแล้ว |
| order.completed | คำสั่งซื้อเสร็จสิ้น |
| order.partial | ส่งงานได้บางส่วน และคืนเงินส่วนที่เหลือแล้ว |
| order.canceled | ยกเลิกหรือคืนเงินคำสั่งซื้อแล้ว |
| order.updated | สถานะเปลี่ยนไปในรูปแบบอื่น |
| refill.created | มีคำขอเติมยอดใหม่ |
| refill.updated | สถานะของคำขอเติมยอดเปลี่ยนไป |
| account.low_balance | ยอดเงินคงเหลือต่ำกว่าเกณฑ์ที่ตั้งไว้ด้วย PATCH /account เหตุการณ์นี้เกิดตอนที่ยอดเงินลดลงผ่านเกณฑ์ ไม่ได้เกิดทุกคำสั่งซื้อ และจะพร้อมแจ้งอีกครั้งเมื่อยอดเงินกลับขึ้นไปสูงกว่าเกณฑ์ |
หากโฮสต์ Webhook ไม่ได้
อ่านเหตุการณ์ชุดเดียวกันได้ด้วย cursor จาก GET /api/v3/events เหมาะสำหรับตอนพัฒนาบนเครื่องตัวเอง ตอนที่ไม่มี IP คงที่ หรือเมื่ออยู่หลังไฟร์วอลล์
// Webhook kuramıyorsanız (yerelde geliştirme, sabit IP yok) aynı bilgiyi
// imleçle çekebilirsiniz. İmleci kendi tarafınızda saklayın.
let cursor = loadCursor(); // en son işlediğiniz olayın "cursor" değeri
const url = new URL("https://panelfollows.com/api/v3/events");
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("starting_after", String(cursor));
const res = await fetch(url, { headers: { Authorization: "Bearer YOUR_API_KEY" } });
const { data, has_more } = await res.json();
for (const event of data) {
handle(event); // sizin işleyiciniz
cursor = event.cursor; // imleci ilerlet
}
saveCursor(cursor);ข้อผิดพลาด (48)
| โค้ด | สถานะ | รายละเอียด |
|---|---|---|
| missing_api_key | 401 | ไม่พบคีย์ API กรุณาส่งในรูปแบบ 'Authorization: Bearer <key>' |
| invalid_api_key | 401 | คีย์ API ที่ส่งมาไม่ถูกต้อง |
| revoked_api_key | 401 | คีย์ API นี้ถูกเพิกถอนแล้ว ใช้งานต่อไม่ได้ |
| account_banned | 403 | บัญชีนี้ถูกแบน |
| account_suspended | 403 | บัญชีนี้ถูกระงับการใช้งาน |
| insufficient_scope | 403 | คีย์ API นี้ไม่มีสิทธิ์เรียกใช้ Endpoint นี้ |
| invalid_json | 400 | body ของคำขอไม่ใช่ JSON ที่ถูกต้อง |
| unsupported_content_type | 415 | ไม่รองรับ Content-Type นี้ กรุณาใช้ application/json หรือ application/x-www-form-urlencoded |
| method_not_allowed | 405 | Endpoint นี้ไม่รองรับ HTTP method นี้ |
| payload_too_large | 413 | body ของคำขอมีขนาดใหญ่เกินไป |
| missing_parameter | 400 | ไม่พบพารามิเตอร์ที่จำเป็น |
| invalid_parameter | 400 | พารามิเตอร์มีค่าไม่ถูกต้อง |
| invalid_link | 400 | ไม่พบลิงก์ หรือลิงก์ไม่ใช่ URL แบบ http(s) ที่ถูกต้อง |
| invalid_quantity | 400 | จำนวนต้องเป็นจำนวนเต็มบวก |
| quantity_out_of_range | 400 | จำนวนไม่อยู่ในช่วงที่บริการนี้กำหนด |
| invalid_comments | 400 | ฟิลด์คอมเมนต์ว่างอยู่ หรือมีจำนวนบรรทัดมากเกินไป |
| invalid_username | 400 | ชื่อผู้ใช้ไม่ถูกต้องสำหรับบริการนี้ |
| invalid_subscription | 400 | พารามิเตอร์ของบริการอัตโนมัติไม่ถูกต้อง |
| invalid_runs | 400 | ค่า 'runs' ไม่ถูกต้องสำหรับการทยอยส่ง (Drip-feed) |
| invalid_interval | 400 | ค่า 'interval' ไม่ถูกต้องสำหรับการทยอยส่ง (Drip-feed) |
| dripfeed_not_supported | 400 | บริการนี้ไม่รองรับการทยอยส่ง (Drip-feed) |
| missing_required_field | 400 | ไม่พบฟิลด์ที่บริการประเภทนี้กำหนดให้ต้องใส่ หรือฟิลด์นั้นมีค่าไม่ถูกต้อง |
| service_inactive | 400 | บริการนี้ไม่เปิดให้สั่งซื้อในขณะนี้ |
| invalid_cursor | 400 | cursor สำหรับแบ่งหน้าไม่ถูกต้อง |
| invalid_limit | 400 | พารามิเตอร์ 'limit' อยู่นอกช่วงที่อนุญาต |
| invalid_webhook_url | 400 | URL ของ Webhook ต้องเป็นที่อยู่ https:// ที่เข้าถึงได้สาธารณะ |
| invalid_events | 400 | มีประเภทเหตุการณ์ที่ระบบไม่รู้จักอย่างน้อยหนึ่งรายการ |
| batch_too_large | 400 | คำขอแบบ batch มีรายการมากเกินไป |
| cancel_not_supported | 400 | บริการนี้ไม่รองรับการยกเลิก |
| refill_not_supported | 400 | บริการนี้ไม่มีการเติมยอด |
| unknown_endpoint | 404 | ไม่พบ Endpoint นี้ ดูเส้นทางที่ใช้ได้ในเอกสาร API |
| service_not_found | 404 | ไม่พบบริการที่มี id นี้ |
| order_not_found | 404 | ไม่พบคำสั่งซื้อที่มี id นี้ในบัญชีของคุณ |
| refill_not_found | 404 | ไม่พบคำขอเติมยอดที่มี id นี้ในบัญชีของคุณ |
| webhook_not_found | 404 | ไม่พบ Endpoint ของ Webhook ที่มี id นี้ในบัญชีของคุณ |
| order_not_cancelable | 409 | คำสั่งซื้อนี้ยกเลิกไม่ได้แล้ว เนื่องจากสถานะปัจจุบัน |
| cancel_rejected | 409 | ผู้ให้บริการปฏิเสธคำขอยกเลิก |
| order_not_completed | 409 | ขอเติมยอดได้เฉพาะคำสั่งซื้อที่เสร็จสิ้นแล้ว |
| duplicate_link | 409 | มีคำสั่งซื้อที่ยังดำเนินการอยู่สำหรับลิงก์นี้ กรุณารอจนเสร็จสิ้น |
| idempotency_key_reuse | 409 | Idempotency-Key นี้เคยใช้กับคำขอที่มี body ต่างออกไปแล้ว |
| idempotency_in_progress | 409 | คำขอที่ใช้ Idempotency-Key นี้ยังประมวลผลอยู่ กรุณาลองใหม่ในอีกสักครู่ |
| webhook_limit_reached | 409 | มี Endpoint ของ Webhook ครบจำนวนสูงสุดแล้ว |
| insufficient_balance | 402 | ยอดเงินไม่พอสำหรับคำสั่งซื้อนี้ |
| rate_limit_exceeded | 429 | เกินขีดจำกัดคำขอ กรุณาดู header Retry-After ในการตอบกลับ |
| provider_error | 502 | ผู้ให้บริการต้นทางส่งข้อผิดพลาดกลับมา กรุณาลองอีกครั้ง |
| refill_failed | 502 | ผู้ให้บริการปฏิเสธคำขอเติมยอด |
| service_temporarily_unavailable | 503 | บริการนี้ไม่พร้อมใช้งานชั่วคราว กรุณาลองใหม่ภายหลัง |
| internal_error | 500 | เกิดข้อผิดพลาดที่ไม่คาดคิดในระบบของเรา |