วิธีใช้ In-Store Media Display API (Display API)

API Display มีเดีย Android ไซเนจ การจับคู่ รีเทลมีเดีย Impression

วัตถุประสงค์ของคู่มือนี้

รวบรวมขั้นตอนการสร้างแอปที่เล่นมีเดียลูป (ภาพ・วิดีโอ) บนเครื่อง Android หน้าร้าน โดยใช้ In-Store Media Display API (Display API) ของ ReceiptRoller คู่มือนี้สำหรับการเผยแพร่รีเทลมีเดียของ ReceiptRoller หรือวิดีโอแคมเปญ บนเครื่องแบบ POS ควบคู่ (kiosk) หรือเครื่องไซเนจเฉพาะทาง (แท็บเล็ตแขวนผนัง ฯลฯ)

เงื่อนไขเบื้องต้น

  • ไม่ได้ออกแบบให้เปิดเบราว์เซอร์เพื่อทำโฟลว์ OAuth ที่ฝั่งเครื่อง Android แต่ละเครื่องจะยืนยันตัวตนด้วยโทเคนอุปกรณ์ที่มีอายุยาว
  • โทเคนอุปกรณ์รับได้จากกระบวนการ "จับคู่ (pairing)" เมื่อป้อนโค้ด 6 หลักที่เจ้าของร้านสร้างในหน้าจัดการลงในแอป Android จะมีการออกโทเคนของแต่ละเครื่อง
  • Base URL: https://receiptroller.io

รายการเอนด์พอยต์

เอนด์พอยต์วัตถุประสงค์การยืนยันตัวตน
POST /api/v1/displays/pairแลกโค้ดจับคู่ 6 หลักเป็นโทเคนอุปกรณ์ไม่จำเป็น (โค้ดคือการยืนยันตัวตน)
POST /api/v1/displays/{displayId}/heartbeatแจ้งการมีชีวิตของเครื่อง (สำหรับตัดสินออนไลน์/ออฟไลน์)โทเคนอุปกรณ์
GET /api/v1/displays/{displayId}/playlistดึงรายการมีเดียที่ควรเล่นพร้อมลำดับโทเคนอุปกรณ์
POST /api/v1/displays/{displayId}/impressionsรายงานผลการเล่น (เพลย์) แบบแบตช์โทเคนอุปกรณ์

โฟลว์การจับคู่

แต่ละเครื่องจับคู่เพียงครั้งเดียว โทเคนจะถูกเก็บถาวรที่ฝั่ง Android และแนบในเฮดเดอร์ Authorization: Bearer {token} ในทุกคำขอต่อจากนั้น

  1. เมื่อเจ้าของร้านลงทะเบียนดิสเพลย์ใหม่ในหน้าจัดการ จะแสดงโค้ดจับคู่ 6 หลัก (อายุ 10 นาที・ใช้ได้ครั้งเดียว)
  2. ป้อนโค้ดในหน้าจอเปิดใช้งานครั้งแรกของแอป Android
  3. Android เรียก POST /api/v1/displays/pair และรับโทเคนกับ displayId
  4. Android เก็บโทเคนและ displayId ลงในเครื่องแบบถาวร (แนะนำให้เข้ารหัส)

ตัวอย่างคำขอ:

POST /api/v1/displays/pair
Content-Type: application/json
User-Agent: RRDisplay/1.0 (Android 14; Pixel Tablet)

{
  "code": "123456",
  "screenWidth": 1920,
  "screenHeight": 1080,
  "supportsVideo": true,
  "supportsAudio": false
}

ตัวอย่างเรสปอนส์ (200):

{
  "displayId": "a1b2c3d4-...",
  "organizationId": "7d325d1d-...",
  "deviceToken": "dvc_3f9c1a8b7e2d6f4a1b8c5d2e9f7a3b6c"
}

เรสปอนส์ข้อผิดพลาด:

  • 400 missing_code — ไม่ได้ระบุฟิลด์ code
  • 401 invalid_or_expired_code — โค้ดผิด หรือเกินอายุ 10 นาที หรือถูกใช้ไปแล้ว

