# QuitSure Assistant — API (for the app / frontend team)

A single stateless HTTP endpoint. You send a user's message, you get back an answer and a
decision about whether to hand the chat to a human.

**We (AI team) own:** this service, the answer and the escalate decision.
**You (app / devs) own:** the chat UI, calling this endpoint, and, when `escalate` is true,
routing the conversation to a human (e.g. the Freshchat coach inbox). This service never touches
Freshchat itself.

> Status: running locally at `http://localhost:8082` for now. Hosted at
> `https://tweb.quitsure.app/support-ai/` once deployed. The contract below will not change.

**Auth:** on the server, every request must send a shared secret as the `X-API-Key` header
(we give you the key). Without it you get `401`. `/health` is open. Locally (no key configured) it
is open too.

---

## POST /chat

### Request
```json
{
  "message": "how do I cancel my subscription?",
  "user_id": "12345",
  "history": [
    { "role": "user",      "text": "hi" },
    { "role": "assistant", "text": "Hi, how can I help?" }
  ],
  "program": "P3",
  "platform": "ios"
}
```

| field | required | notes |
|---|---|---|
| `message` | yes | the user's latest message |
| `user_id` | no | your user id, only used for our logging |
| `history` | no | recent turns for context. Stateless, so send the last few each call. We keep no session. |
| `program` | no | the user's program: `P3` (Original), `P9` (Relaxed). `P11` (LeanSure) is not covered by this KB. |
| `platform` | no | `ios` or `android`. Lets us pick platform-specific steps (cancel, restore purchase, etc). |
| `locale` | no | reply-language hint, e.g. `hi`, `es`. Auto-detected from the message if omitted. |
| `program_day` | no | current day/stage in the program, for stage-specific answers. |
| `subscription_status` | no | e.g. `active`, `expired`, `trial`. |

### Response
```json
{
  "answer": "You can cancel your subscription by going to My Account ...",
  "escalate": false,
  "escalate_reason": null,
  "coach_summary": null,
  "sources": ["Final shortcut sheet / How to cancel the subscription ?"]
}
```

| field | notes |
|---|---|
| `answer` | the text to show the user, always present |
| `escalate` | `true` means hand the chat to a human |
| `escalate_reason` | why (see table below), or `null` |
| `coach_summary` | one line of context for the human, only set when escalating (max ~300 chars) |
| `sources` | which approved entries the answer came from (for our debugging, you can ignore) |
| `message_id` | unique id for this reply; send it back in `/feedback` to link a rating |
| `suggested_channel` | on escalation: `coaching` or `technical`; `null` otherwise. A hint, the app owns final routing and program gating |

### What you do with the response
- `escalate: false` and reason `null` or `out_of_scope`: just show `answer`. No human needed.
  (`out_of_scope` means the user asked something off-topic and the bot politely declined. Do NOT
  route these to a human.)
- `escalate: true`: show `answer`, then route the conversation to a human and pass `coach_summary`
  so nobody has to repeat themselves.

### escalate_reason values
| reason | escalate | meaning / what you do |
|---|---|---|
| `null` | false | answered normally, show it |
| `greeting` | false | a greeting / thanks / small talk, show it (no human needed) |
| `out_of_scope` | false | off-topic / time-pass, politely declined, do NOT route to a human |
| `service_error` | true | our service hit an error, route to a human |
| `no_confident_answer` | true | on-topic but we have no approved answer, route to a human |
| `medical_topic` | true | medication, alarming symptoms, conditions, route to a human |
| `crisis` | true | self-harm or distress, route to a human and show help resources |
| `user_requested` | true | user asked for a human |
| `user_frustrated` | true | user is upset |
| `account_action` | true | account-specific billing (paid but no access, refund status), needs a human lookup |

---

## POST /feedback  (optional, powers our improvement loop)
```json
{ "user_id": "12345", "message_id": "14063d1feba5", "message": "how do I pay?",
  "answer": "<the reply shown>", "rating": "up", "note": "optional, only on down" }
```
Send this if you add thumbs up / down in the UI. `rating` is `"up"` or `"down"`. Pass the
`message_id` from the `/chat` reply so we can link the rating to the exact answer.

## GET /health
```json
{ "status": "ok", "kb_entries": 483, "model": "gemini-2.5-flash", "retrieval_floor": 0.55 }
```

---

## Run it locally
```
./scripts/serve.sh           # starts on http://localhost:8082/
```
There is also a test chat UI at `http://localhost:8082/` to try it by hand.

## Quick test
```
curl -s -X POST http://localhost:8082/chat \
  -H 'content-type: application/json' \
  -d '{"message":"how do I restart the program?","platform":"ios"}'
```
