メインコンテンツまでスキップ

プッシュ候補

非同期 API を通じて、ユーザーごとにパーソナライズされたプッシュ通知候補を生成します。

概要​

  1. POST /v1/push-candidates でジョブを送信します。
  2. API が候補を非同期で処理し、job_id を返します。
  3. GET /v1/push-candidates/{job_id} でステータスをポーリングするか、完了時に Webhook を受信します。
  4. 署名付き URL から JSONL 結果ファイルをダウンロードします。

ジョブの作成​

curl -X POST "https://api.feed.storyhub.studio/v1/push-candidates" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"scenario": "push_morning",
"candidates_per_user": 3
}'

candidates_per_user は 1 ユーザーあたりに生成される候補記事数です(デフォルト: 1、最大: 3)。

レスポンス:

{
"job_id": "job-uuid",
"status": "queued"
}

ジョブステータスの確認​

GET /v1/push-candidates/{job_id}
ステータス意味
queuedジョブはキューに入っています
processingジョブは実行中です
completed結果の準備が完了しました — download_url が提供されます
failedジョブが失敗しました — error フィールドを確認してください

完了すると、レスポンスに download_url(署名付き URL、有効期限 1 時間)が含まれます。

ステータスレスポンスの主要フィールド​

フィールド型出現条件説明
target_user_countinteger | nullオーディエンス解決後ジョブの対象ユーザー数。queued や解決前の processing / failed では未設定(null またはフィールド省略)。condition モードで合致 0 人の場合は 0(null と区別されます)。
failed_user_countinteger | nullstatus=completed のみ確定値対象ユーザーのうち候補生成がエラーで失敗したユーザー件数。queued / processing / failed では未設定。0 人完了時も 0 で null と区別されます。
candidates_generatedinteger | nullstatus=completed のみ確定値全ユーザー合算の生成候補数。queued / processing / failed では未設定(null またはフィールド省略)— ここで「候補生成に到達しなかった」ことを 0(合致ユーザーなしで完了)と区別できます。
consumed_creditsnumber常に存在(完了前は 0)確定したクレジット消費量。
filter_statsobject | nulltarget_mode=condition 完了時オーディエンスフィルタの内訳(後述)。target_mode=manual では null。
download_urlstring | nullstatus=completedJSONL のダウンロード URL(署名付き、有効期限あり)。

これらのフィールドは Webhook ペイロード(push_job.completed)と同じ値で、GET と Webhook の対称性が保たれます。

完了時のレスポンス例:

{
"job_id": "01HZX9...",
"status": "completed",
"progress": 100,
"target_user_count": 1234,
"failed_user_count": 3,
"candidates_generated": 3702,
"consumed_credits": 12.34,
"download_url": "https://storage.googleapis.com/...",
"download_url_expires_at": "2026-04-27T10:00:00Z",
"filter_stats": {
"total_audience": 5000,
"excluded_due_to_null_last_active": 200,
"excluded_due_to_last_active_days": 3500,
"excluded_due_to_registered_range": 66
},
"error": null
}
対象0人で完了する場合

target_mode=condition で条件解決の結果、対象ユーザーが 0 人になった場合も、ジョブは completed で完了します(failed にはなりません)。

  • status=completed、target_user_count=0、failed_user_count=0、candidates_generated=0、consumed_credits=0
  • 予約クレジットは解放されます(消費は 0)
  • 空の JSONL(0 行)に対する download_url が発行されます
  • 通常通り push_job.completed の Webhook が送信されます

filter_stats を確認することで、なぜ対象が 0 人になったか(last_active_days 除外など)を把握できます。

ダウンロード URL の使い方

download_url は認証情報が URL のクエリパラメーターに含まれた署名付き URL です。追加の認証ヘッダーは不要で、そのまま HTTP GET でダウンロードできます。

curl -o candidates.jsonl "DOWNLOAD_URL_FROM_RESPONSE"

有効期限が切れた場合は、GET /v1/push-candidates/{job_id} を再度呼び出すと新しい URL が発行されます。