สำคัญ:

  • โทเคนรับได้จากเรสปอนส์เท่านั้น เนื่องจากฝั่งเซิร์ฟเวอร์เก็บเพียงแฮชไว้ หากทำสูญหาย ให้ลบดิสเพลย์ในหน้าจัดการและจับคู่ใหม่
  • โค้ดจับคู่หนึ่งอันใช้ได้เพียงครั้งเดียว เมื่อจับคู่ใหม่ กรุณาออกโค้ดใหม่

Heartbeat

แจ้งเซิร์ฟเวอร์ว่าเครื่องออนไลน์อยู่ การแสดง "ออนไลน์/ออฟไลน์" ในหน้าจัดการอิงตามไทม์สแตมป์นี้ (หากไม่เกิน 5 นาทีนับจาก heartbeat ล่าสุด ถือว่าออนไลน์)

POST /api/v1/displays/{displayId}/heartbeat
Authorization: Bearer dvc_3f9c1a8b...
User-Agent: RRDisplay/1.0 (Android 14; Pixel Tablet)

ตัวอย่างเรสปอนส์ (200):

{
  "ok": true,
  "serverTimeUtc": "2026-06-03T05:21:30Z",
  "nextHeartbeatSeconds": 60
}

ช่วงเวลาเรียกที่แนะนำ: กรุณาทำตาม nextHeartbeatSeconds ในเรสปอนส์ (ปัจจุบัน 60 วินาที) เนื่องจากฝั่งเซิร์ฟเวอร์อาจเปลี่ยนค่าตามภาระการโพลลิง จึงแนะนำให้อ้างอิงเรสปอนส์แทนการฮาร์ดโค้ด

การดึงเพลย์ลิสต์

เครื่องดึงรายการมีเดียที่ควรเล่นพร้อมลำดับ แต่ละไอเทมมี URL เข้าถึงโดยตรงที่เซ็น SAS แล้ว (อายุ 1 ชั่วโมง)

GET /api/v1/displays/{displayId}/playlist
Authorization: Bearer dvc_3f9c1a8b...

ตัวอย่างเรสปอนส์ (200):

{
  "displayId": "a1b2c3d4-...",
  "generatedAt": "2026-06-03T05:21:30Z",
  "pollIntervalSeconds": 60,
  "items": [
    {
      "creativeId": "creative-001",
      "name": "夏の新商品キャンペーン",
      "type": "Image",
      "mediaUrl": "https://strprdomnicon.blob.core.windows.net/creatives/...?sv=...&sig=...",
      "durationSeconds": 10,
      "hashHint": "creative-001_638549812340000000"
    },
    {
      "creativeId": "creative-002",
      "name": "新メニュー紹介動画",
      "type": "Video",
      "mediaUrl": "https://strprdomnicon.blob.core.windows.net/creatives/...?sv=...&sig=...",
      "durationSeconds": 30,
      "hashHint": "creative-002_638549812350000000"
    }
  ]
}

คู่มือการใช้งาน:

  • ลำดับการเล่น — กรุณาเล่นตามลำดับในอาร์เรย์ items และเมื่อเล่นถึงตัวสุดท้ายแล้วให้วนกลับไปเล่นตัวแรก
  • แคช — ตราบใดที่ hashHint ไม่เปลี่ยน ไฟล์มีเดียไม่จำเป็นต้องดาวน์โหลดซ้ำ แนะนำให้แคชในเครื่อง เมื่อ hashHint เปลี่ยน จึงดึงใหม่
  • อายุของ SAS URLmediaUrl มีอายุ 1 ชั่วโมง หากพยายามดาวน์โหลดด้วย URL ที่หมดอายุจะได้ 403 กรุณาดึงเพลย์ลิสต์ใหม่ทุกช่วง pollIntervalSeconds เพื่อรับ URL ใหม่
  • durationSeconds — ภาพคือ 10 วินาที (ค่าเริ่มต้น) วิดีโอคือความยาวจริง แอปควรสลับไปไอเทมถัดไปตามจำนวนวินาทีที่ระบุ
  • เพลย์ลิสต์ว่าง — หากฝั่งเจ้าของยังไม่อนุมัติครีเอทีฟ items จะเป็นอาร์เรย์ว่าง เมื่อว่าง แนะนำให้แสดงจอดำหรือพเลสโฮลเดอร์ "กำลังเตรียมการ"

