Shoplive API スタートガイド
Shoplive External API v2 を3 つのテーマで学びます。認証と規約を一度押さえれば、「最初の配信を作る」→「商品カタログの同期」→「配信結果の収集」の順に、実運用に必要な連携を完成できます。
概要と準備
External API v2 は、顧客企業のシステム(管理画面、バックオフィス、コマースサーバー)から Shoplive の配信・商品・セラー・ブランド・ショートフォーム・統計をサーバー間で扱うための REST API です。Shoplive Admin 画面でできることのほとんどを API で自動化できます。
準備するもの
- accessKey / secretKey — 顧客アカウントごとに発行される API キーのペアです。Shoplive Admin で確認するか、担当マネージャーに依頼してください。
secretKeyはサーバーだけで保管し、ブラウザやアプリには入れないでください。 - HTTPS リクエストを送れるサーバー環境 — サンプルは curl を基準にし、Node.js · Python · Java · Go · Ruby のトークン生成例も用意しています。
- External API v2 の利用設定 — アカウントで API v2 が有効になっている必要があります。担当マネージャーにご確認ください。
- (任意) 送出ツール — OBS などの RTMP エンコーダー。実際に映像を送出するときに必要です。
Base URL
| 環境 | Base URL | 備考 |
|---|---|---|
| 本番 | https://private.shopliveapi.com | 環境は 1 つだけです。テストは本番アカウントでリハーサル配信とテスト用商品を使って行ってください。 |
v2 のエンドポイントはすべて /v2/{accessKey}/... の形です。パスの {accessKey} とトークン内の accessKey クレームが一致している必要があります。
3 つのテーマ
次へ → 認証 (JWT)
認証 (JWT)
External API は顧客企業が自ら署名した JWT で認証します。トークン発行用の API を呼ぶ必要はありません。secretKey で HS256 署名したトークンを作り、Authorization: Bearer ヘッダーに載せるだけです。
トークンの仕様
| 項目 | 値 | 説明 |
|---|---|---|
| アルゴリズム | HS256 | 署名キーは secretKey です。 |
クレーム accessKey | 必須 | 顧客の accessKey。同じアカウントの secretKey で署名してください。 |
クレーム exp | 必須 | 有効期限。サーバー間通信用なので十分長く(例: 1〜3 年)取り、サーバーで再利用して構いません。 |
クレーム iat | 推奨 | 発行時刻。 |
| ヘッダー | Authorization: Bearer {JWT} | すべてのリクエストに含めます。 |
secretKey は Base64 エンコードされたキーです。Java の JJWT(0.9.x)は文字列キーを自動で Base64 デコードしますが、Node.js · Python など他のライブラリではデコードしたバイト列を署名キーに渡さないとサーバー側の検証と一致しません。下のサンプルはこの違いを反映しています。トークン生成サンプル
// npm i jsonwebtoken
const jwt = require('jsonwebtoken');
function createShopliveToken(accessKey, base64SecretKey) {
const key = Buffer.from(base64SecretKey, 'base64'); // デコードしたバイト列で署名
return jwt.sign({ accessKey }, key, { algorithm: 'HS256', expiresIn: '365d' });
}
# pip install pyjwt
import base64, time, jwt
def create_shoplive_token(access_key: str, base64_secret_key: str) -> str:
key = base64.b64decode(base64_secret_key) # デコードしたバイト列で署名
now = int(time.time())
payload = {"accessKey": access_key, "iat": now, "exp": now + 365 * 24 * 3600}
return jwt.encode(payload, key, algorithm="HS256")
// build.gradle: implementation 'io.jsonwebtoken:jjwt:0.9.1'
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class ShopliveToken {
public static String create(String accessKey, String base64SecretKey) {
long oneYear = 365L * 24 * 60 * 60 * 1000;
return Jwts.builder()
.signWith(SignatureAlgorithm.HS256, base64SecretKey) // 文字列キーは Base64 として解釈される
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + oneYear))
.claim("accessKey", accessKey)
.compact();
}
}
// go get github.com/golang-jwt/jwt/v5
package shoplive
import (
"encoding/base64"
"time"
"github.com/golang-jwt/jwt/v5"
)
func CreateShopliveToken(accessKey, base64SecretKey string) (string, error) {
key, err := base64.StdEncoding.DecodeString(base64SecretKey) // デコードしたバイト列で署名
if err != nil {
return "", err
}
now := time.Now()
claims := jwt.MapClaims{
"accessKey": accessKey,
"iat": now.Unix(),
"exp": now.Add(365 * 24 * time.Hour).Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(key)
}
# gem install jwt
require 'jwt'
require 'base64'
def create_shoplive_token(access_key, base64_secret_key)
key = Base64.decode64(base64_secret_key) # デコードしたバイト列で署名
now = Time.now.to_i
payload = { accessKey: access_key, iat: now, exp: now + 365 * 24 * 3600 }
JWT.encode(payload, key, 'HS256')
end
最初の呼び出しで確認
生成したトークンで配信一覧を取得し、認証が通るか確認します。サンプルの YOUR_JWT の位置にトークンを入れてください。トークンはシークレットなので右のパネルでは受け付けません。
curl -s "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/campaign?page=1&count=5" \
-H "Authorization: Bearer YOUR_JWT"正常なら 200 とともに results 配列が返ります(配信がなければ空配列)。失敗すると 401 と次のようなボディが返ります。
{ "_s": -824, "_e": "Invalid token" }Token does not exist.— Authorization ヘッダーがない、または値が空です。ヘッダーをAuthorization: Bearer <トークン>の形式で送っているか確認してください。Invalid token— 署名不一致(secretKey の誤り・Base64 処理漏れ)、accessKeyクレームの欠落、存在しない accessKey。Expired token(_s: -412)—expを過ぎています。トークンを再生成してください。- トークンが正しいのに
_s: -824が続く場合 — URL の accessKey がトークンのaccessKeyクレームと一致しているか確認してください。一致している場合は、アカウントで External API v2 が有効になっていない可能性があるため、担当マネージャーに有効化を依頼してください。
前へ ← 概要次へ → リクエスト・レスポンスの規約
リクエスト・レスポンスの規約
すべてのエンドポイントに共通するルールです。一度覚えれば、リファレンスのどの API も同じ方法で呼び出せます。
形式
- リクエスト・レスポンスのボディは JSON (UTF-8) です。ボディを持つリクエストには
Content-Type: application/jsonを付けます。 - 時刻は ISO 8601 の日時文字列(UTC オフセット付き)です。例:
2026-10-01T20:00:00+09:00、2026-10-01T11:00:00Z。レスポンスは UTC(Z)で返ります。 - 成功レスポンスは HTTP
200または204で、返す内容がない API(配信開始・終了など)は空のボディを返します。
識別子
| 識別子 | 説明 |
|---|---|
accessKey | 顧客キー。すべてのパスの最初のセグメント。 |
campaignKey / campaignId | 配信の識別子。パスの {campaignKeyOrId} にはどちらを入れても構いません。プレイヤー・SDK 連携では campaignKey を使います。 |
productId / customerProductId | 商品の識別子。customerProductId は顧客システム側の商品 ID で、登録時に設定しておくと以後の重複チェック・更新・紐付けの基準になります。 |
sellerId, brandId | セラー・ブランドの識別子。セラー単位で配信・商品を分けるときに使います。 |
一覧取得とページネーション
一覧 API は共通のクエリパラメーターを受け取り、results + totalCount の形で応答します。
| パラメーター | デフォルト | 説明 |
|---|---|---|
page | 1 | 1 から始まるページ番号。 |
count | 10 | ページサイズ。配信一覧は最大 20です。デフォルト値と上限は API ごとに異なります(商品一覧はデフォルト 30・最大 100)。 |
keyword | — | タイトルなどのテキスト検索。商品一覧は keyword の代わりに name・brand・sku・url で検索します。 |
sortAscending | false | 並び順。配信一覧は予約時刻基準です。 |
{
"results": [ { "...": "項目" } ],
"totalCount": 42
}複数件処理のレスポンス
商品の登録・更新・紐付けのように配列を受け取る API は、項目ごとの結果を返します。HTTP が 200 でも一部が失敗していることがあるので failCount を確認してください。
{
"results": [
{ "customerProductId": "SKU-1001", "status": "CREATE" },
{ "customerProductId": "SKU-1002", "status": "FAIL", "message": "..." }
],
"successCount": 1, "warnCount": 0, "failCount": 1, "totalCount": 2
}エラーレスポンス
HTTP ステータスが 400 以上、またはレスポンスボディ(JSON)に負の _s がある場合はエラーです。ステータスコードと _s の両方を確認してください。成功レスポンスに負の _s は含まれません。
{
"_s": -710,
"_e": "Campaign does not exist"
}_s は Shoplive のステータスコード(負の値)、_e は人が読むメッセージです。レスポンスによってはボディが空、または別の形式の場合があります。
よく見るコードはエラーコードにまとめました。クライアントは HTTP ステータスと _s の両方を確認してエラーを判定し、_s·_e(または message)はログと問い合わせ時の参考値として残してください。商品の一括 API のように、HTTP 200 のまま項目ごとの失敗を status: "FAIL" で知らせるレスポンスもあるため、failCount と項目ごとの message も確認してください。
最初の配信を作る
API で配信を作り、商品を付けたうえで、自社の Web ページにプレイヤーを表示して配信が見えることまで確認します。配信の開始・終了は配信者が Shoplive コンソールで行うのが一般的なので、最後に任意ステップとして置いています。
- ミッション 1 · 配信を作成 — タイトルと予約時刻で配信を作り、
campaignKeyを受け取ります。 - ミッション 2 · 商品を登録 — 顧客側の商品 ID(
customerProductId)で商品を登録します。 - ミッション 3 · 配信に商品を紐付け — 登録した商品を配信に付け、順序を決めます。
- ミッション 4 · 送出情報を確認 — RTMP URL・ストリームキー・プレイヤー URL を受け取ります。
- ミッション 5 · 自社ページにプレイヤーを表示 — Web SDK の数行のコードで配信を表示します。
- ミッション 6 · (任意) API で開始・終了 — コンソールの代わりに自動化が必要なときに。
前へ ← 規約次へ → ミッション 1 · 配信を作成
ミッション 1 · 配信を作成
配信(campaign)は Shoplive の基本単位です。タイトルだけあれば作成でき、作成直後にプレイヤー URL と送出情報が一緒に発行されます。
curl -s -X POST "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/campaign" \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '{
"campaignMeta": {
"title": "秋の新作ライブ",
"description": "10月1日 20時",
"tags": ["autumn", "new"],
"serveReplay": true,
"scheduledStartAt": "2026-10-01T20:00:00+09:00",
"scheduledEndAt": "2026-10-01T21:00:00+09:00"
}
}'| フィールド | 必須 | 説明 |
|---|---|---|
campaignMeta.title | 必須 | 配信タイトル。ないと _s: -837 で失敗します。 |
campaignMeta.description | 内部メモ。 | |
campaignMeta.tags | 検索・分類用のタグ配列。 | |
campaignMeta.serveReplay | 終了後にリプレイを公開するかどうか。 | |
campaignMeta.scheduledStartAt / scheduledEndAt | 予約時刻。プレイヤーの配信予定表示に使われます。 | |
campaignMeta.rehearsalPasscode | リハーサルの入場コード。 | |
seller.sellerId, campaignMeta.brand.brandId | セラー・ブランド所属として作るときに指定します。 |
{
"campaignMeta": {
"campaignId": "123456",
"campaignKey": "a1b2c3d4e5f6",
"shoplivePlayerUrl": "https://player.shoplive.cloud/...",
"title": "秋の新作ライブ",
"campaignStatus": "READY",
"serveReplay": true,
"tags": ["autumn", "new"],
"scheduledStartAt": "2026-10-01T11:00:00Z",
"scheduledEndAt": "2026-10-01T12:00:00Z"
},
"stream": {
"streamUrl": "rtmp://...",
"streamKey": "live_...",
"liveUrl": "https://.../playlist.m3u8",
"videoAspectRatio": "9:16"
}
}レスポンスの campaignMeta.campaignKey を保存してください。以後のすべてのステップで使います。右パネルの campaignKey 欄に入れると、下のサンプルにすぐ反映されます。
配信ステータス(campaignStatus)
| ステータス | 意味 |
|---|---|
RESERVED | 自動開始が予約された状態。 |
READY | 配信開始が可能。作成直後のデフォルト。プレイヤーは配信予定画面を表示します。 |
REHEARSAL | リハーサル中。入場コード(rehearsalPasscode)を知っている人だけが視聴できる。end で終了すると READY に戻る。 |
ONAIR | ライブ配信中。 |
ENDED | 終了。serveReplay が true ならリプレイを提供。 |
前へ ← 最初の配信を作る次へ → ミッション 2 · 商品を登録
ミッション 2 · 商品を登録
商品は顧客アカウント単位で一度登録し、複数の配信で再利用します。配列で一度に複数登録できます。
curl -s -X POST "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/product" \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '[
{
"customerProductId": "SKU-1001",
"name": "ウールブレンドコート",
"brand": "Shoplive Apparel",
"url": "https://shop.example.com/products/1001",
"imageUrl": "https://cdn.example.com/1001.jpg",
"currency": "JPY",
"originalPrice": 25900,
"discountPrice": 19900,
"showPrice": true
},
{
"customerProductId": "SKU-1002",
"name": "カシミヤマフラー",
"url": "https://shop.example.com/products/1002",
"imageUrl": "https://cdn.example.com/1002.jpg",
"currency": "JPY",
"originalPrice": 8900
}
]'| フィールド | 必須 | 説明 |
|---|---|---|
customerProductId | 推奨 | 顧客側の商品 ID(一意)。このガイドの同期フローでは必ず指定してください。以後の更新・紐付け・取得の基準キーで、同じ値がすでにあるとその項目は status: FAIL で返ります。更新はミッション 7の PUT を使います。 |
name | 必須 | 商品名。 |
url | 必須 | 商品詳細ページ。プレイヤーで商品をタップしたときの遷移先です。 |
imageUrl | 推奨 | 代表画像。 |
currency, originalPrice, discountPrice, discountRate | 価格情報。 | |
showPrice | 価格を表示するかどうか(デフォルト true)。更新(PUT)時も希望する値を一緒に送ってください。 | |
brand, brandIdentifier | ブランド表示名・顧客側のブランドコード。 | |
sku, description, ctaButtonTitle | 付加情報。ボタン文言を変えるには ctaButtonTitle(使用前に Shoplive との協議が必要)。 | |
seller.sellerId | セラー所有の商品として作るときに指定。省略すると顧客所有。 |
{
"results": [
{ "status": "CREATE", "productId": "90001", "customerProductId": "SKU-1001", "name": "ウールブレンドコート", "...": "..." },
{ "status": "CREATE", "productId": "90002", "customerProductId": "SKU-1002", "name": "カシミヤマフラー", "...": "..." }
],
"successCount": 2, "warnCount": 0, "failCount": 0, "totalCount": 2
}前へ ← ミッション 1 · 配信を作成次へ → ミッション 3 · 配信に商品を紐付け
ミッション 3 · 配信に商品を紐付け
登録した商品を配信に付けます。この API は追加方式です。送った商品が配信の商品一覧の末尾に順番どおり追加されます。すでに紐付いている商品は再度追加されません。
curl -s -X POST "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/campaign/CAMPAIGN_KEY/product" \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '[ { "customerProductId": "SKU-1001" }, { "customerProductId": "SKU-1002" } ]'| フィールド | 説明 |
|---|---|
customerProductId または productId | 紐付ける商品。1 リクエスト内ではどちらか一方だけ使ってください。 |
{
"results": [
{ "customerProductId": "SKU-1001", "status": "SUCCESS", "message": null },
{ "customerProductId": "SKU-1002", "status": "FAIL", "message": "The product is already registered." }
],
"successCount": 1, "failCount": 1, "totalCount": 2
}すでに紐付いている商品は項目の結果が FAIL(The product is already registered.)で返り、残りの項目はそのまま紐付けられます。同じリクエストを再送しても問題ありません。順序は別の API で決めます。ボディに並べた順に並び替えられ、含めなかった商品は後ろに送られます。
curl -s -X PUT "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/campaign/CAMPAIGN_KEY/product/order" \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '[ { "customerProductId": "SKU-1002" }, { "customerProductId": "SKU-1001" } ]'前へ ← ミッション 2 · 商品を登録次へ → ミッション 4 · 送出情報を確認
ミッション 4 · 送出情報を確認
配信を 1 件取得すると、メタ情報とともに送出用 RTMP アドレス、視聴用プレイヤー URL、紐付いた商品一覧がすべて返ります。
curl -s "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/campaign/CAMPAIGN_KEY" \
-H "Authorization: Bearer YOUR_JWT"| パス | 用途 |
|---|---|
stream.streamUrl + stream.streamKey | OBS などの送出ツールのサーバー · ストリームキーに入力します。送出を始めるとコンソールのプレビューに映像が表示されます。 |
campaignMeta.shoplivePlayerUrl | すぐに開ける視聴ページ。 |
campaignMeta.campaignKey | 次のステップで Web SDK に渡す値。 |
stream.liveUrl | HLS(m3u8)アドレス。自前のプレイヤーを使うときに。 |
campaignProductList[] | ミッション 3 で紐付けた商品と sortOrder。 |
ONAIR にすることは別の操作です。通常は送出を先に始め、コンソールのプレビューで確認してから配信者がコンソールで配信開始を押します(API ではミッション 6)。ミッション 5 · 自社ページにプレイヤーを表示
API で作った配信は Web SDK の数行のコードで自社ページに表示できます。このステップまで来れば「最初の配信を作る」は完了です。配信が READY なら配信予定画面、ONAIR ならライブが表示されます。
<script src="https://static.shoplive.cloud/shoplive.js"></script>
<div id="shoplive-player"></div>
<script>
cloud.shoplive.init({ accessKey: "YOUR_ACCESS_KEY" });
cloud.shoplive.setPlayer("shoplive-player", { campaignKey: "CAMPAIGN_KEY" });
</script>- Web — ユーザー情報の連携、カート・購入コールバックなど残りの連携は Web SDK ガイドに従ってください。
- Android · iOS — 同じ
accessKeyとcampaignKeyで モバイル SDK からプレイヤーを表示します。 - 配信一覧ページ —
GET /v2/{accessKey}/campaign?campaignStatus=READYで予定配信を取得し(管理画面で保存動画の配信に自動開始を予約した場合はRESERVEDで別途取得)、自社 UI に描き、クリック時に上記コードでプレイヤーを開く構成が一般的です。
ミッション 6 · (任意) API で開始・終了
配信者がコンソールを使わず、スケジューラーが配信をオン・オフする構成なら Console API を使います。送出ツールが映像を送っている状態で開始を呼ぶと、視聴者にライブが公開されます。
# リハーサル開始(入場コードを知っている人だけ視聴可)
curl -s -X POST "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/console/CAMPAIGN_KEY/start?rehearsal=true" \
-H "Authorization: Bearer YOUR_JWT" -o /dev/null -w "%{http_code}\n"
# リハーサル終了 → READY
curl -s -X POST "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/console/CAMPAIGN_KEY/end" \
-H "Authorization: Bearer YOUR_JWT" -o /dev/null -w "%{http_code}\n"
# ライブ開始 → ONAIR
curl -s -X POST "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/console/CAMPAIGN_KEY/start" \
-H "Authorization: Bearer YOUR_JWT" -o /dev/null -w "%{http_code}\n"
# 終了 → ENDED
curl -s -X POST "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/console/CAMPAIGN_KEY/end" \
-H "Authorization: Bearer YOUR_JWT" -o /dev/null -w "%{http_code}\n"リハーサルを end で終了すると READY に戻ります。その後 start を呼び出してライブを開始してください。
ONAIR になりますが、視聴者には待機画面が表示されます。自動化する場合は、送出ツールがストリームを送っているか(コンソールのプレビューまたは stream.liveUrl のレスポンス)を先に確認してください。前へ ← ミッション 5 · 自社ページにプレイヤーを表示次へ → 商品カタログの同期
商品カタログの同期
自社の商品 DB が原本で、Shoplive はその複製であるべきです。商品カタログの同期ではcustomerProductId を軸に商品を作成・更新・削除する同期ルーチンを作り、配信中の売り切れ・在庫変化を視聴者画面に即時反映する方法を扱います。
ミッション 7 · 商品 upsert パターン
商品登録(POST)は、同じ customerProductId がすでにあるとその項目を status: FAIL で返します。更新(PUT)は customerProductId で対象を探します。この 2 つを組み合わせると同期ルーチンになります。
- 変更のあった商品をすべて POST で送ります。新規は
CREATE、既存はFAILに分かれます。 FAILで返ってきた項目を集めて PUT で再送します。内容が同じでもUPDATEで応答します。messageの文言は言語設定によって変わるため、文字列で判定しないでください。- 自社側で削除された商品は DELETE で整理します。配信に紐付いた商品まで削除しないようにするには
?force=falseを付けてください。この場合、配信に紐付いた商品は削除されずFAILが返ります。デフォルト(true)では商品を配信の商品リストから外して削除します。
# 値下げを反映: customerProductId で対象を探して更新
curl -s -X PUT "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/product" \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '[
{ "customerProductId": "SKU-1001", "name": "ウールブレンドコート", "url": "https://shop.example.com/products/1001",
"currency": "JPY", "originalPrice": 25900, "discountPrice": 17900, "showPrice": true }
]'# force=false: 配信に紐付いた商品は削除せず FAIL を返す(デフォルト true は配信の商品リストから外して削除)
curl -s -X DELETE "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/product?force=false" \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '[ { "customerProductId": "SKU-OLD-0001" } ]'以下の例は、認証 で作成した JWT を環境変数 SL_TOKEN に設定してあることを前提としています。
async function syncProducts(products) { // products: 自社 DB の変更分
const created = await sl('POST', '/product', products);
const existing = created.results
.filter(r => r.status === 'FAIL')
.map(r => products.find(p => p.customerProductId === r.customerProductId));
const updated = existing.length ? await sl('PUT', '/product', existing) : { results: [] };
return { created: created.successCount, updated: updated.successCount,
failed: updated.results.filter(r => r.status === 'FAIL') };
}
async function sl(method, path, body) {
const res = await fetch(`https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY${path}`, {
method, headers: { Authorization: `Bearer ${process.env.SL_TOKEN}`, 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const text = await res.text();
let data = null;
try { data = JSON.parse(text); } catch {} // ボディが空、または JSON でない場合がある
if (!res.ok || data?._s < 0) throw new Error(`${res.status} ${text}`); // HTTP ≥ 400 または _s < 0 ならエラー(エラーレスポンス参照)
return data;
}
import os, requests
BASE = "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY"
H = {"Authorization": f"Bearer {os.environ['SL_TOKEN']}", "Content-Type": "application/json"}
def sl(method, path, body):
r = requests.request(method, BASE + path, json=body, headers=H, timeout=10)
try:
data = r.json()
except ValueError: # ボディが空、または JSON でない場合がある
data = None
s = data.get("_s") if isinstance(data, dict) else None
if r.status_code >= 400 or (isinstance(s, (int, float)) and s < 0):
raise RuntimeError(f"{r.status_code} {r.text}") # HTTP ≥ 400 または _s < 0 ならエラー(エラーレスポンス参照)
return data
def sync_products(products): # products: 自社 DB の変更分(dict のリスト)
created = sl("POST", "/product", products)
by_id = {p["customerProductId"]: p for p in products}
existing = [by_id[r["customerProductId"]] for r in created["results"]
if r["status"] == "FAIL"]
updated = sl("PUT", "/product", existing) if existing else {"results": [], "successCount": 0}
return created["successCount"], updated["successCount"]
// build.gradle: implementation 'org.json:json:20240303'
import org.json.JSONArray;
import org.json.JSONObject;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class ProductSync {
static final HttpClient CLIENT = HttpClient.newHttpClient();
static JSONObject syncProducts(JSONArray products) throws Exception { // products: 自社 DB の変更分
JSONObject created = sl("POST", "/product", products);
JSONArray createdResults = created.getJSONArray("results");
JSONArray existing = new JSONArray();
for (int i = 0; i < createdResults.length(); i++) {
JSONObject r = createdResults.getJSONObject(i);
if ("FAIL".equals(r.getString("status"))) {
String cid = r.getString("customerProductId");
for (int j = 0; j < products.length(); j++) {
JSONObject p = products.getJSONObject(j);
if (cid.equals(p.getString("customerProductId"))) { existing.put(p); break; }
}
}
}
JSONObject updated = existing.length() > 0
? sl("PUT", "/product", existing)
: new JSONObject().put("results", new JSONArray()).put("successCount", 0);
JSONArray failed = new JSONArray();
JSONArray updatedResults = updated.getJSONArray("results");
for (int i = 0; i < updatedResults.length(); i++) {
JSONObject r = updatedResults.getJSONObject(i);
if ("FAIL".equals(r.getString("status"))) failed.put(r);
}
return new JSONObject()
.put("created", created.getInt("successCount"))
.put("updated", updated.getInt("successCount"))
.put("failed", failed);
}
static JSONObject sl(String method, String path, Object body) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY" + path))
.header("Authorization", "Bearer " + System.getenv("SL_TOKEN"))
.header("Content-Type", "application/json")
.method(method, HttpRequest.BodyPublishers.ofString(body.toString()))
.build();
HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
String text = response.body() == null ? "" : response.body().trim();
JSONObject json = text.startsWith("{") ? new JSONObject(text) : new JSONObject(); // ボディが空、または JSON でない場合がある
if (response.statusCode() >= 400 || json.optInt("_s", 0) < 0) {
throw new RuntimeException(response.statusCode() + " " + text); // HTTP ≥ 400 または _s < 0 ならエラー(エラーレスポンス参照)
}
return json;
}
}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
type slResult struct {
CustomerProductID string `json:"customerProductId"`
Status string `json:"status"`
}
type slResponse struct {
S int `json:"_s"`
Results []slResult `json:"results"`
SuccessCount int `json:"successCount"`
}
// products: 自社 DB の変更分
func syncProducts(products []map[string]interface{}) (map[string]interface{}, error) {
created, err := sl("POST", "/product", products)
if err != nil {
return nil, err
}
var existing []map[string]interface{}
for _, r := range created.Results {
if r.Status == "FAIL" {
for _, p := range products {
if p["customerProductId"] == r.CustomerProductID {
existing = append(existing, p)
break
}
}
}
}
var updated slResponse
if len(existing) > 0 {
u, err := sl("PUT", "/product", existing)
if err != nil {
return nil, err
}
updated = *u
}
var failed []slResult
for _, r := range updated.Results {
if r.Status == "FAIL" {
failed = append(failed, r)
}
}
return map[string]interface{}{
"created": created.SuccessCount,
"updated": updated.SuccessCount,
"failed": failed,
}, nil
}
func sl(method, path string, body interface{}) (*slResponse, error) {
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
req, err := http.NewRequest(method, "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY"+path, bytes.NewReader(buf))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("SL_TOKEN"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
var out slResponse
jsonErr := json.Unmarshal(data, &out) // ボディが空、または JSON でない場合がある
if res.StatusCode >= 400 || (jsonErr == nil && out.S < 0) {
return nil, fmt.Errorf("%d %s", res.StatusCode, string(data)) // HTTP ≥ 400 または _s < 0 ならエラー(エラーレスポンス参照)
}
if jsonErr != nil {
return nil, jsonErr
}
return &out, nil
}
require 'net/http'
require 'json'
require 'uri'
# products: 自社 DB の変更分
def sync_products(products)
created = sl('POST', '/product', products)
existing = created['results']
.select { |r| r['status'] == 'FAIL' }
.map { |r| products.find { |p| p['customerProductId'] == r['customerProductId'] } }
updated = existing.any? ? sl('PUT', '/product', existing) : { 'results' => [], 'successCount' => 0 }
{
'created' => created['successCount'],
'updated' => updated['successCount'],
'failed' => updated['results'].select { |r| r['status'] == 'FAIL' }
}
end
def sl(method, path, body)
uri = URI("https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY#{path}")
req = Net::HTTP.const_get(method.capitalize).new(uri)
req['Authorization'] = "Bearer #{ENV['SL_TOKEN']}"
req['Content-Type'] = 'application/json'
req.body = body.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body.to_s) rescue nil # ボディが空、または JSON でない場合がある
s = data.is_a?(Hash) ? data['_s'] : nil
raise "#{res.code} #{res.body}" if res.code.to_i >= 400 || (s.is_a?(Numeric) && s < 0) # HTTP ≥ 400 または _s < 0 ならエラー(エラーレスポンス参照)
data
end
status を同期ログに残してください。値は API ごとに異なります: 登録 CREATE・WARN・FAIL / 更新 UPDATE・FAIL / 削除 DELETE・FAIL。WARN は商品は作成されたものの確認が必要な場合です。前へ ← 商品カタログの同期次へ → ミッション 8 · 配信商品の更新
ミッション 8 · 配信商品の更新
配信に付いた商品一覧は 4 つの操作で管理します。すべて解除を除く 3 つの操作は customerProductId の配列をボディで受け取ります。
| 操作 | エンドポイント | 備考 |
|---|---|---|
| 追加 | POST /v2/{accessKey}/campaign/{campaignKeyOrId}/product | 末尾に追加。すでにある項目は FAIL。 |
| 除外 | DELETE /v2/{accessKey}/campaign/{campaignKeyOrId}/product | 配信から外すだけで、商品自体は残る。 |
| 並び替え | PUT /v2/{accessKey}/campaign/{campaignKeyOrId}/product/order | 並べた順に整列、含めないものは後ろへ。 |
| すべて解除 | DELETE /v2/{accessKey}/campaign/{campaignKeyOrId}/product/all | 一覧を空にして再構成するときに。 |
# 配信から商品を 1 点除外
curl -s -X DELETE "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/campaign/CAMPAIGN_KEY/product" \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '[ { "customerProductId": "SKU-1002" } ]'「配信の商品一覧を自社の企画画面とまったく同じにする」のが目的なら、すべて解除 → 追加 → 並び替えの 3 回の呼び出しが最も単純です。配信中は一覧が一瞬空になるので、配信前に行うか、差分だけを計算して追加・除外してください。
前へ ← ミッション 7 · 商品 upsert パターン次へ → ミッション 9 · 売り切れ・在庫のリアルタイム反映
ミッション 9 · 売り切れ・在庫のリアルタイム反映
配信中に在庫が変わったら、Console API でその配信内の商品状態を変更します。視聴者のプレイヤーに即時反映されます。商品自体(価格・名称)を直す PUT とは異なり、この API は特定の配信内での表示状態だけを扱います。
# 売り切れにする
curl -s -X PUT "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/console/CAMPAIGN_KEY/product/stockStatus?stockStatus=SOLD_OUT" \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '[ { "customerProductId": "SKU-1002" } ]'
# 再入荷 → 販売中に戻す
curl -s -X PUT "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/console/CAMPAIGN_KEY/product/stockStatus?stockStatus=IN_STOCK" \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '[ { "customerProductId": "SKU-1002" } ]'
# 「紹介中(Now)」の強調をオンにする
curl -s -X PUT "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/console/CAMPAIGN_KEY/product/show?show=true" \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '[ { "customerProductId": "SKU-1001" } ]'stockStatus | 表示 |
|---|---|
IN_STOCK | 販売中。 |
LOW_IN_STOCK | 残りわずかの表示。 |
SOLD_OUT | 売り切れ。購入ボタンが無効になります。 |
ONAIR の配信だけを対象にすれば呼び出し量を抑えられます。前へ ← ミッション 8 · 配信商品の更新次へ → ミッション 10 · セラー別に分ける
ミッション 10 · セラー別に分ける
出店者(セラー)ごとに配信と商品を分けて運用するマーケットプレイス型の顧客なら、リソース作成時に seller.sellerId を指定し、一覧取得で sellerId フィルターを使います。すでに作ったリソースは所有権移転 API で移せます。
| やりたいこと | 方法 |
|---|---|
| セラーの作成・取得 | Seller API — POST /v2/{accessKey}/seller |
| セラー所有で商品・配信を作成 | ボディに "seller": { "sellerId": 123 } |
| 既存の商品・配信をセラーに移す | PUT /v2/{accessKey}/product/transferToSeller/{sellerId}、PUT /v2/{accessKey}/campaign/{campaignKeyOrId}/transferToSeller/{sellerId}。配信に紐付いた商品もあわせて移すには、配信の移管に ?withProduct=true を付けてください。 |
| セラーのものだけ取得 | 配信・商品一覧 API に sellerId=123 |
前へ ← ミッション 9 · 売り切れ・在庫のリアルタイム反映次へ → 配信結果の収集
配信結果の収集
配信が終わったら、視聴・エンゲージメント指標と生ログを自社のデータウェアハウスに取り込み、リプレイ URL を自社ページに連携します。ポイントはポーリングではなく Webhook で終了を検知することです。
ミッション 11 · Webhook で終了を検知
Webhook は Shoplive が顧客のエンドポイントへ配信イベントを送る機能です。設定はセルフサービスではなく、Shoplive 側での登録が必要なので、担当マネージャーまたは ask@shoplive.cloud に受信 URL を伝えてください。
| イベント | 活用 |
|---|---|
| 配信開始 | 自社ページの「LIVE」バッジ点灯、プッシュ通知の送信 |
| 配信終了 | このテーマのトリガー。統計・ログの収集、リプレイの公開 |
| オークション開始 · 終了 · リセット | オークション型配信の落札処理 |
- 受信サーバーは約 1 秒以内に 200 を返し、実際の処理はキューに入れて非同期で行ってください。終了直後は集計完了まで数分かかることがあるため、収集ジョブを少し後ろに予約するのが安全です。
- Webhook はイベントごとに 1 回だけ送信されます。下のポーリングを定期的な補完(reconciliation)ジョブとして併せて実行してください。同じ配信が 2 つの経路で届くことがあるため、
campaignKeyをキーに冪等に処理してください。 - 開始・終了イベントの本文は配信 API が返す Campaign オブジェクトと同じです。
campaignMeta.campaignKey,campaignMeta.campaignStatus… のように読みます。開始・終了イベントはそれぞれ別の URL に、オークションイベントは 3 つ目の URL に送られます(受け取るイベントの種類も設定できます)。 - 署名の検証(開始・終了イベントのみ、Webhook のシークレットキーを設定した場合):リクエストヘッダー
X-SHOPLIVE-WEBHOOK-TIMESTAMP(Unix epoch ミリ秒の文字列)とX-SHOPLIVE-WEBHOOK-SIGNATURE(HMAC-SHA256 の小文字 hex)が付きます。署名の対象はタイムスタンプ +.+ 受信したままのリクエストボディのバイト列(JSON パース前)で、鍵はシークレット文字列の UTF-8 バイト列です。比較は定数時間比較で行ってください。 - リハーサル配信は、登録時に該当オプションを有効にした場合のみ Webhook を送信します。
- フィールドの詳細は Webhook リファレンスを参照してください。
署名検証の例です。WEBHOOK_SECRET は登録時に設定した Webhook のシークレットキーで、ボディはパースする前の生のバイト列で検証します。
const crypto = require('crypto');
const express = require('express');
const app = express();
// Express: パース前の生の Buffer で検証する
app.post('/webhooks/shoplive', express.raw({ type: 'application/json' }), (req, res) => {
const ts = req.get('X-SHOPLIVE-WEBHOOK-TIMESTAMP') || '';
const sig = req.get('X-SHOPLIVE-WEBHOOK-SIGNATURE') || '';
const expected = crypto.createHmac('sha256', process.env.WEBHOOK_SECRET) // シークレット文字列(UTF-8)
.update(Buffer.concat([Buffer.from(ts + '.'), req.body])).digest('hex');
const ok = sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!ok) return res.sendStatus(401);
res.sendStatus(200); // 約 1 秒以内に応答し、処理はキューへ渡す
const body = JSON.parse(req.body); // body.campaignMeta.campaignKey
});
import hashlib, hmac, os
from flask import Flask, request, abort
app = Flask(__name__)
@app.post("/webhooks/shoplive")
def shoplive_webhook():
raw = request.get_data() # パース前の生のバイト列
ts = request.headers.get("X-SHOPLIVE-WEBHOOK-TIMESTAMP", "")
sig = request.headers.get("X-SHOPLIVE-WEBHOOK-SIGNATURE", "")
expected = hmac.new(os.environ["WEBHOOK_SECRET"].encode(),
ts.encode() + b"." + raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401)
return "", 200 # 約 1 秒以内に 200 を返し、処理はキューへ渡す
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HexFormat; // Java 17+
static boolean verify(String secret, String timestamp, byte[] rawBody, String signature) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
mac.update((timestamp + ".").getBytes(StandardCharsets.UTF_8));
String expected = HexFormat.of().formatHex(mac.doFinal(rawBody));
return MessageDigest.isEqual( // 定数時間比較
expected.getBytes(StandardCharsets.UTF_8), signature.getBytes(StandardCharsets.UTF_8));
}
終了した配信を定期的に取得する方法は、Webhook をまだ登録できていない間の代替になり、登録後も取りこぼしたイベントを補う補完ジョブとして使えます。数分間隔で十分です。
# 過去 1 日に予約され、いま ENDED の配信(from は予約時刻が基準)
curl -s -G "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/campaign" \
-H "Authorization: Bearer YOUR_JWT" \
--data-urlencode "campaignStatus=ENDED" \
--data-urlencode "from=$(date -u -v-1d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d '1 day ago' +%Y-%m-%dT%H:%M:%SZ)" \
--data-urlencode "count=20"前へ ← 配信結果の収集次へ → ミッション 12 · 統計を取得
ミッション 12 · 統計を取得
配信 1 件のサマリー指標は配信統計 API で、複数の配信を一度に取るときは期間指定 API を使います。
curl -s "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/campaign/CAMPAIGN_KEY/stats" \
-H "Authorization: Bearer YOUR_JWT"統計は配信終了後に取得できます。配信中、または終了直後で集計前の場合は 404 を返します。
{
"campaignId": "123456",
"campaignKey": "a1b2c3d4e5f6",
"title": "秋の新作ライブ",
"campaignStartedAt": "2026-10-01T11:00:12Z",
"campaignEndedAt": "2026-10-01T12:03:40Z",
"totalViewers": 8421,
"totalLoginViewers": 5210,
"newLoginViewers": 1830,
"peakConcurrentViewers": 1320,
"averageViewingTime": 412,
"totalViewingTime": 3469452,
"likes": 25630,
"chats": 4180,
"chatViewers": 910,
"productClicks": 2760,
"productClickViewers": 1540,
"bannerClicks": 130
}| フィールド | 説明 |
|---|---|
totalViewers | ユニーク視聴者数。 |
totalLoginViewers | ログイン(ユーザー情報連携)済み視聴者数。SDK でユーザー情報を渡した場合のみ集計されます。 |
newLoginViewers | この顧客の配信を初めて視聴した視聴者数。 |
peakConcurrentViewers | 最大同時視聴者数。 |
averageViewingTime, totalViewingTime | 平均・合計視聴時間(秒)。 |
likes, chats, productClicks, bannerClicks | インタラクション回数。*Viewers はその行動をしたユニーク視聴者数。 |
# 過去 7 日以内に終了した配信の統計を一度に(最大 1 週間の範囲)
curl -s -G "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/campaign/stats" \
-H "Authorization: Bearer YOUR_JWT" \
--data-urlencode "from=2026-09-25T00:00:00Z" \
--data-urlencode "to=2026-10-02T00:00:00Z"期間指定の from/to は配信の終了時刻が基準です。
ミッション 13 · ログをダウンロード
サマリー指標だけでは足りない場合、イベント単位の生ログを取得します。配信 1 件のログは ck で、複数の配信は from/to(最大 1 週間)で指定します。
# 配信 1 件の商品クリックログを CSV で(ck 指定時は配信開始時刻から 4 日後までの範囲が自動適用)
curl -s -G "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/log" \
-H "Authorization: Bearer YOUR_JWT" \
--data-urlencode "type=productClickLog" \
--data-urlencode "format=csv" \
--data-urlencode "ck=CAMPAIGN_KEY" \
-o product_clicks.csv| パラメーター | 値 |
|---|---|
type | likeLog · chatLog · productClickLog · popupClickLog · viewerAccessLog · viewerActivityLog · highlightedChatLog · filteredChatLog |
format | json · csv · tsv |
from, to | ISO 8601。ck なしで取得する場合は必須で、範囲は最大 1 週間。 |
ck | 配信キーまたは ID で限定(任意)。指定すると from/to の代わりに配信開始時刻から 4 日後までの範囲が適用されます。開始済みの配信のみ指定できます。 |
excludeAdmin | デフォルト true。viewerAccessLog・viewerActivityLog から管理者・運営者のアクティビティを除外します。チャットはフィルタリングされません。 |
前へ ← ミッション 12 · 統計を取得次へ → ミッション 14 · リプレイ URL
ミッション 14 · リプレイ URL
配信作成時に serveReplay: true にしていれば、終了後にリプレイが自動で作られます。「最初の配信を作る」の Web SDK コードは終了した配信でそのままリプレイを再生するので、追加作業は不要です。自前のプレイヤーや他チャネルで使う場合はアドレスを取り出します。
# 終了した配信のリプレイ URL
curl -s "https://private.shopliveapi.com/v2/YOUR_ACCESS_KEY/campaign/CAMPAIGN_KEY" \
-H "Authorization: Bearer YOUR_JWT" | jq '.stream.replayLiveUrl'stream.replayLiveUrl— リプレイのストリームアドレス。終了直後はまだ準備中のことがあるので、空なら少し後に再取得してください。campaignMeta.shoplivePlayerUrl— Shoplive プレイヤーでリプレイを開くリンク。共有・プッシュにそのまま使えます。
前へ ← ミッション 13 · ログをダウンロード次へ → エラーコード
エラーコード
よく見るステータスコードです。ボディの _s 値を基準にしており、HTTP ステータスが 400 以上、または _s が負ならエラーとして扱ってください(エラーレスポンス)。ステータスコードだけで分岐せず、_s も併せて確認してください。
| HTTP | _s | 意味 | 対応 |
|---|---|---|---|
| 401 | -824 | 認証失敗(トークンなし・署名不一致・URL とトークンの accessKey 不一致)、またはアカウントで External API v2 の利用が有効化されていない | 認証のチェックリストを確認。トークンが正しいのに続く場合は、担当マネージャーに API v2 の有効化を依頼 |
| 401 | -412 | トークン期限切れ | トークンを再生成 |
| 400 | -510 | 不正なリクエスト(必須フィールド欠落、形式エラー) | _e のメッセージを参照 |
| 400 | -836 / -837 | 配信作成時に campaignMeta / title が欠落 | ボディを確認 |
| 200 | — | 商品の検証失敗(name · url の欠落、価格・割引率が負)。HTTP エラーではなく、一括レスポンス内で項目ごとの status: "FAIL" + message として返ります | failCount と results[].message を確認 |
| 400 | -839 | 配信への商品紐付けで productId / customerProductId がどちらもない | ボディを確認 |
| 403 | -714 · -717 · -711 · -114 · -706 | 配信の現在の状態では実行できないリクエスト(進行中の配信への start -714 ・ 終了済み配信への start/end -717 ・ 未開始の配信への end・ログ取得 -711 ・ 開始済み配信の削除 -114 ・ 再配信の元配信の削除 -706) | 配信の状態(campaignStatus)を確認して再試行 |
| 404 | -710 | 配信が見つからない | campaignKeyOrId を確認 |
| 404 | -831 | 商品が見つからない | productId/customerProductId を確認 |
| 404 | -207 | セラーが見つからない | sellerId を確認 |
| 404 | -404 | 統計がまだない(配信中または集計前) | 数分後に再取得 |
| 406 | -1009 | 売り切れ商品のオークション開始 | 在庫状態を確認 |
| 409 | -1002 · -1004 · -116 | 現在の状態と合わないリクエスト(進行中または終了済みのオークション、表示中のポップアップの紐付け解除など) | 状態を確認してから再リクエスト |
| 500 | — | サーバーエラー | 再試行しても続く場合はリクエスト時刻とエンドポイントを添えて問い合わせ |
前へ ← ミッション 14 · リプレイ URL次へ → 次のステップ
次のステップ
3 つのテーマをすべて終えたら、運用に必要な残りの機能はリファレンスで同じ規約のまま呼び出せます。
| やりたいこと | API |
|---|---|
| 過去の配信を再配信・複製 | Campaign API の rerun · duplicate |
| ブランド管理 | Brand API |
| ショートフォームの登録・商品紐付け | Shortform API |
| VOD 動画の管理 | Video API |
| 配信中のクーポンポップアップ | Popup API |
| Web・アプリ画面連携の詳細 | Web SDK ガイド · モバイル SDK ガイド |
前へ ← エラーコード次へ → トラブルシューティング & FAQ
トラブルシューティング & FAQ
トークンを作ったのに 401 Invalid token が続きます。
順に確認してください。① ヘッダーが Authorization: Bearer <トークン> の形式か(大文字小文字・空白)。② ペイロードに accessKey クレームがあるか。③ パスの {accessKey} とクレームの値が同じか。④ Java 以外の言語で secretKey をBase64 デコードしたバイト列で署名しているか。⑤ すべて合っているのに続く場合は、アカウントで API v2 が有効化されているか担当マネージャーに確認してください。jwt.io にトークンを貼ってペイロードを目で確認するのが最も早いです。
トークンはどのくらいの頻度で更新すべきですか。
サーバー間通信用なので短くする理由はありません。1〜3 年で作ってサーバー設定に保管し、期限前に差し替える運用手順だけ用意してください。期限切れは _s: -412 で通知されます。ただしトークンが漏れた場合は secretKey の再発行が必要になるので、フロントエンド・アプリのバンドルには絶対に入れないでください。
campaignKey と campaignId のどちらを保存すべきですか。
API のパスにはどちらも使えます。プレイヤー・SDK 連携と視聴 URL には campaignKey が使われるので、campaignKey を主キーとして保存し、campaignId は統計・ログとの照合用に併せて保管することを推奨します。
一覧 API に count=100 を指定したのに 20 件しか返りません。
配信一覧は 1 回のリクエストで最大 20 件まで取得されます(上限は API ごとに異なります)。totalCount を確認し、page を増やして順に取得してください。大量の同期は from/to で期間を分けると速くなります。
商品登録(POST)を再送したら FAIL が返りました。
同じ customerProductId がすでにあるためです。重複作成を防ぐ正常動作で、内容を変えるにはミッション 7の PUT を使ってください。POST → FAIL 項目だけを PUT で再試行するパターンが upsert です。
配信に商品を紐付けたら一部が FAIL で返りました。
message フィールドを確認してください。値が The product is already registered. の場合は、すでに紐付いている商品です。The product does not exist. の場合は、商品が登録されていないか customerProductId に誤字があります。紐付け API は追加方式なので成功した項目はそのまま反映され、順序は PUT .../product/order で別に決めます。
時刻を +09:00 で送ったのにレスポンスは Z で返ります。
正常です。入力はオフセット付きの ISO 8601 なら何でも受け付け、保存・レスポンスは UTC に統一されます。画面に表示する際に自社のタイムゾーンへ変換してください。
start を呼んで ONAIR になったのに、プレイヤーに映像が出ません。
start はステータスを変えるだけです。送出ツールが stream.streamUrl + stream.streamKey で実際に映像を送っているか確認してください。通常は送出を先に始め、コンソールのプレビューで確認してから start を呼びます。
終了直後に統計を取得したら 404 が返る、または値が小さいか 0 です。
終了後、集計が完了するまで数分かかることがあります。Webhook の終了イベントを受けたらすぐ取得せず数分後に予約するか、値が変わらなくなるまで数回再取得してください。
_s·_e の値を添えて、担当マネージャーまたは技術サポート窓口へ連絡してください。トークンや secretKey の原文は送らないでください。