結果フォーマット(JSONL)​

ダウンロードしたファイルの各行は JSON オブジェクトです。candidates 配列はスコア降順(先頭が最もスコアの高い記事)で並びます:

{"user_id": "user-123", "candidates": [{"id": "content-uuid", "title": "...", "url": "...", "thumbnail": {"url": "...", "width": 1024, "height": 512}, "source": {"name": "..."}, "published_at": "2025-01-15T08:00:00Z", "score": 0.95, "tracking_token": "eyJ..."}]}

候補が空の行​

candidates が空配列の行には、空である理由を示す empty_reason フィールドが付与されます。candidates が 1 件以上ある行にはこのフィールドは含まれません。

{"user_id":"200589","candidates":[],"empty_reason":"generation_failed"}
{"user_id":"99545","candidates":[],"empty_reason":"no_matching_content"}
empty_reason の値意味failed_user_count への集計
generation_failed候補生成処理がエラーで失敗した(自動再試行後も失敗)される
no_matching_content候補生成自体は成功したが、配信条件(スコア閾値・除外設定・配信対象コンテンツの有無など)を満たす記事が無かったされない

JSONL の行数は、対象ユーザー全員分(target_user_count と同数)が常に出力されます。候補が生成できなかったユーザーの行が省略されることはありません。

未知の empty_reason 値を受け取った場合

empty_reason には今後値が追加される可能性があります。未知の値を受け取った場合は generation_failed と同様に扱い、そのユーザーへの配信をスキップすることを推奨します。

送信済み記事の除外​

一度プッシュ候補として生成された記事は、同じユーザーへの以降のジョブでは候補から除外されます(アルゴリズム設定の「送信済み記事を除外」)。

  • 除外の判定は StoryHub Feed 側の生成記録に基づきます。クリックイベントの送信有無に関わらず、同じ記事が同じユーザーへ繰り返し候補になることを防ぎます。
  • この設定は管理画面のアルゴリズム設定(Pre-ranking セクション)で ON/OFF を切り替えられます。新規に作成したプッシュ用アルゴリズムでは既定で ON です(既存のアルゴリズムは自動では変更されません)。
  • 除外が適用されるのは、本機能のリリース以降に生成された候補の記録に対してのみです。それ以前に送信した記事は除外対象になりません。

プッシュクリックのトラッキング​

プッシュ記事のクリックを報告する際は、JSONL 出力の tracking_token を含めてください:

POST /v1/events
{
"events": [
{
"type": "click",
"session_id": "...",
"tracking_token": "eyJ... (from push candidates JSONL)",
"occurred_at": "..."
}
]
}

プッシュクリックをトラッキングするメリット:

  • プッシュ経由でクリックされた記事は、以降のフィードリクエストから除外されます(重複排除)。
  • クリックシグナルにより、将来のレコメンデーションが改善されます。
  • アナリティクスでプッシュ通知のエンゲージメントを測定できます。
tracking_token について

tracking_token はフィード取得(GET /v1/feed)と Push 候補ファイル(JSONL)に含まれます。一方、GET /v1/contents/{content_id}(単体取得)にはランキング文脈がないため、tracking_token は含まれません。

Webhooks​

テナントに webhook_url が設定されている場合、ジョブ完了時に通知が送信されます:

  • completed または failed ステータス時に配信されます。
  • 検証用の X-StoryHub-Signature ヘッダー(HMAC-SHA256)が含まれます。
  • ベストエフォート配信で、最大 3 回リトライされます。

Webhook ペイロード​

{
"event": "push_job.completed",
"job_id": "...",
"tenant_id": "...",
"scenario_id": "...",
"status": "completed",
"target_user_count": 1234,
"failed_user_count": 3,
"candidates_generated": 3702,
"consumed_credits": 12.34,
"download_url": "https://...",
"download_url_expires_at": "...",
"filter_stats": {
"total_audience": 5000,
"excluded_due_to_null_last_active": 200,
"excluded_due_to_last_active_days": 3500,
"excluded_due_to_registered_range": 66
},
"completed_at": "..."
}