การรายงานผลการเล่น (Impression)

ส่งข้อมูล "เล่นครีเอทีฟไหน・เมื่อไหร่・นานเท่าไร" จาก Android ไปยังเซิร์ฟเวอร์แบบแบตช์ ต่อ 1 สล็อตที่เล่น Phase 3 / t-e9a2f501

POST /api/v1/displays/{displayId}/impressions
Authorization: Bearer dvc_3f9c1a8b...
Content-Type: application/json

{
  "events": [
    {
      "eventId": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
      "creativeId": "creative-001",
      "campaignId": "camp-001",
      "placementId": "place-001",
      "playedAtUtc": "2026-06-03T05:20:00Z",
      "durationPlayedMs": 10000,
      "completed": true
    },
    {
      "eventId": "b2c3d4e5-f678-9012-3456-7890abcdef01",
      "creativeId": "creative-002",
      "campaignId": "camp-001",
      "placementId": "place-001",
      "playedAtUtc": "2026-06-03T05:20:10Z",
      "durationPlayedMs": 30000,
      "completed": true
    }
  ]
}

ตัวอย่างเรสปอนส์ (200):

{
  "accepted": 2,
  "duplicates": 0,
  "rejected": 0,
  "errors": []
}

คู่มือการใช้งาน:

  • ความถี่ในการส่ง — แนะนำให้ส่งทุก 60 วินาที การส่งต่อทุกสล็อตที่เล่นจะทำให้เกิดภาระเครือข่ายมากเกินไป
  • ขีดจำกัดแบตช์ — สูงสุด 500 รายการต่อคำขอ หากเกินจะได้ 400 batch_too_large
  • eventId สร้างโดยไคลเอนต์ — แนะนำ UUID v4 ฝั่งเซิร์ฟเวอร์ใช้ ID นี้เพื่อรับประกันความเป็นไอเดมโพเทนต์ (idempotency) แม้ส่งแบตช์เดิมซ้ำหลังออฟไลน์ เซิร์ฟเวอร์จะนับครั้งที่สองเป็น duplicates อย่างเงียบ ๆ
  • แฟลก completed — หากเล่นสล็อตจนจบเป็น true หากสลับไปตัวถัดไปกลางคันเป็น false ใช้สำหรับวิเคราะห์อัตราการเล่นจบ
  • รองรับออฟไลน์ — เมื่อเครือข่ายล้มเหลว ให้เก็บไว้ในคิวในเครื่องและส่งซ้ำหลังกู้คืน เนื่องจากไอเดมโพเทนต์ การส่งซ้ำจึงปลอดภัย
  • placementId / campaignId เป็นตัวเลือก — ไม่ได้รวมอยู่ในไอเทมของเพลย์ลิสต์โดยตรง แต่หากแอป Android เข้าใจโครงสร้างเพลย์ลิสต์ของ Phase 2 การใส่จะเพิ่มความละเอียดของการวิเคราะห์ แม้ว่างก็ถูก accepted

ฟิลด์ในเรสปอนส์:

  • accepted — จำนวนที่บันทึกใหม่
  • duplicates — จำนวนที่ประมวลผลไปแล้ว (ซ้ำจากการส่งซ้ำ)
  • rejected — จำนวนที่ประมวลผลไม่ได้ เช่น ขาดฟิลด์ที่จำเป็น
  • errors — อาร์เรย์ของเหตุผลการปฏิเสธ (สำหรับดีบัก เนื้อหาอาจเปลี่ยนตามเวอร์ชัน)

