Authenticated with the Client SDK key (osk_ prefix). Recommend runs async (202) + polling.
Flow
1. POST /v1/profile/{profile_id}/session/start (start Profile session → session_id)
2. POST /v1/chat/{profile_id} (create LLM session; no body)
3. POST /v1/profile/{profile_id}/session/stamp (status="CHAT" stamp)
4. POST /v1/chat/{profile_id}/recommend (kickoff, 202)
5. GET /v1/chat/{profile_id}/recommend/{session_id} (poll at 0.5s intervals)
6. DELETE /v1/chat/{profile_id}/session/{session_id} (end session)
The client only tracks profile_id and the session_id returned from step 1. name / gender / age / locale are read server-side from the profile row and turned into the LLM prelude automatically.
Session status
Drives polling.
| Value | Meaning | Client action |
|---|
CHAT | Pipeline running | Re-poll after 0.5s |
READY | Recommendation attached | If back_list_status == "completed" → consume result |
ERROR | Pipeline failed | Check last_error |
ENDED | Session ended (DELETE or 1h idle) | 404 — create a new session |
1. Create chat session
POST /v1/chat/{profile_id}
No request body. The server reads name / gender / age / locale directly from the profile row (populated at /v1/profile registration) and prepends a natural-language block to the LLM user_query.
Prerequisite: POST /v1/profile/{profile_id}/session/start must have been called first. Otherwise → SESSION_NOT_STARTED 409.
Response 200
{
"success": true,
"data": {
"session_id": "a14b2f9c-3d5e-4f2a-9a8b-1c2d3e4f5a6b",
"profile_id": "...",
"query_results": {
"session": { "session_id": "...", "status": "READY", "back_list_status": "not_started" }
}
}
}
session_id equals the Profile session id and is reused across all subsequent calls.
2. Kickoff a recommendation
POST /v1/chat/{profile_id}/recommend
Prerequisite: the session’s latest stamp must be CHAT. Any other stage → STAGE_NOT_CHAT 409. Set the stamp via POST /v1/profile/{profile_id}/session/stamp.
Request
{
"session_id": "a14b2f9c-3d5e-4f2a-9a8b-1c2d3e4f5a6b",
"prompt": "What should I cook for dinner?"
}
| Field | Type | Required | Description |
|---|
session_id | string | yes | Session id from step 1 |
prompt | string | yes | Natural-language query. Mapped to llm-demo’s user_query internally |
Response 202
{
"success": true,
"data": {
"session": { "session_id": "...", "status": "CHAT", "back_list_status": "not_started" }
}
}
Re-kickoff while still CHAT → 409 Conflict.
3. Poll
GET /v1/chat/{profile_id}/recommend/{session_id}
Recommended interval: 0.5s. Response shape depends on status.
status: "CHAT" (in flight)
{
"success": true,
"data": {
"session_id": "...",
"status": "CHAT",
"back_list_status": "not_started",
"updated_at": "...",
"last_error": null
}
}
Keep polling.
status: "READY" (done)
{
"success": true,
"data": {
"session_id": "...",
"status": "READY",
"back_list_status": "completed",
"updated_at": "...",
"last_error": null,
"result": {
"shopping_list": {
"items": [
{
"item_name_en": "...",
"price_yen": 380,
"zone_name": "...",
"id": 1,
"matched": true,
"product": {
"id": 1,
"name": "라무네 캔디 29g",
"barcode": "4909254512030",
"price": 1400,
"price_unit": "₩",
"section_code": "CK",
"section_name": "계산대",
"src_url": "https://cdn.ones1ght.com/store/product/no_image.png"
}
}
],
"total_yen": 2450
},
"back_list": {
"back_recommendations": [
{
"recommendation_id": "...",
"item_name_en": "...",
"price_yen": 180,
"reason_type": "copurchase",
"store_reasoning": { "method": "copurchase_lift", "scoring_equation": "..." }
}
]
}
}
}
}
Completion check: status == "READY" AND back_list_status == "completed".
status: "ERROR"
{
"success": true,
"data": {
"session_id": "...",
"status": "ERROR",
"back_list_status": "not_started",
"last_error": "upstream timeout",
"result": { "shopping_list": null, "back_list": null }
}
}
Inspect last_error, then either kickoff again or end the session.
Note: llm-demo internals — conversation_state / preferences / profile_context / meal / upsell_surface / timing — are not included in the response.
4. End session
DELETE /v1/chat/{profile_id}/session/{session_id}
Response 200
{
"success": true,
"data": {
"session_id": "...",
"ended": true,
"ended_at": "2026-04-22T09:05:00Z",
"reason": "explicit"
}
}
Sessions auto-end after 1 hour of inactivity even without an explicit DELETE.
Error codes
| HTTP | code | Cause |
|---|
| 400 | INVALID_REQUEST | Body validation failed |
| 404 | NOT_FOUND | Profile / session missing or ended |
| 409 | SESSION_NOT_STARTED | Chat session create attempted before Profile session start |
| 409 | STAGE_NOT_CHAT | Recommend called while stamp isn’t CHAT |
| 409 | CONFLICT | Session already has a CHAT recommendation in flight |
| 500 | INTERNAL_ERROR | Upstream failure |
Full example
SDK_KEY="osk_..."
PROFILE_ID="..."
# 1. Start profile session
SESSION_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')
# 2. Create LLM chat session (no body)
curl -X POST https://api.ones1ght.com/v1/chat/$PROFILE_ID \
-H "Authorization: Bearer $SDK_KEY"
# 3. Stamp CHAT
curl -X POST https://api.ones1ght.com/v1/profile/$PROFILE_ID/session/stamp \
-H "Authorization: Bearer $SDK_KEY" \
-H "Content-Type: application/json" \
-d '{"status":"CHAT"}'
# 4. Kickoff
curl -X POST https://api.ones1ght.com/v1/chat/$PROFILE_ID/recommend \
-H "Authorization: Bearer $SDK_KEY" \
-H "Content-Type: application/json" \
-d "{\"session_id\":\"$SESSION_ID\",\"prompt\":\"Dinner ideas?\"}"
# 5. Poll (0.5s)
while true; do
RESP=$(curl -s https://api.ones1ght.com/v1/chat/$PROFILE_ID/recommend/$SESSION_ID \
-H "Authorization: Bearer $SDK_KEY")
STATUS=$(echo "$RESP" | jq -r '.data.status')
[ "$STATUS" = "READY" ] && { echo "$RESP" | jq '.data.result'; break; }
[ "$STATUS" = "ERROR" ] && { echo "$RESP" | jq '.data.last_error'; break; }
sleep 0.5
done
# 6. End
curl -X DELETE https://api.ones1ght.com/v1/chat/$PROFILE_ID/session/$SESSION_ID \
-H "Authorization: Bearer $SDK_KEY"