{"openapi":"3.1.0","info":{"title":"uEngage Upsell API","version":"0.1.0","description":"Channel-aware upsell placements. GET /placements returns which upsell surfaces a channel should render for a tenant; POST /suggestions returns the items for one placement given the live cart, resolved through the touchpoint policy and the master resolution ladder; POST /events reports attach-rate telemetry. Placement config (per platform / brand / business) is managed via the service-actor-only /admin surface.","license":{"name":"Proprietary"}},"servers":[{"url":"https://api.platform.uengage.io","description":"Production"}],"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"OAuth2 Bearer JWT, RS256-signed by the auth service. Verify against the JWKS at `/auth/business/jwks.json`. Service-to-service callers mint via `POST /auth/business/oauth/token` with `grant_type=client_credentials`."}},"schemas":{"Channel":{"type":"string","enum":["mobile_app","kiosk","whitelabel_web","whatsapp","voice"]},"Touchpoint":{"type":"string","enum":["home","menu_browsing","menu_composing","search_no_result","cart","pre_checkout","post_order"]},"Layout":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9_]*$","default":"horizontal_carousel","description":"Opaque rendering hint owned by the channel frontend; the platform never interprets it. Known values: carousel_2up, horizontal_carousel, grid_2xn_modal, carousel_1up, vertical_list, grid_3xn, compact_list, hero_banner. New layouts require no platform change.","example":"carousel_2up"},"PlacementDisplay":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120,"example":"You may also like"},"maxItems":{"type":"integer","minimum":1,"maximum":12,"default":6},"layout":{"$ref":"#/components/schemas/Layout"}},"required":["title"]},"Placement":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9_-]*$","example":"menu_add_item"},"channel":{"$ref":"#/components/schemas/Channel"},"touchpoint":{"$ref":"#/components/schemas/Touchpoint"},"enabled":{"type":"boolean"},"display":{"$ref":"#/components/schemas/PlacementDisplay"},"configVersion":{"type":"string"}},"required":["key","channel","touchpoint","enabled","display","configVersion"]},"PlacementsResponse":{"type":"object","properties":{"placements":{"type":"array","items":{"$ref":"#/components/schemas/Placement"}}},"required":["placements"]},"ApiError":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"issues":{"type":"array","items":{"nullable":true}}},"required":["error"],"description":"Standard error envelope. `error` is a stable machine-readable code; `message` is a human-readable hint; `issues` (when present) is a Zod validation issue list."},"ReasonCode":{"type":"string","enum":["popular_now","completes_meal","often_ordered_together","your_usual","chefs_pick"]},"UpsellItem":{"type":"object","properties":{"itemId":{"type":"integer"},"itemName":{"type":"string"},"itemSlug":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"sectionId":{"type":"integer","nullable":true},"sectionName":{"type":"string","nullable":true},"price":{"type":"number"},"image":{"type":"string","nullable":true},"veg":{"type":"number","nullable":true},"brandId":{"type":"number","nullable":true},"reasonCode":{"$ref":"#/components/schemas/ReasonCode"}},"required":["itemId","itemName","itemSlug","description","sectionId","sectionName","price","image","veg","brandId","reasonCode"]},"Strategy":{"type":"string","enum":["menu-order-popularity","legacy-catalog-sample"]},"LadderRung":{"type":"string","enum":["full_engine","band_relaxation","popularity_floor","backup_suggestions","empty_degraded"]},"SuggestionsResponse":{"type":"object","properties":{"placement":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9_-]*$"},"touchpoint":{"$ref":"#/components/schemas/Touchpoint"},"items":{"type":"array","items":{"$ref":"#/components/schemas/UpsellItem"}},"meta":{"type":"object","properties":{"enabled":{"type":"boolean"},"strategy":{"$ref":"#/components/schemas/Strategy"},"configVersion":{"type":"string"},"rung":{"$ref":"#/components/schemas/LadderRung"},"maxItems":{"type":"integer","minimum":0},"ttlSeconds":{"type":"integer","minimum":0},"degraded":{"type":"boolean"}},"required":["enabled","strategy","configVersion","rung","maxItems","ttlSeconds"]}},"required":["placement","touchpoint","items","meta"]},"Tenant":{"type":"object","properties":{"parentId":{"type":"integer","minimum":0,"exclusiveMinimum":true,"example":5},"businessId":{"type":"integer","minimum":0,"exclusiveMinimum":true,"example":7175}},"required":["parentId","businessId"]},"Cart":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"itemId":{"type":"integer"},"qty":{"type":"integer","minimum":0,"exclusiveMinimum":true},"unitPrice":{"type":"number","minimum":0},"itemSlug":{"type":"string"},"sectionName":{"type":"string"},"veg":{"type":"integer"}},"required":["itemId","qty","unitPrice"]},"maxItems":200},"subtotal":{"type":"number","minimum":0}},"required":["items","subtotal"]},"SuggestionsRequest":{"type":"object","properties":{"channel":{"$ref":"#/components/schemas/Channel"},"placement":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9_-]*$"},"tenant":{"$ref":"#/components/schemas/Tenant"},"context":{"type":"object","properties":{"cart":{"$ref":"#/components/schemas/Cart"},"fulfilmentMode":{"type":"string","enum":["delivery","pickup","dine_in"]},"customer":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":64}},"required":["id"]},"sessionId":{"type":"string","minLength":1,"maxLength":128}},"required":["cart"]}},"required":["channel","placement","tenant","context"]},"UpsellEventsResponse":{"type":"object","properties":{"accepted":{"type":"integer","minimum":0}},"required":["accepted"]},"UpsellEventType":{"type":"string","enum":["shown","added","ignored"]},"UpsellEvent":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/UpsellEventType"},"itemId":{"type":"integer"},"reasonCode":{"$ref":"#/components/schemas/ReasonCode"},"occurredAt":{"type":"string","format":"date-time"}},"required":["type","itemId"]},"UpsellEventsRequest":{"type":"object","properties":{"channel":{"$ref":"#/components/schemas/Channel"},"placement":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9_-]*$"},"tenant":{"$ref":"#/components/schemas/Tenant"},"sessionId":{"type":"string","minLength":1,"maxLength":128},"configVersion":{"type":"string","maxLength":128},"events":{"type":"array","items":{"$ref":"#/components/schemas/UpsellEvent"},"minItems":1,"maxItems":50}},"required":["channel","placement","tenant","events"]},"StrategyParams":{"type":"object","properties":{"includedItemSlugs":{"type":"array","items":{"type":"string","minLength":1},"maxItems":200},"includedSections":{"type":"array","items":{"type":"string","minLength":1},"maxItems":50},"excludedSections":{"type":"array","items":{"type":"string","minLength":1},"maxItems":50},"neverSuggestItemSlugs":{"type":"array","items":{"type":"string","minLength":1},"maxItems":500},"priorityItemSlugs":{"type":"array","items":{"type":"string","minLength":1},"maxItems":50},"backupItemSlugs":{"type":"array","items":{"type":"string","minLength":1},"maxItems":6},"excludeCartItems":{"type":"boolean","default":true},"maxPrice":{"type":"number","minimum":0,"exclusiveMinimum":true}}},"PlacementRecord":{"type":"object","properties":{"scope":{"type":"string","enum":["default","tenant","business"]},"parentId":{"type":"integer","minimum":0,"exclusiveMinimum":true},"businessId":{"type":"integer","minimum":0,"exclusiveMinimum":true},"channel":{"$ref":"#/components/schemas/Channel"},"key":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9_-]*$"},"touchpoint":{"$ref":"#/components/schemas/Touchpoint"},"enabled":{"type":"boolean"},"display":{"$ref":"#/components/schemas/PlacementDisplay"},"strategy":{"$ref":"#/components/schemas/Strategy"},"strategyParams":{"$ref":"#/components/schemas/StrategyParams"},"configVersion":{"type":"string"},"updatedAt":{"type":"string"},"updatedBy":{"type":"string"}},"required":["scope","channel","key","touchpoint","enabled","display","strategy","strategyParams","configVersion","updatedAt","updatedBy"]},"PlacementUpsertRequest":{"type":"object","properties":{"parentId":{"type":"integer","minimum":0,"exclusiveMinimum":true},"businessId":{"type":"integer","minimum":0,"exclusiveMinimum":true},"channel":{"$ref":"#/components/schemas/Channel"},"key":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9_-]*$"},"touchpoint":{"$ref":"#/components/schemas/Touchpoint"},"enabled":{"type":"boolean"},"display":{"$ref":"#/components/schemas/PlacementDisplay"},"strategy":{"allOf":[{"$ref":"#/components/schemas/Strategy"},{"default":"menu-order-popularity"}]},"strategyParams":{"allOf":[{"$ref":"#/components/schemas/StrategyParams"},{"default":{"excludeCartItems":true}}]}},"required":["channel","key","touchpoint","enabled","display"]}},"parameters":{}},"paths":{"/v1/upsell/placements":{"get":{"tags":["upsell"],"summary":"List upsell placements for a channel + tenant","security":[{"bearer":[]}],"parameters":[{"schema":{"$ref":"#/components/schemas/Channel"},"required":true,"name":"channel","in":"query"},{"schema":{"type":"integer","minimum":0,"exclusiveMinimum":true,"example":5},"required":true,"name":"parentId","in":"query"},{"schema":{"type":"integer","minimum":0,"exclusiveMinimum":true,"example":7175},"required":true,"name":"businessId","in":"query"}],"responses":{"200":{"description":"Resolved placements (business override > brand > platform default)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlacementsResponse"}}}},"400":{"description":"invalid query","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"missing or invalid bearer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/upsell/suggestions":{"post":{"tags":["upsell"],"summary":"Get upsell items for a placement","security":[{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestionsRequest"}}}},"responses":{"200":{"description":"Ranked upsell items. `items` is empty when the placement is disabled, the ladder bottomed out, or the service degraded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestionsResponse"}}}},"400":{"description":"invalid body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"missing or invalid bearer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"placement not configured for this channel + tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/upsell/events":{"post":{"tags":["upsell"],"summary":"Report upsell impressions and conversions","description":"Report shown / added / ignored events for a rendered placement. Fire-and-forget: always 202 on a well-formed batch. Feeds attach-rate measurement and weight tuning.","security":[{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsellEventsRequest"}}}},"responses":{"202":{"description":"events accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsellEventsResponse"}}}},"400":{"description":"invalid body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"missing or invalid bearer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/upsell/admin/placements":{"get":{"tags":["upsell-admin"],"summary":"List raw placement rows for a tenant (or the platform defaults)","description":"Returns the unresolved rows of one config partition: brand rows plus every business override when parentId is set; the platform defaults when omitted.","security":[{"bearer":[]}],"parameters":[{"schema":{"type":"integer","minimum":0,"exclusiveMinimum":true},"required":false,"name":"parentId","in":"query"}],"responses":{"200":{"description":"raw config rows (no precedence merge applied)","content":{"application/json":{"schema":{"type":"object","properties":{"placements":{"type":"array","items":{"$ref":"#/components/schemas/PlacementRecord"}}},"required":["placements"]}}}},"401":{"description":"missing or invalid bearer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"caller is not a service-actor, or lacks the required capability","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"put":{"tags":["upsell-admin"],"summary":"Create or replace a placement config row","security":[{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlacementUpsertRequest"}}}},"responses":{"200":{"description":"saved row, with a fresh configVersion","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlacementRecord"}}}},"400":{"description":"invalid body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"missing or invalid bearer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"caller is not a service-actor, or lacks the required capability","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"tags":["upsell-admin"],"summary":"Delete a placement config row","security":[{"bearer":[]}],"parameters":[{"schema":{"type":"integer","minimum":0,"exclusiveMinimum":true},"required":false,"name":"parentId","in":"query"},{"schema":{"type":"integer","minimum":0,"exclusiveMinimum":true},"required":false,"name":"businessId","in":"query"},{"schema":{"$ref":"#/components/schemas/Channel"},"required":true,"name":"channel","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9_-]*$"},"required":true,"name":"key","in":"query"},{"schema":{"type":"boolean","nullable":true},"required":false,"name":"confirmDeleteDefault","in":"query"}],"responses":{"204":{"description":"deleted"},"400":{"description":"invalid query","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"missing or invalid bearer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"caller is not a service-actor, or lacks the required capability","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"no such placement row","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}}}}