filter_stats(target_mode=condition のみ)​

target_mode=condition の場合、filter_stats でフィルタの内訳を確認できます。各カウンタは独立しており、複数の条件で除外されたユーザーはそれぞれのカウンタに重複してカウントされます。

フィールド説明
total_audienceテナントの総オーディエンス数(フィルタ前)
excluded_due_to_null_last_activelast_active_days が指定されているが last_active_at が NULL のため除外された数
excluded_due_to_last_active_dayslast_active_at が cutoff(now − last_active_days)より古いため除外された数
excluded_due_to_registered_rangeregistered_from / registered_to の範囲外のため除外された数

target_mode=manual の場合、filter_stats は null です。同じ値が GET /v1/push-candidates/{job_id} のレスポンスにも含まれます。

target_user_count / failed_user_count / candidates_generated も Webhook と GET レスポンスの両方で公開されており、両系統での集計を一致させることができます。failed_user_count は候補生成がエラーで失敗したユーザー数を表します(成功して候補 0 件の場合は失敗扱いになりません)。これらのユーザーは JSONL 上で empty_reason: "generation_failed" の行として出力されます。

制約​

制限値
テナントあたりの最大同時実行ジョブ数5
実行時間(10 万ユーザー)10 分未満
ダウンロード URL の有効期限1 時間
リクエストボディサイズ8 MiB
user_ids の最大件数(manual モード)100,000 件
1 件あたりの user_id の長さ128 文字以下
専用レート制限1 RPS / burst 5

user_ids 件数や本文サイズが上限を超える場合のレスポンス:

  • 件数超過 → 400 Validation Error (user_ids: must not exceed 100000 entries)
  • ボディサイズ超過 → 413 Payload Too Large

1 件あたりの user_id が長い場合(例えば 128 文字に近い長さ)、件数上限に達する前にボディサイズ上限 8 MiB に当たる可能性があります。100,000 件を超えるユーザーへ配信したい場合は、リクエストを複数バッチに分割するか、target_mode=condition の活用を検討してください。

condition モード(推奨)​

ユーザー絞り込み条件で対象を選びます。

POST /v1/push-candidates
{
"scenario": "push_morning",
"target_mode": "condition",
"conditions": { "last_active_days": 7 }
}
条件説明
last_active_days指定日数以内に session_start 等のイベントがあったユーザーのみを対象とします。
last_active_days(NULL の挙動)last_active_at が未設定(一度もイベント送信が無い)ユーザーは、本フィルタ適用時は対象外となります。
last_active_days(未指定時)本フィールドを省略するとフィルタは無効化され、活動記録の無いユーザーも含めて全員が対象となります。
registered_from / registered_toregistered_at(PUT /v1/users で送信した値、または初回イベント時刻)の範囲で絞り込みます。
休眠ユーザーへの配信

復帰促進など、まだ一度もアクセスしていないユーザーを含めたい場合は、last_active_days を省略してください。条件を省略すると「絞り込みなし=全ユーザー」となります。

テスト用: manual モード​

特定のユーザーを指定して候補を生成できます。開発・テスト時に便利です。

POST /v1/push-candidates
{
"scenario": "YOUR_PUSH_SCENARIO",
"target_mode": "manual",
"user_ids": ["user-12345", "user-67890"]
}

user_ids の各エントリは、/v1/events の user_id と同じ文字種ルール(A-Za-z0-9._:@|+=-、最大 128 文字)に従う必要があります。違反すると 400 Validation Error を返します。

クレジット​

プッシュ候補の生成は、候補が生成されたユーザーごとにクレジットを消費します。詳細は クレジット使用量 をご覧ください。

JSONL 上で candidates が空配列となる行(empty_reason が generation_failed または no_matching_content の行)はクレジット消費の対象外です。このうち generation_failed の行は、Webhook ペイロードおよび GET レスポンスの failed_user_count に集計されます。確定消費量は consumed_credits フィールドおよび Webhook ペイロードで確認できます。