店內媒體顯示器 API(Display API)的使用方式

API Display 媒體 Android 數位看板 配對 零售媒體 曝光

本指南的目的

本文彙整使用 ReceiptRoller 的店內媒體顯示器 API(Display API),在店頭 Android 端末播放媒體循環(圖片、影片)的應用程式實作步驟。這是一份為了在 POS 端末兼用的電子看板,或專用的數位看板機(壁掛平板等),播送 ReceiptRoller 的零售媒體與宣傳影片的指南。

前提

  • 不預期在 Android 端末端啟動瀏覽器的 OAuth 流程。各端末以長期有效的裝置權杖進行驗證
  • 裝置權杖以「配對(Pairing)」流程取得。店鋪擁有者在管理畫面產生的 6 位數代碼輸入至 Android 應用程式後,即會為各端末發行權杖
  • 基底 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以批次回報播放實績(play)裝置權杖

配對流程

各端末只進行 1 次配對。權杖在 Android 端持久化,之後所有的請求都在 Authorization: Bearer {token} 標頭附上。

  1. 店鋪擁有者在管理畫面登錄新的顯示器時,會顯示 6 位數的配對代碼(有效期間 10 分鐘、僅限 1 次)
  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 分鐘的有效期限,或已被使用

重要:

  • 權杖只能在回應中取得。伺服器端僅保存雜湊,因此遺失時請在管理畫面刪除顯示器,再重新配對
  • 1 個配對代碼只能使用 1 次。重新配對時請發行新的代碼

心跳

向伺服器傳達端末處於線上。管理畫面的「線上/離線」顯示以此時間戳為基準(若距最後一次心跳在 5 分鐘以內則視為線上)。

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 秒)。由於伺服器端可能會依輪詢負載變更此值,建議不要寫死,而是參照回應。

播放清單取得

端末取得應播放媒體的排序清單。各項目中含有已 SAS 簽章的直接存取 URL(有效 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 URL 的有效期限mediaUrl 有效 1 小時。以過期的 URL 嘗試下載會回傳 403,因此請以 pollIntervalSeconds 間隔重新取得播放清單,取得新的 URL
  • durationSeconds — 圖片為 10 秒(預設),影片為實際長度。應用程式請以指定秒數切換至下一個項目
  • 空的播放清單 — 若擁有者端尚未承認任何素材,items 為空陣列。為空時,建議顯示黑畫面或「準備中」佔位畫面

播放實績的回報(曝光)

針對播放的每個時段,將「何時、播放了哪個素材、播放了多久」從 Android 批次傳送至伺服器。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 秒傳送一次。若每播放 1 個時段就傳送會造成過度的網路負載
  • 批次上限 — 一次請求最多 500 筆。超過時會回傳 400 batch_too_large
  • eventId 由用戶端產生 — 建議使用 UUID v4。伺服器端以此 ID 保證冪等性。即使離線後重送相同批次,伺服器也會靜默地將第二次計為 duplicates
  • completed 旗標 — 若播完整個時段則為 true,中途切換至下一個則為 false。用於完成率的分析
  • 離線因應 — 網路失敗時請保存於本地佇列,恢復後再重送。由於具備冪等性,重複傳送是安全的
  • placementId / campaignId 為選填 — 播放清單項目中並未直接包含,但若 Android 應用程式已理解 Phase 2 的播放清單結構,附上可提升分析的粒度。留空也會被 accepted

回應欄位:

  • accepted — 新記錄的筆數
  • duplicates — 已處理過的筆數(重送造成的重複)
  • rejected — 因必填欄位缺漏等而無法處理的筆數
  • errors — 拒絕理由的陣列(供除錯用,內容可能隨版本變更)

典型的實作流程(Android)

  1. 初次啟動 — 若本地不存在權杖則顯示配對畫面。輸入代碼 → POST /pair → 取得權杖 → 保存
  2. 常時執行迴圈
    1. 啟動時及其後每 60 秒呼叫 GET /playlist
    2. hashHint 與前次不同的項目重新下載,相同的項目從快取讀取
    3. 依取得順序全螢幕播放圖片/影片,每個 durationSeconds 切到下一個
    4. 播到最後一個項目後回到開頭
  3. 並行 — 每 60 秒呼叫 POST /heartbeat 向伺服器傳達存活
  4. 並行 — 針對每個播放的時段記錄至本地佇列,每 60 秒以 POST /impressions 批次傳送
  5. 錯誤處理
    • 401 — 權杖無效。回到重新配對畫面
    • 403 — 權杖與路徑上的 displayId 不一致(Bug)。取得日誌後重新配對
    • 網路失敗 — 繼續播放最後取得的播放清單,保持曝光佇列並以退避重試

安全性

  • 裝置權杖為長期有效。請保存於 Android Keystore 等端末的加密儲存區
  • 伺服器端僅保存權杖的 SHA-256 雜湊。遺失的權杖可在管理畫面刪除顯示器再重新配對來失效
  • 1 個權杖 = 1 個顯示器。若在多個端末重複使用同一權杖,管理畫面的「最後心跳」「目前解析度」會被各端末覆寫而混亂

這個 API 的未來

目前的播放清單支援「依活動 × 版位進行時段、店鋪定向與加權循環」。今後的功能會追加以下項目:

  • 店鋪擁有者用播放實績儀表板(曝光數、完成率、各顯示器分帳)
  • 頻率上限的實效化(版位的 frequencyCapPerHour 目前可設定,但在 Phase 5 實作前為無作用)

API 綱要維持向前相容 — 既有 4 個端點的回應格式今後也不會變更(可能會追加欄位)。

相關資訊

發布日: 2026-06-03 更新日: 2026-07-06
このトピックについて
開発者API
機能の詳細を見る
標籤
API (22) OAuth (15) Android (10) iOS (9) Webhook (8) api (7) oauth (5) POS串接 (4) getting-started (4) 參考 (4)
相關文章