# API Cookbook

> Reference material for the replayer skill. Load this when you need exact API calls.

## Phase 1: Get Auth Token

### Local: Create dev token

```bash
cd /Users/lingtaoli/investibook/backend && .venv/bin/python3 -c "
from investibook.db.queries import _db_init, _db_get_or_create_user, _db_create_user_session
_db_init()
user = _db_get_or_create_user('dev@localhost')
token = _db_create_user_session(user['id'])
print(token)
"
```

Store: `TOKEN=<output>` and `BASE_URL=http://localhost:8000`

### Remote: Authenticate via CLI profile

**One-time setup** (if not already configured):

```bash
# Example: dev environment
investibook init --api-host https://investibook.dev.prom.integration.tidbcloud.com \
  --api-key ib_<your-key> --profile dev

# Example: prod environment
investibook init --api-host https://investibook.prom.integration.tidbcloud.com \
  --api-key ib_<your-key> --profile prod
```

**Set active profile:**

```bash
investibook profile use <profile-name>
# Verify: investibook profile list
```

**Extract BASE_URL and TOKEN:**

```bash
PROFILE=dev  # or prod, staging, etc.
BASE_URL=$(python3 -c "import json; c=json.load(open('$HOME/.investibook/config.json')); print(c['profiles']['$PROFILE']['api'])")
TOKEN=$(python3 -c "import json; c=json.load(open('$HOME/.investibook/config.json')); print(c['profiles']['$PROFILE']['token'])")
```

**List workspaces:**

```bash
investibook workspace list
```

**Export messages and sources:**

```bash
WS_ID=ws-xxx

curl -s "$BASE_URL/api/workspace/$WS_ID/messages" \
  -H "Authorization: Bearer $TOKEN" > /tmp/messages-$WS_ID.json

curl -s "$BASE_URL/api/workspace/$WS_ID/sources" \
  -H "Authorization: Bearer $TOKEN" \
  | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin).get('sources',[]), indent=2))" \
  > /tmp/sources-$WS_ID.json
```

## Phase 2: Create Workspace and Prepare Source

```bash
ISSUE_ID="O11Y-XXXXXXX"

# Create workspace
WS_ID=$(curl -s -X POST $BASE_URL/api/workspaces \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"name\": \"$ISSUE_ID postmortem\"}" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")

echo "Workspace: $WS_ID"

# Add Jira source
curl -s -X POST "$BASE_URL/api/workspace/$WS_ID/sources" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"type\":\"jira_issue\",\"name\":\"$ISSUE_ID\",\"issueId\":\"$ISSUE_ID\",\"status\":\"loading\"}"

# Prepare source
TASK_ID=$(curl -s -X POST "$BASE_URL/api/sources/prepare" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"workspace_id\":\"$WS_ID\",\"issue_id\":\"$ISSUE_ID\"}" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['task_id'])")

# Wait for preparation
while true; do
  STATUS=$(curl -s "$BASE_URL/api/sources/prepare/$TASK_ID" \
    -H "Authorization: Bearer $TOKEN" \
    | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
  echo "Prepare status: $STATUS"
  [ "$STATUS" = "completed" ] && break
  [ "$STATUS" = "error" ] && echo "FAILED" && break
  sleep 5
done
```

## Phase 3: Run Diagnosis (non-interactive)

```bash
# Mode C only: clear previous messages
# curl -s -X DELETE "$BASE_URL/api/workspace/$WS_ID/messages" \
#   -H "Authorization: Bearer $TOKEN"

curl -s -N --max-time 600 \
  "$BASE_URL/api/chat/stream?message=Use%20tidb-clinic-diagnosis%20to%20analyze%20this%20alert.%20Provide%20root%20cause%2C%20evidence%20chain%2C%20and%20recommendations.&workspace_id=$WS_ID&token=$TOKEN" \
  > /tmp/replay-$ISSUE_ID.txt
```

## Phase 4: Extract Results

**Fetch messages:**

```bash
# Mode A/B/C (via API):
curl -s "$BASE_URL/api/workspace/$WS_ID/messages" \
  -H "Authorization: Bearer $TOKEN" > /tmp/messages-$WS_ID.json

# Mode D: already exported in Phase 1R
```

**Extract with Python:**