โฟลว์การนำไปใช้ทั่วไป (Android)

  1. เปิดใช้งานครั้งแรก — หากไม่มีโทเคนในเครื่อง ให้แสดงหน้าจอจับคู่ ป้อนโค้ด → POST /pair → รับโทเคน → บันทึก
  2. ลูปทำงานปกติ
    1. เรียก GET /playlist ตอนเริ่มและทุก 60 วินาทีหลังจากนั้น
    2. ไอเทมที่ hashHint ต่างจากครั้งก่อนให้ดาวน์โหลดใหม่ ไอเทมเดิมอ่านจากแคช
    3. เล่นภาพ/วิดีโอเต็มจอตามลำดับที่ได้ และไปตัวถัดไปทุก durationSeconds
    4. เมื่อเล่นถึงไอเทมสุดท้ายแล้วให้วนกลับไปต้น
  3. ขนาน — เรียก POST /heartbeat ทุก 60 วินาทีเพื่อแจ้งการมีชีวิตแก่เซิร์ฟเวอร์
  4. ขนาน — บันทึกลงคิวในเครื่องต่อทุกสล็อตที่เล่น และส่งแบตช์ด้วย POST /impressions ทุก 60 วินาที
  5. การจัดการข้อผิดพลาด
    • 401 — โทเคนไม่ถูกต้อง กลับไปหน้าจอจับคู่ใหม่
    • 403 — โทเคนกับ displayId บนพาธไม่ตรงกัน (บั๊ก) เก็บล็อกแล้วจับคู่ใหม่
    • เครือข่ายล้มเหลว — เล่นเพลย์ลิสต์ที่ดึงมาล่าสุดต่อไปตามเดิม เก็บคิว impression ไว้และลองใหม่แบบแบ็กกราวด์

ความปลอดภัย

  • โทเคนอุปกรณ์มีอายุยาว กรุณาเก็บในสตอเรจเข้ารหัสของเครื่อง เช่น Android Keystore
  • ฝั่งเซิร์ฟเวอร์เก็บเพียงแฮช SHA-256 ของโทเคน โทเคนที่ทำสูญหายจะยกเลิกได้โดยลบดิสเพลย์ในหน้าจัดการแล้วจับคู่ใหม่
  • 1 โทเคน = 1 ดิสเพลย์ หากใช้โทเคนเดิมซ้ำในหลายเครื่อง "heartbeat ล่าสุด" และ "ความละเอียดปัจจุบัน" ในหน้าจัดการจะถูกเขียนทับกันไปมาระหว่างเครื่องและสับสน

อนาคตของ API นี้

เพลย์ลิสต์ปัจจุบันรองรับ"การกำหนดเป้าหมายตามช่วงเวลา・ร้านค้า และลูปถ่วงน้ำหนักด้วยแคมเปญ × เพลสเมนต์" ฟีเจอร์ในอนาคตจะเพิ่มดังนี้:

  • แดชบอร์ดผลการเล่นสำหรับเจ้าของร้าน (จำนวน impression, อัตราการเล่นจบ, ยอดวิวแยกตามดิสเพลย์)
  • การบังคับใช้ขีดจำกัดความถี่ให้มีผลจริง (frequencyCapPerHour ของเพลสเมนต์ตั้งค่าได้แล้วในปัจจุบัน แต่จะเป็น no-op จนกว่าจะมีการนำ Phase 5 ไปใช้)

API สคีมาจะรักษาความเข้ากันได้แบบไปข้างหน้า — รูปแบบเรสปอนส์ของ 4 เอนด์พอยต์ที่มีอยู่จะไม่เปลี่ยนในอนาคต (แต่อาจมีฟิลด์เพิ่มเติมได้)

ข้อมูลที่เกี่ยวข้อง

วันที่เผยแพร่: 2569-06-03 วันที่อัปเดต: 2569-07-05
このトピックについて
開発者API
機能の詳細を見る
แท็ก
API (22) OAuth (15) Android (10) iOS (9) Webhook (8) api (7) oauth (5) getting-started (4) การลงทะเบียนแอป (4) การแก้ปัญหา (4)
บทความที่เกี่ยวข้อง