Client SDK キー(osk_ プレフィックス)で認証します。レコメンド実行は非同期(202) + ポーリング構造です。
フロー
1. POST /v1/profile/{profile_id}/session/start (プロフィールセッション開始 → session_id 発行, stamp=READY)
2. POST /v1/chat/{profile_id} (チャットセッション作成 → chat_id 発行)
3. POST /v1/chat/{profile_id}/recommend (kickoff → result: true)
4. GET /v1/chat/{profile_id}/recommend/{chat_id} (0.5 秒間隔でポーリング)
5. DELETE /v1/chat/{profile_id}/end/{chat_id} (チャット終了、ユーザー状態変更)
クライアントは profile_id と 1 番で受け取った session_id (2 番では chat_id にリネームされて返却、UUID は同一) を保持すれば OK。認証セッションと混同しないよう /chat エンドポイントでは chat_id キーで統一しています。name / gender / age / locale はサーバがプロフィール行から読み取り、自動で LLM プレリュードを組み立てます。
セッションステータス
ポーリング制御用。
| 値 | 意味 | クライアント動作 |
|---|
in_progress | パイプライン実行中 | 0.5 秒後に再ポーリング |
completed | レコメンド完了 (todos / notification_items 添付) | 消費してポーリング終了 |
error | パイプライン失敗 | 再 kickoff もしくはセッション終了 |
1. チャットセッション作成
POST /v1/chat/{profile_id}
リクエスト body は不要。プロフィールに登録済みの name / gender / age / locale をサーバが読み取って LLM の user_query プレリュードを自動生成します。
前提: POST /v1/profile/{profile_id}/session/start で プロフィールセッションを先に開始 しておく必要があります。未開始だと SESSION_NOT_STARTED 409。
レスポンス 200
{
"success": true,
"data": {
"chat_id": "a14b2f9c-3d5e-4f2a-9a8b-1c2d3e4f5a6b"
}
}
chat_id はプロフィールセッション ID (/v1/profile/{profile_id}/session/start の session_id) と同じ UUID。以降の recommend/poll/delete 全てでそのまま使用します。現在の stamp 状態が必要なら GET /v1/profile/{profile_id}/session を呼んでください。
2. レコメンド kickoff
POST /v1/chat/{profile_id}/recommend
前提: プロフィールセッションが アクティブ + stamp=READY であること (/session/start 直後のデフォルト)。CHAT など他スタンプでは STAGE_NOT_READY 409、終了済みセッションでは SESSION_INACTIVE 409 で拒否されます。
リクエスト
{
"chat_id": "a14b2f9c-3d5e-4f2a-9a8b-1c2d3e4f5a6b",
"prompt": "今夜のおかず何がいい?"
}
| フィールド | 型 | 必須 | 説明 |
|---|
chat_id | string | ○ | 2 番で受け取ったチャットセッション ID |
prompt | string | ○ | ユーザー問い合わせ。内部で llm-demo の user_query にマッピング |
レスポンス 200
{
"success": true,
"data": { "result": true }
}
result: true はレコメンドパイプラインの kickoff 成功を示します。結果は ポーリングエンドポイント (GET /v1/chat/{profile_id}/recommend/{chat_id}) で取得してください。失敗時は success: false + エラーコードで返り、result フィールドは含まれません。
3. ポーリング
GET /v1/chat/{profile_id}/recommend/{chat_id}
推奨間隔 0.5 秒。レスポンスは status によって形が変わります。読み取り専用 — ステージ遷移は DELETE のみ。
既に終了したチャット (DELETE 済み or llm-demo 1 時間 TTL 経過) をポーリングすると NOT_FOUND 404 (chat session ended)。
status: "in_progress"
{
"success": true,
"data": {
"created_at": "2026-04-23T05:28:08.056493+00:00",
"updated_at": "2026-04-23T05:29:10.001000+00:00",
"status": "in_progress",
"back_list_status": "in_progress",
"todos": null,
"recommended_items": null,
"notification_items": null
}
}
ポーリング継続。todos / notification_items はパイプライン確定前は null。
status: "completed" (完了)
{
"success": true,
"data": {
"created_at": "2026-04-23T05:28:08.056493+00:00",
"updated_at": "2026-04-23T05:29:30.922285+00:00",
"status": "completed",
"back_list_status": "completed",
"todos": {
"recipe_title": "매콤 소고기 카레",
"intro_message": "오늘 메뉴는 매콤 소고기 카레 어떠세요? 제가 장보기 목록을 정리해 드렸어요.",
"recipe_description": "일본식 카레. 소고기 + 양파 + 감자를 기본으로 카레 루를 녹여 20분 끓이면 완성.",
"items": [
{
"quantity": 1,
"product": {
"id": 196,
"name": "소 목심 얇은썰기 300g",
"barcode": "4907065390564",
"price": 5900,
"price_unit": "₩",
"section_code": "MT",
"section_name": "육류",
"src_url": "https://cdn.ones1ght.com/store/product/no_image.png"
}
}
],
"total": 5900
},
"recommended_items": [
{
"reason": "카레 재료와 동시구매율이 높은 상품입니다 (lift 4.2, 동시구매 확률 16.9%). 현재 쇼핑 리스트의 카레 루, 양파와 함께 구매하면 조리 완성도가 높아집니다.",
"product": {
"id": 512,
"name": "카베르네 소비뇽 750ml",
"barcode": "4901234567890",
"price": 12000,
"price_unit": "₩",
"section_code": "AL",
"section_name": "주류",
"src_url": "https://cdn.ones1ght.com/store/product/no_image.png"
}
}
],
"notification_items": [
{
"discount_pct": 5,
"priority_score": 0.95,
"rank": 1,
"product": {
"id": 860,
"name": "히게타 간장 1L",
"barcode": "4906244055584",
"price": 4400,
"price_unit": "₩",
"section_code": "PA",
"section_name": "식료품",
"src_url": "https://cdn.ones1ght.com/store/product/no_image.png"
}
}
]
}
}
完了判定: status == "completed"。
todos — 買い物リスト (旧 shopping_list)。recipe_title / intro_message / recipe_description は llm-demo meal 出力から取得 — intro_message が上段チャット吹き出し用のフック、recipe_description はカード本文。各 items[*] は {quantity, product} に圧縮、store マッチ不成立の要素は除外、total は Σ(product.price × quantity)。
recommended_items — メニューに合う cross-sell (llm-demo upsell_surface.surface_recommendations 原本、通常 3~5個)。各要素は {reason, product} — reason は llm-demo reason_detail 原文 (プロンプト契約上 lift / confidence 等の数値を含む) をそのまま。iOS が一度に 1 個レンダリング、「추천받기 / もっと見る」ボタンはクライアント側でカート遷移として処理。
notification_items — 店内ゾーン通知候補 (back_list.back_recommendations 原本)。各要素 {discount_pct, priority_score, rank, product} — 識別子は product.id のみ。rank は priority_score 降順 1-indexed、store マッチ不成立の要素は除外。product に将来の追加フィールド (座標等) が入ってもそのまま公開。
status: "error"
{
"success": true,
"data": {
"created_at": "...",
"updated_at": "...",
"status": "error",
"back_list_status": "not_started",
"last_error": "meal_suggest failed: upstream timeout",
"todos": null,
"recommended_items": null,
"notification_items": null
}
}
last_error は llm-demo パイプライン失敗理由 (例: meal_suggest failed: upstream timeout)。error 時のみ含まれ、success / in-progress 応答には出ません。再 kickoff もしくはセッション終了。
備考: llm-demo 内部項目 (conversation_state / preferences / profile_context / meal / upsell_surface / timing) はレスポンスに含まれません。
4. チャット終了、ユーザー状態変更
DELETE /v1/chat/{profile_id}/end/{chat_id}
DELETE /v1/chat/{profile_id}/end/{chat_id}?next_session_status=PAYMENT
llm-demo チャットセッションは常にクローズ。プロフィールセッション はクエリパラメータで分岐:
DELETE = チャット終了 + 次ステージへの移行。どちらの変形でも llm-demo チャットセッションはクローズされます。
next_session_status | 動作 |
|---|
| 未指定 | プロフィールセッションに自動 COMPLETE スタンプ + 全体終了 (アクティブスレッド整理、is_active=false、stamps 履歴返却) |
| 値あり | カスタムスタンプを記録後、プロフィールセッションは 活性維持 (次ステージへ移行) |
プロフィールセッションのライフサイクル — アプリ起動から決済完了までの 1 サイクル:
READY → CHAT → [custom, custom, ...] → COMPLETE
↑ ↑ ↑ ↑
session recommend DELETE ?next_session_status= DELETE (no param)
/start kickoff X (自由ラベル) 自動 COMPLETE + セッション終了
予約語:
READY — /session/start 直後に自動付与
CHAT — /recommend kickoff で自動付与
COMPLETE — DELETE (no param) で自動付与、サイクル終結
この 3 つを ?next_session_status に指定すると RESERVED_STAMP 409。CHAT ~ COMPLETE の間はクライアントが自由にラベル (SHOPPING, PAYMENT など) を付けて、アプリクラッシュからの復帰時に画面復元に使用します。
レスポンス 200 — パラメータ未指定 (セッション終了)
{
"success": true,
"data": {
"chat_id": "...",
"profile_id": "...",
"started_at": "2026-04-23T05:28:00Z",
"ended_at": "2026-04-23T05:35:00Z",
"stamps": [
{ "status": "READY", "timestamp": "2026-04-23T05:28:00Z" },
{ "status": "CHAT", "timestamp": "2026-04-23T05:29:05Z" },
{ "status": "PAYMENT", "timestamp": "2026-04-23T05:33:00Z" },
{ "status": "COMPLETE", "timestamp": "2026-04-23T05:35:00Z" }
]
}
}
レスポンス 200 — ?next_session_status=PAYMENT (スタンプ)
{
"success": true,
"data": {
"chat_id": "...",
"status": "PAYMENT",
"timestamp": "2026-04-23T05:32:00Z"
}
}
明示的な DELETE がなくても 1 時間非活動 で自動終了。
エラーコード
| HTTP | code | 原因 |
|---|
| 400 | INVALID_REQUEST | body バリデーション失敗 |
| 404 | NOT_FOUND | profile / session が存在しない or 終了済み |
| 409 | SESSION_NOT_STARTED | プロフィールセッション未開始でチャットセッション作成を試みた |
| 409 | SESSION_INACTIVE | 終了済みセッションに対して recommend を呼び出した |
| 409 | STAGE_NOT_READY | recommend 呼び出し時にスタンプが READY ではない |
| 500 | INTERNAL_ERROR | アップストリーム障害等 |
フル例
SDK_KEY="osk_..."
PROFILE_ID="..."
# 1. プロフィールセッション開始
CHAT_ID=$(curl -s -X POST https://api.ones1ght.com/v1/profile/$PROFILE_ID/session/start \
-H "Authorization: Bearer $SDK_KEY" | jq -r '.data.session_id') # same UUID as chat_id
# 2. LLM チャットセッション作成 (body なし)
curl -X POST https://api.ones1ght.com/v1/chat/$PROFILE_ID \
-H "Authorization: Bearer $SDK_KEY"
# 3. レコメンド kickoff ({ "result": true } を確認できたらポーリングへ)
curl -X POST https://api.ones1ght.com/v1/chat/$PROFILE_ID/recommend \
-H "Authorization: Bearer $SDK_KEY" \
-H "Content-Type: application/json" \
-d "{\"chat_id\":\"$CHAT_ID\",\"prompt\":\"今夜のおかず何がいい?\"}"
# 4. ポーリング (0.5s)
while true; do
RESP=$(curl -s https://api.ones1ght.com/v1/chat/$PROFILE_ID/recommend/$CHAT_ID \
-H "Authorization: Bearer $SDK_KEY")
STATUS=$(echo "$RESP" | jq -r '.data.status')
[ "$STATUS" = "completed" ] && { echo "$RESP" | jq '.data'; break; }
[ "$STATUS" = "error" ] && { echo "$RESP" | jq '.data'; break; }
sleep 0.5
done
# 5. 終了
curl -X DELETE https://api.ones1ght.com/v1/chat/$PROFILE_ID/session/$CHAT_ID \
-H "Authorization: Bearer $SDK_KEY"