เอกสาร API
ดึงรายชื่อซีรีส์แนวตั้งพากย์ไทยและบรรยายไทย พร้อมลิงก์วิดีโอทุกตอน ไปแสดงบนเว็บหรือแอปของคุณ
เริ่มใช้งาน
- สมัครสมาชิก แล้วคัดลอก key จากแดชบอร์ด key ขึ้นต้นด้วย
rnj_ระบบแสดงเต็มครั้งเดียว ทำหายออกใหม่ได้ที่แดชบอร์ด - เลือกแพ็กเกจ เพื่อเปิดใช้ key และขอลิงก์วิดีโอได้ ก่อนชำระเงิน key ยังเรียก API ไม่ได้
- ดึงรายชื่อเรื่องด้วย /series เก็บลงฐานของคุณ แล้วตามของใหม่เป็นระยะด้วย updated_since
- ตอนผู้ชมกดเล่น ให้เซิร์ฟเวอร์ของคุณขอลิงก์ด้วย /play หรือ /links แล้วส่งลิงก์ให้
<video>
ลองเรียกทุก endpoint ได้จากหน้า ทดสอบ API ในแดชบอร์ด การดูรายการที่นั่นไม่นับ req
ส่ง key
ที่อยู่ API คือ https://api.nungindy.com/v1 ทุกคำขอเป็น GET และตอบเป็น JSON (UTF-8) ส่ง key ใน header X-API-Key หรือ Authorization: Bearer ระบบไม่รับ key ใน query string
curl -H "X-API-Key: rnj_…" "https://api.nungindy.com/v1/series?per_page=5"
PHP
$ch = curl_init("https://api.nungindy.com/v1/series?provider=netshort");
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 20,
CURLOPT_HTTPHEADER => ["X-API-Key: " . getenv("RENTAPI_KEY")]]);
$data = json_decode(curl_exec($ch), true);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
Node.js
const r = await fetch("https://api.nungindy.com/v1/series?provider=netshort", {
headers: { "X-API-Key": process.env.RENTAPI_KEY } });
const data = await r.json();
if (!r.ok) throw new Error(data.error);
Python
import os, requests
r = requests.get("https://api.nungindy.com/v1/series", params={"provider": "netshort"},
headers={"X-API-Key": os.environ["RENTAPI_KEY"]}, timeout=20)
data = r.json()
เก็บ key ไว้ที่เซิร์ฟเวอร์เท่านั้น ถ้าใส่ในโค้ดหน้าเว็บ ผู้ชมทุกคนจะเห็นและเอาไปใช้แทนคุณได้
เพดานการใช้งาน
ทุกคำขอนับ 1 req เท่ากัน ไม่ว่าจะเป็นรายชื่อเรื่อง รายละเอียด หรือลิงก์วิดีโอ /links ได้ลิงก์ทั้งเรื่องในคำขอเดียว จึงนับแค่ 1 req เหมือนกัน แต่ละ key มีเพดาน 3 อย่างกับวันหมดอายุ
| เพดานต่อ key | เริ่มต้น |
|---|---|
| คำขอต่อนาที เกินแล้วรอรอบนาทีถัดไป | 60 req |
| คำขอต่อวัน นับรวมทุกคำขอ ทั้งรายชื่อเรื่อง รายละเอียด และลิงก์วิดีโอ | 30,000 req |
| IP ต่อวัน จำนวนเครื่องเซิร์ฟเวอร์ที่ใช้ key นี้ได้ในวันเดียว | 4 IP |
key ใหม่ได้ 60 req/นาที, 30,000 req/วัน และ 4 IP/วัน IP นับจากเครื่องที่เรียก API เมื่อครบจำนวนแล้ว IP ใหม่ได้รหัส 403 ส่วน IP ที่ใช้ไปแล้ววันนี้เรียกต่อได้ตามปกติ ตัวนับรายวันเริ่มใหม่เที่ยงคืนเวลาไทย ต่ออายุหรือซื้อเพิ่มได้ที่หน้าแพ็กเกจ
ใช้ req ให้คุ้ม
- เก็บรายการเรื่องไว้ในฐานของคุณ อย่าเรียก
/seriesทุกครั้งที่มีคนเปิดหน้า - ขอลิงก์ตอนผู้ชมกดเล่นจริง และใช้ลิงก์เดิมซ้ำได้จนใกล้
expires_at - ผู้ชมดูหลายตอนติดกัน ใช้
/linksครั้งเดียวคุ้มกว่าขอ/playทีละตอน
รหัสตอบกลับ
| พารามิเตอร์ | ความหมาย |
|---|---|
| 200 | สำเร็จ |
| 400 | พารามิเตอร์ไม่ถูกต้อง ข้อความใน error บอกว่าตัวไหน |
| 401 | ไม่มี key, key ผิด หรือ key ถูกปิด |
| 403 | key หมดอายุ, ใช้ครบจำนวน IP ของวันนี้, key ไม่มีสิทธิ์ขอลิงก์วิดีโอ หรือเรียกจากโดเมนที่ไม่ได้ลงทะเบียน |
| 404 | ไม่พบเรื่อง ตอน หรือ endpoint |
| 405 | ใช้ method อื่นที่ไม่ใช่ GET |
| 429 | เกินเพดานต่อนาทีหรือต่อวัน หรือส่ง key ผิดหลายครั้ง รอตามจำนวนวินาทีใน header Retry-After |
| 503 | ระบบขัดข้องชั่วคราว ลองใหม่ได้ |
คำตอบที่ผิดพลาดมีรูปแบบเดียวเสมอ ข้อความเป็นภาษาไทย แสดงให้ทีมของคุณดูได้เลย
{ "error": "เกินเพดาน 30000 req/วัน เริ่มนับใหม่เที่ยงคืนเวลาไทย" }
รอแล้วลองใหม่เมื่อได้ 429 หรือ 503
async function call(path, tries = 3) {
for (let i = 0; i < tries; i++) {
const r = await fetch("https://api.nungindy.com/v1" + path, { headers: { "X-API-Key": process.env.RENTAPI_KEY } });
if (r.status !== 429 && r.status !== 503) return r;
const wait = Number(r.headers.get("Retry-After")) || 5;
if (wait > 120) return r; // เกินเพดานรายวัน รอถึงพรุ่งนี้
await new Promise(ok => setTimeout(ok, wait * 1000));
}
}
เรียกจากเบราว์เซอร์
คำขอที่ไม่มี header Origin (เรียกจากเซิร์ฟเวอร์) ใช้ได้เสมอ ถ้าจำเป็นต้องเรียกจากหน้าเว็บโดยตรง แจ้งทีมงานให้ลงทะเบียนโดเมนของคุณกับ key ก่อน โดเมนอื่นได้รหัส 403 วิธีนี้ยังทำให้ key อยู่ในหน้าเว็บ จึงแนะนำให้เรียกผ่านเซิร์ฟเวอร์ของคุณมากกว่า
GET/v1/providers
รายชื่อแอปต้นทางที่มีเรื่องในคลัง พร้อมจำนวนเรื่องและจำนวนตอน ใช้ค่า slug กรองรายการเรื่อง · icon (96×96) / icon_small (48×48) เป็นรูป WebP ของแอป ไม่มีรูป = null
{ "items": [
{ "slug": "dramabox", "name": "DramaBox", "series": 1224,
"series_complete": 1224, "r2_episodes": 98110,
"icon": "https://nungindy.com/icons/dramabox-96.webp",
"icon_small": "https://nungindy.com/icons/dramabox-48.webp" } ] }GET/v1/genres
หมวดทั้งหมดพร้อมจำนวนเรื่อง ใช้ค่า slug กรองรายการเรื่อง
{ "items": [ { "slug": "romance", "name": "โรแมนติก", "name_en": "Romance", "series": 412 } ] }GET/v1/series
รายการเรื่องในคลัง แบ่งหน้าได้สองแบบ: page สำหรับแสดงทีละหน้า หรือ after_id สำหรับไล่ทั้งคลัง
| พารามิเตอร์ | ความหมาย |
|---|---|
| provider | เฉพาะแอปนี้ เช่น netshort (slug จาก /providers) |
| genre | slug ของหมวด หมวดที่ไม่มีอยู่ได้ 404 |
| dub | dub เฉพาะพากย์ไทย หรือ sub เฉพาะบรรยายไทย |
| q | ค้นจากชื่อเรื่อง |
| order | new เข้าใหม่ (ค่าเริ่มต้น) · updated อัปเดตล่าสุด · trending ยอดดู 7 วัน · popular ยอดดูรวม |
| page, per_page | หน้าที่ต้องการ และจำนวนต่อหน้า ค่าเริ่มต้น 50 สูงสุด 100 |
| after_id | ไล่ทั้งคลังเรียงตาม id เริ่มที่ 0 แล้วส่ง next_after_id ของคำตอบก่อนหน้า ใช้แล้ว order ไม่มีผล |
| updated_since | เฉพาะเรื่องที่ข้อมูลหรือไฟล์เปลี่ยนหลังเวลานี้ รูปแบบ ISO 8601 เช่น 2026-11-11T00:00:00Z |
| complete | 1 = เฉพาะเรื่องที่มีไฟล์ครบทุกตอน (ตอนนี้คลังมีแต่เรื่องที่ครบแล้ว) |
ตัวอย่างคำตอบ
{ "page": 1, "per_page": 50, "total": 94, "total_pages": 2,
"items": [ {
"id": 594349, "title": "ตื่นจากรักร้ายจุติเทพ", "description": "…",
"locale": "th", "provider": "netshort", "platform": "NetShort", "is_thai_dub": true,
"episodes": 52, "r2_episodes": 52, "r2_complete": true,
"codec": "h264", "codec_string": "avc1.64001F",
"cover": { "small": "https://…-300.webp", "large": "https://…-600.webp" },
"genres": [ { "slug": "romance", "name": "โรแมนติก", "name_en": "Romance" } ],
"views_7d": 12, "views_total": 340,
"created_at": "2026-11-10T03:12:00.000Z", "updated_at": "2026-11-11T01:02:00.000Z",
"source_updated_at": "2026-11-09T22:40:00.000Z" } ] }
แบบ after_id คำตอบมีแค่ items, per_page และ next_after_id (ไม่มี total) คำตอบเดียวกันถูกเก็บไว้ 60 วินาที
GET/v1/series/:id
รายละเอียดของเรื่องเดียว ทุก field เหมือนในรายการ เพิ่มรายการตอน
{ "id": 594349, "title": "…", "episodes": 52, "r2_episodes": 52, …,
"episode_list": [
{ "number": 1, "duration_sec": 94, "in_r2": true } ] }
in_r2 บอกว่าตอนนั้นมีไฟล์วิดีโอแล้ว duration_sec เป็น null เมื่อไม่ทราบความยาว
field ของเรื่อง
| field | ชนิด | ความหมาย |
|---|---|---|
| id | ตัวเลข | รหัสเรื่อง ไม่เปลี่ยน ใช้เป็นคีย์ในฐานของคุณ |
| title, description | ข้อความ | ชื่อและเรื่องย่อภาษาไทย description อาจเป็นข้อความว่าง |
| locale | ข้อความ | th |
| provider | ข้อความ | slug ของแอปต้นทาง |
| platform | ข้อความ | ชื่อแอปสำหรับแสดงผล |
| is_thai_dub | true/false | true = พากย์ไทย, false = เสียงต้นฉบับ บรรยายไทย |
| episodes | ตัวเลข | จำนวนตอนของเรื่อง |
| r2_episodes | ตัวเลข | จำนวนตอนที่มีไฟล์วิดีโอ |
| r2_complete | true/false | มีไฟล์ครบทุกตอน |
| codec | h264 / h265 / null | รหัสวิดีโอของเรื่อง ทุกตอนใช้รหัสเดียวกัน null = ระบบยังตรวจไฟล์ไม่เสร็จ (เรื่องที่เพิ่งเข้าคลัง) |
| codec_string | string / null | ค่าสำหรับ canPlayType เช่น avc1.64001F, hvc1.1.6.L120.90 |
| cover.small, cover.large | URL | ปก webp กว้าง 300 และ 600 px สัดส่วน 3:4 เปิดจากเว็บของคุณได้ตรง ๆ |
| genres | รายการ | slug, name, name_en |
| views_7d, views_total | ตัวเลข | ยอดดู 7 วันล่าสุด และยอดดูรวม |
| created_at | เวลา | เวลาที่เรื่องเข้าคลัง |
| updated_at | เวลา | เวลาที่ข้อมูลหรือไฟล์ในคลังเปลี่ยนล่าสุด (ใช้กับ updated_since) |
| source_updated_at | เวลา | เวลาที่ต้นทางอัปเดตเรื่องล่าสุด |
เวลาทุกตัวเป็น ISO 8601 เขตเวลา UTC เช่น 2026-11-11T01:02:00.000Z
GET/v1/series/:id/links
ลิงก์วิดีโอทุกตอนที่มีไฟล์ในคำขอเดียว นับ 1 req ต้องเป็น key ที่มีสิทธิ์ขอลิงก์
{ "id": 594349, "title": "…", …, "storage": "r2", "missing_episodes": [],
"expires_at": "2026-11-11T08:30:00.000Z", "expires_hint_sec": 21600,
"episode_list": [
{ "number": 1, "type": "mp4", "src": "https://…/1.mp4?X-Amz-…",
"subtitles": [ { "lang": "th", "src": "https://…/vtt/594349-1-th.vtt?s=…" } ] } ] }
ลิงก์ใน src ใช้ได้ถึง expires_at (ตามอายุลิงก์ของ key ค่าเริ่มต้น 6 ชั่วโมง) หลังจากนั้นขอใหม่ อย่าเก็บลิงก์ถาวรในฐานข้อมูล ส่วน missing_episodes คือเลขตอนที่ยังไม่มีไฟล์
GET/v1/play/:id/:ep
ลิงก์วิดีโอของตอนเดียว เหมาะกับการขอตอนผู้ชมกดเล่น นับ 1 req
{ "series_id": 594349, "episode": 1, "storage": "r2",
"codec": "h264", "codec_string": "avc1.64001F",
"parts": [ { "src": "https://…/1.mp4?X-Amz-…", "type": "mp4", "codec": "h264", "duration_sec": null, "proxied": false } ],
"subtitles": [ { "lang": "th", "src": "https://…/vtt/594349-1-th.vtt?s=…" } ],
"source": "r2", "sources": 1,
"expires_hint_sec": 21600, "expires_at": "2026-11-11T08:30:00.000Z" }
ทุกตอนมีไฟล์เดียว ใช้ parts[0].src ได้เลย ตอนที่ยังไม่มีไฟล์ได้ 404
นำลิงก์ไปเล่น
ลิงก์เป็นไฟล์ MP4 ธรรมดา (เปิดเล่นได้ทันที ไม่ต้องรอโหลดจบ และกรอได้) ใส่ใน <video> ได้เลย ไม่ต้องใช้ hls.js ภาพเป็นแนวตั้ง 9:16
<video controls playsinline preload="metadata" src="ลิงก์จาก src"
style="aspect-ratio: 9 / 16; width: 100%; max-height: 90vh; background: #000">
<track kind="subtitles" srclang="th" label="ไทย" src="ลิงก์ซับ" default>
</video>
ลิงก์วิดีโอใช้ได้กับทุกคนที่ได้ลิงก์ไปจนกว่าจะหมดอายุ ถ้าไม่อยากให้ลิงก์หลุด ให้ขอจากเซิร์ฟเวอร์ตอนผู้ชมกดเล่น แล้วส่งให้เฉพาะผู้ชมคนนั้น
ตัวอย่าง: เซิร์ฟเวอร์ของคุณส่งลิงก์ให้ผู้ชม
// Express — ผู้ชมกดเล่น → หน้าเว็บขอ /watch/594349/1 ของคุณ
app.get("/watch/:id/:ep", async (req, res) => {
const r = await fetch(`https://api.nungindy.com/v1/play/${+req.params.id}/${+req.params.ep}`,
{ headers: { "X-API-Key": process.env.RENTAPI_KEY } });
const d = await r.json();
if (!r.ok) return res.status(r.status).json({ error: d.error });
res.json({ src: d.parts[0].src, subtitles: d.subtitles, expires_at: d.expires_at });
});
H.264 และ H.265
ไฟล์ส่วนใหญ่เป็น H.264 ซึ่งเล่นได้ทุกเบราว์เซอร์ แต่บางแอปส่งมาเป็น H.265 (HEVC) ซึ่งเครื่องเก่าบางรุ่นเล่นไม่ได้ ภาพจะดำหรือมีแต่เสียง
| แอป (provider) | H.265 |
|---|---|
Melolo melolo | ทุกเรื่อง |
PineDrama pinedrama | ทุกเรื่อง |
ShortWave shortwave | บางเรื่อง |
แอปอื่นทั้งหมดเป็น H.264 เบราว์เซอร์ที่เล่น H.265 ได้: Safari บน iPhone/iPad/Mac, Chrome และ Edge รุ่นใหม่บน Windows/Mac/Android ที่เครื่องถอดรหัส HEVC ได้ ส่วน Firefox และเครื่องรุ่นเก่ารองรับไม่แน่นอน ขึ้นกับเครื่อง
ดูจาก field codec
ทุกเรื่องมี codec (h264 หรือ h265) และ codec_string ทั้งใน /series, /series/:id, /links และ /play ระบบตรวจจากไฟล์จริงใน R2 เรื่องที่เพิ่งเข้าคลังอาจเป็น null อยู่ไม่กี่นาที
// ในเบราว์เซอร์ของผู้ชม
const v = document.createElement("video");
function playable(s) {
if (!s.codec_string) return true; // ยังไม่รู้ ลองเล่นไปก่อน
return v.canPlayType('video/mp4; codecs="' + s.codec_string + '"') !== "";
}
// playable(series) เป็น false → ซ่อนเรื่องนั้น
// หรือขึ้นข้อความแนะนำให้เปิดด้วย Safari หรือ Chrome รุ่นใหม่
ถ้ากรองฝั่งเซิร์ฟเวอร์ ใช้ codec === "h265" แยกเรื่องที่เครื่องเก่าอาจเล่นไม่ได้
ถ้าเล่นแล้วเกิด error ที่ <video> (เช่น MEDIA_ERR_SRC_NOT_SUPPORTED) ให้แสดงข้อความเดียวกันแทนจอดำ
ซับไทย
เรื่องที่ซับไทยฝังมาในภาพแล้วจะไม่มีไฟล์ซับแยก ส่วนเรื่องที่มีซับแยก ลิงก์ไฟล์ WebVTT อยู่ใน subtitles ลิงก์ซับไม่หมดอายุ และเปิดจากโดเมนอื่นได้ (ส่ง Access-Control-Allow-Origin: *) จึงใส่ใน <track> ได้ตรง ๆ subtitles เป็นรายการว่างเมื่อไม่มีซับแยก
GET/v1/ping
ตรวจว่า key ใช้ได้ ดูเพดาน วันหมดอายุ จำนวนคำขอและ IP ที่ใช้วันนี้ นับ 1 req
{ "ok": true, "ip": "203.0.113.7",
"key": { "name": "เว็บของคุณ", "prefix": "rnj_ab12", "allow_links": true, "allow_r2": true,
"r2_ttl_hours": 6, "per_min": 60, "per_day": 30000, "ips_per_day": 4,
"expires_at": "2026-12-12T16:59:00.000Z" },
"remaining": { "requests": 29880,
"usedToday": { "catalog": 100, "play": 20, "total": 120 },
"ips": { "max": 4, "used": 1, "list": [ { "ip": "203.0.113.7", "calls": 120 } ] },
"expires_at": "2026-12-12T16:59:00.000Z" },
"time": "2026-11-12T08:00:00.000Z" }
ค่า null หมายถึงไม่จำกัด allow_links เป็น false = key ดูรายการได้อย่างเดียว ขอลิงก์วิดีโอไม่ได้
ดึงทั้งคลังแล้วตามของใหม่
ครั้งแรก ไล่ทั้งคลังด้วย after_id จนได้ next_after_id เป็น null
GET https://api.nungindy.com/v1/series?after_id=0&per_page=100
GET https://api.nungindy.com/v1/series?after_id=576873&per_page=100 ← ใช้ next_after_id ของรอบก่อน
รอบถัดไป จดเวลาที่เริ่มดึงรอบก่อนไว้ แล้วขอเฉพาะเรื่องที่เปลี่ยนหลังเวลานั้น เรื่องใหม่และเรื่องที่ข้อมูลเปลี่ยนจะติดมาด้วย
GET https://api.nungindy.com/v1/series?updated_since=2026-11-11T00:00:00Z&per_page=100&page=1
เรื่องเข้าคลังเมื่อมีไฟล์ครบทุกตอน คลังอัปเดตทุก 5 นาที ดึงถี่กว่านั้นไม่ได้ข้อมูลใหม่ ดึงชั่วโมงละครั้งก็เพียงพอ เรื่องที่ถูกนำออกจากคลังจะหายจากรายการ และ /series/:id ได้ 404 ให้ซ่อนเรื่องนั้นในเว็บของคุณ
ความเข้ากันได้
ชื่อและความหมายของ field ใน /v1 จะไม่เปลี่ยน ถ้าจำเป็นต้องเปลี่ยนจะออกเป็น /v2 แยก ระหว่างนี้อาจมี field ใหม่เพิ่มเข้ามา โค้ดของคุณควรข้าม field ที่ไม่รู้จัก สารบัญแบบ JSON อยู่ที่ https://api.nungindy.com/v1