```python
import json

with open(f'/tmp/messages-{WS_ID}.json') as f:
    data = json.load(f)

# API returns {"messages": [...]}, sqlite3 -json returns [...]
msgs = data.get('messages', data) if isinstance(data, dict) else data
all_tools = []
all_texts = []
human_interventions = 0

for m in msgs:
    raw_parts = m.get('parts')
    if raw_parts is None:
        parts = []
    elif isinstance(raw_parts, str):
        parts = json.loads(raw_parts)
    else:
        parts = raw_parts
    role = m.get('role', '')

    if role == 'user' and parts:
        human_interventions += 1

    if role != 'assistant':
        continue

    for p in parts:
        if p.get('type') == 'tool':
            state = p.get('state', {})
            inp = state.get('input', {})
            all_tools.append({
                'title': state.get('title', ''),
                'description': inp.get('description', '') if isinstance(inp, dict) else '',
                'status': state.get('status', ''),
            })
        elif p.get('type') == 'text':
            all_texts.append(p.get('text', ''))

# Final diagnosis is the last assistant text block
final_text = all_texts[-1] if all_texts else ''

# Summary
total = len(all_tools)
completed = sum(1 for t in all_tools if t['status'] == 'completed')
stuck = sum(1 for t in all_tools if t['status'] == 'running')

print(f"Messages: {len(msgs)}")
print(f"Human interventions: {human_interventions}")
print(f"Tool calls: {total} (completed={completed}, stuck={stuck})")
print()
print("Tool call sequence:")
for i, t in enumerate(all_tools):
    label = t['description'] or t['title']
    print(f"  {i+1}. [{t['status']}] {label[:80]}")
```

**Key metrics to extract from tool calls:**

| Metric | How to check |
|--------|-------------|
| TopSQL TiDB queried | Tool description contains "TopSQL" + "TiDB" or "tidb" |
| TopSQL TiKV queried | Tool description contains "TopSQL" + "TiKV" or "tikv" |
| SlowQuery queried | "slow query" or "slow_query_logs" |
| Loki logs queried | "error log" or "ERROR/FATAL" |
| Events queried | "cluster events" or "activity" |
| Hotspot detected | Final text mentions specific instance name (e.g., "db-tikv-19") |
| Stuck tool calls | Any tool with status "running" |

## Phase 6: Get Issue ID from Workspace

**Mode A/B/C (via API):**

```bash
ISSUE_ID=$(curl -s "$BASE_URL/api/workspace/$WS_ID/sources" \
  -H "Authorization: Bearer $TOKEN" \
  | python3 -c "
import sys, json
sources = json.load(sys.stdin).get('sources', [])
jira = next((s for s in sources if s.get('type') == 'jira_issue'), None)
print(jira['issueId'] if jira else 'UNKNOWN')
")
echo "Issue ID: $ISSUE_ID"
```

**Mode D (from exported sources):**

```bash
ISSUE_ID=$(python3 -c "
import json
with open('/tmp/sources-$WS_ID.json') as f:
    sources = json.load(f)
jira = next((s for s in sources if s.get('type') == 'jira_issue'), None)
print(jira['issueId'] if jira else 'UNKNOWN')
")
echo "Issue ID: $ISSUE_ID"
```

## Sending Follow-up Messages

```bash
curl -s -N --max-time 300 \
  "$BASE_URL/api/chat/stream?message=<URL-encoded-message>&workspace_id=$WS_ID&token=$TOKEN" \
  > /tmp/followup-$WS_ID.txt
```

## API Reference

| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/api/auth/login` | POST | Email+password login, returns JWT |
| `/api/workspaces` | GET | List all workspaces |
| `/api/workspaces` | POST | Create workspace |
| `/api/workspaces/{id}` | GET | Get workspace metadata |
| `/api/workspace/{id}/sources` | GET | Get workspace sources |
| `/api/workspace/{id}/sources` | POST | Add source |
| `/api/sources/prepare` | POST | Start source data fetch |
| `/api/sources/prepare/{task_id}` | GET | Poll preparation status |
| `/api/chat/stream` | GET (SSE) | Send message, stream response |
| `/api/workspace/{id}/messages` | GET | Read full message history |
| `/api/workspace/{id}/messages` | DELETE | Clear messages (for re-run) |
| `/api/workspace/{id}/export` | POST | Start workspace export |
| `/api/workspace/{id}/export/{task_id}` | GET | Poll export status |
