---
summary: "End-to-end developer recipe \u2014 bot config, knowledge ingestion, tone,\
  \ escalation rules, embed, analytics, all driven from curl/Python."
title: 'Advanced: build a support bot via the API'
path: tutorials/build-a-support-bot
status: published
---

> **Developer-facing tutorial.** This page drives the whole flow from the API. For the no-code path through the ScaiGrid admin UI, see [Build a bot with the admin UI](./build-with-the-admin-ui).

# Build a support bot

You're going from zero to a deployed support bot that:

- answers from your product docs,
- escalates billing questions to a human queue,
- escalates angry visitors,
- attributes conversations to your authenticated users.

Roughly 30 minutes if you have the docs and queue ready.

## 1. Decide the bot's shape

Before any API calls, settle these:

- **Scope.** What does the bot answer? ("product features, pricing tiers, account management" — keep it tight; broader scope means worse answers.)
- **Voice.** Casual or formal? Verbose or concise? Empathetic? Mirror your brand.
- **Escalation triggers.** What words / intents are obvious "human, not bot" territory?
- **Knowledge.** Which documents go in? Pull together PDFs / docs / FAQ pages.

## 2. Create the bot

```bash
curl -X POST "$SCAIGRID_HOST/v1/modules/scaibot/bots" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Support",
    "slug": "acme-support",
    "model": "scailabs/poolnoodle-omni",
    "display_name": "Acme Support",
    "welcome_message": "Hi there! I can help with product features, pricing, or your account. What do you need?",
    "max_tokens_per_response": 600,
    "max_context_messages": 20,
    "knowledge_enabled": true,
    "knowledge_mode": "managed"
  }'
```

Save the `bot.id`. Everything below targets it.

## 3. Configure tone

```bash
curl -X PUT "$SCAIGRID_HOST/v1/modules/scaibot/bots/$BOT_ID/tone" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "persona_name": "Avery",
    "persona_description": "A friendly Acme support specialist.",
    "formality": "casual",
    "verbosity": "concise",
    "empathy_level": "high",
    "language": "en",
    "terminology": ["Acme", "Widget", "Acme Cloud", "WidgetPro"],
    "topics_in_scope": ["product features", "pricing", "account management", "billing"],
    "topics_out_of_scope": ["legal advice", "third-party products"],
    "custom_instructions": "If unsure, offer to escalate rather than guess."
  }'
```

## 4. Upload knowledge

Loop through your documents. Wait for each to index before uploading the next if you want predictable ordering; in practice you can fire them all and check status afterwards.

```python
import httpx, os, time

H = {"Authorization": f"Bearer {os.environ['SCAIGRID_API_KEY']}"}
BOT = os.environ["BOT_ID"]
HOST = os.environ["SCAIGRID_HOST"]

docs = [
    ("Product Handbook.pdf", "product-handbook.pdf"),
    ("Pricing Guide.pdf",    "pricing-guide.pdf"),
    ("Account FAQ.md",       "account-faq.md"),
]

uploaded = []
for name, path in docs:
    with open(path, "rb") as f:
        r = httpx.post(
            f"{HOST}/v1/modules/scaibot/bots/{BOT}/documents",
            headers=H,
            files={"file": (path, f)},
            data={"name": name},
        )
        r.raise_for_status()
        uploaded.append(r.json()["data"]["id"])

# Wait for indexing
while True:
    statuses = []
    for doc_id in uploaded:
        s = httpx.get(
            f"{HOST}/v1/modules/scaibot/bots/{BOT}/documents/{doc_id}",
            headers=H,
        ).json()["data"]["status"]
        statuses.append(s)
    if all(s == "indexed" for s in statuses):
        break
    if any(s == "failed" for s in statuses):
        raise RuntimeError(f"document failed: {statuses}")
    time.sleep(2)
```

## 5. Add escalation rules

Order matters — first by priority (high → low), then by creation order. We'll add three:

```bash
# Highest priority: explicit human request
curl -X POST "$SCAIGRID_HOST/v1/modules/scaibot/bots/$BOT_ID/escalations" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Explicit request",
    "priority": 100,
    "trigger_type": "explicit",
    "action_type": "scaiqueue",
    "action_config": {"topic": "support-human", "priority": 5},
    "message": "Got it — let me get a human on this. One moment."
  }'

# Billing keywords
curl -X POST "$SCAIGRID_HOST/v1/modules/scaibot/bots/$BOT_ID/escalations" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Billing keywords",
    "priority": 80,
    "trigger_type": "keyword",
    "trigger_config": {"keywords": ["refund","chargeback","double charged","wrong amount"]},
    "action_type": "scaiqueue",
    "action_config": {"topic": "support-billing", "priority": 8},
    "message": "Billing questions go to our specialist team — connecting you now."
  }'

# Sustained negative sentiment — safety net
curl -X POST "$SCAIGRID_HOST/v1/modules/scaibot/bots/$BOT_ID/escalations" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Frustrated visitor",
    "priority": 30,
    "trigger_type": "sentiment",
    "trigger_config": {"threshold": -0.6, "window_messages": 3},
    "action_type": "scaiqueue",
    "action_config": {"topic": "support-human", "priority": 9},
    "message": "I want to make sure you get the right answer — let me bring in someone who can help."
  }'
```

## 6. Embed with authenticated visitors

Add a token endpoint to your backend (see [embed on your site](./embed-on-your-site)):

```python
@app.route("/chat-token")
def chat_token():
    user = current_user()
    resp = httpx.post(
        f"{HOST}/v1/modules/scaibot/bots/{BOT}/embed-token",
        headers=H,
        json={
            "ttl_seconds": 3600,
            "visitor_id": user.id,
            "visitor_email": user.email,
            "metadata": {"plan": user.plan, "signup_date": user.signup_date.isoformat()},
        },
    )
    return resp.json()["data"]
```

Drop the widget into your authenticated pages:

```html
<script
  src="https://scaigrid.scailabs.ai/static/modules/scaibot/widget.js"
  data-bot-id="bot_abc123"
  data-token-url="/chat-token"
  data-primary-color="#0D9488"
  data-position="bottom-right"
  async>
</script>
```

## 7. Confirm via the conversation log

After a few real chats:

```bash
curl "$SCAIGRID_HOST/v1/modules/scaibot/conversations?bot_id=$BOT_ID&limit=20" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY"
```

Each conversation shows:

- Visitor id and email (from the token, not the HTML).
- Every turn with its citations and which knowledge chunks fired.
- Whether an escalation rule fired and what action ran.
- Tokens spent and latency per turn.

## 8. Watch the metrics

The admin UI's ScaiBot dashboard has time-series for:

- Conversations started / completed / escalated.
- Average turns per conversation.
- Tokens spent (against your tenant budget).
- Top topics by retrieved-chunk frequency.
- Top escalation triggers.

Adjust the bot when you see:

- High escalation rate on a specific topic → knowledge gap, write a doc.
- Low completion rate (visitors abandoning early) → tone is wrong, or the welcome message overpromises.
- High tokens-per-conversation → cap `max_tokens_per_response` lower or trim `max_context_messages`.

## Done

You have a deployed support bot with grounded answers, sensible escalation, and end-to-end attribution. Iterate from here — the bot is just data, every parameter is editable in place.
