builder
Builder
Build and configure Studio Chat assistants using the API. All calls are authenticated automatically via environment variables. The API base URL (https://api.studiochat.io) is hardcoded in the scripts.
IMPORTANT: Always confirm before creating or modifying. Never create knowledge bases, playbooks, API tools, or trigger syncing without explicit user confirmation. Show what you're about to do and wait for approval before executing any write operation.
Key Terminology
Assistants and playbooks are the same concept. In the API, the term "playbook" is used everywhere — but users refer to them as "assistants," "bots," or "agents." When the user mentions any of these, they mean a playbook.
Setup
Set the following environment variables before using the scripts:
export STUDIO_API_TOKEN="sbs_your_api_key_here"
export STUDIO_PROJECT_ID="your-project-uuid"
API keys are available by request from the Studio Chat team at hey@studiochat.io.
API Client
python3 scripts/api.py <path> [-X METHOD] [--body JSON] [--params k=v] [-o file]
Supports GET, POST, PUT, PATCH, DELETE. Auth injected from env vars.
Full API Reference
See references/api-reference.md for complete endpoint specs.
Knowledge Bases
KBs are the information the assistant searches to answer customers. Types: TEXT, FAQ, SNIPPETS, FILE.
List KBs
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/knowledgebases" \
--params include_playbook_usage=true
Get KB content
python3 scripts/api.py "/knowledgebases/KB_ID"
Create Text KB
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/knowledgebases/text" \
-X POST --body '{
"title": "Product FAQ",
"content": "Your plain text content here..."
}'
Create FAQ KB
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/knowledgebases/faq" \
-X POST --body '{
"title": "Common Questions",
"faq_items": [
{"questions": ["How do I reset my password?"], "answer": "Go to Settings > Security > Reset Password."},
{"questions": ["What are your hours?"], "answer": "Monday-Friday 9am-5pm EST."}
]
}'
Create Snippets KB (product catalog)
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/knowledgebases/snippets" \
-X POST --body '{
"title": "Product Catalog",
"snippet_items": [
{"title": "Basic Plan", "content": "Price: $10/mo. Includes 100 conversations."},
{"title": "Pro Plan", "content": "Price: $50/mo. Includes 1000 conversations."}
]
}'
Update KB
python3 scripts/api.py "/knowledgebases/KB_ID" \
-X PATCH --body '{"content": "New text content..."}'
Delete / Restore KB
python3 scripts/api.py "/knowledgebases/KB_ID" -X DELETE
python3 scripts/api.py "/knowledgebases/KB_ID/restore" -X POST
After KB changes, sync the project to apply them.
Correction Notes
Add correction notes to individual KB items to override their content at query time. Notes take effect immediately — no syncing required.
List all notes in the project:
python3 scripts/api.py "/projects/$STUDIO_PROJECT_ID/notes"
List all items with notes in a specific KB:
python3 scripts/api.py "/knowledgebases/KB_ID/notes"
Get notes for a specific item:
python3 scripts/api.py "/knowledgebases/KB_ID/items/ITEM_ID/notes"
Add a note to an item:
python3 scripts/api.py "/knowledgebases/KB_ID/items/ITEM_ID/notes" -X POST --body '{"note": "The correct answer is X, not Y."}'
Remove a note from an item:
python3 scripts/api.py "/knowledgebases/KB_ID/items/ITEM_ID/notes" -X DELETE --body '{"note": "The correct answer is X, not Y."}'
Edit a note on an item:
python3 scripts/api.py "/knowledgebases/KB_ID/items/ITEM_ID/notes" -X PUT --body '{"old_note": "old text", "new_note": "corrected text"}'
Notes override the original content for the LLM. Use them to correct outdated information without editing the source. To clear all notes, remove them one by one.
Playbooks
Playbooks define how the AI assistant behaves — instructions, linked KBs, and model.
List playbooks
python3 scripts/api.py "/projects/$STUDIO_PROJECT_ID/playbooks"
Create playbook
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/playbooks" \
-X POST --body '{
"name": "Support Bot",
"content": "You are a helpful support assistant.\n\nRules:\n- Be concise and friendly\n- Escalate billing disputes",
"kb_ids": ["KB_ID_1", "KB_ID_2"]
}'
Get latest playbook version
python3 scripts/api.py "/playbooks/BASE_ID/latest"
Update playbook (creates new version automatically)
IMPORTANT: Before updating instructions, ALWAYS fetch the latest version first using
GET /playbooks/BASE_ID/latest, even if you already have the instructions in your context.
The playbook may have been modified by another process since you last read it. Read the
latest content, apply your changes on top of it, then send the PATCH.
# 1. Fetch the current latest version
python3 scripts/api.py "/playbooks/BASE_ID/latest"
# 2. Apply your changes on top of the fetched content and patch
python3 scripts/api.py "/playbooks/BASE_ID/latest" \
-X PATCH --body '{"content": "Updated instructions based on latest..."}'
Version management
# Get version history
python3 scripts/api.py "/playbooks/PLAYBOOK_ID/history"
# Get currently active version (uses base_id)
python3 scripts/api.py "/playbooks/BASE_ID/active"
# Set active version
python3 scripts/api.py "/playbooks/BASE_ID/active" \
-X PUT --body '{"version_number": 3}'
Playbook settings
# Enable/disable playbook (kill switch)
python3 scripts/api.py "/playbooks/PLAYBOOK_ID/settings" \
-X PATCH --body '{"is_disabled": true}'
# Configure winback
python3 scripts/api.py "/playbooks/PLAYBOOK_ID/settings" \
-X PATCH --body '{"winback_enabled": true, "winback_delay_minutes": 30}'
Syncing
After modifying KBs, the project must be synced to apply changes. This re-indexes the knowledge base sources.
# Sync the project (re-index knowledge bases)
python3 scripts/api.py "/projects/$STUDIO_PROJECT_ID/train" -X POST
# Check sync status
python3 scripts/api.py "/jobs/JOB_ID"
Schedule (Office Hours)
# Get current schedule
python3 scripts/api.py "/projects/$STUDIO_PROJECT_ID/schedule"
# Create schedule
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/schedule" \
-X POST --body '{
"name": "Business Hours",
"timezone": "America/New_York",
"enabled": true,
"weekly_schedule": {
"monday": {"start_time": "09:00", "end_time": "17:00", "is_available": true},
"saturday": {"is_available": false},
"sunday": {"is_available": false}
}
}'
# Add a holiday override
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/schedule/overrides" \
-X POST --body '{"date": "2025-12-25", "label": "Christmas Day", "is_available": false}'
Skills
Skills are sub-instructions loaded on-demand during conversations. Only skill metadata (name + description) goes in the system prompt; the full content is loaded via a load_skill tool call when the conversation matches the skill's description. This keeps the base context window small.
Skills are versioned with the playbook — adding, editing, or deleting a skill creates a new playbook version.
List skills
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/playbooks/BASE_ID/skills"
Create skill
Creates a new playbook version with the skill added.
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/playbooks/BASE_ID/skills" \
-X POST --body '{
"name": "refund-process",
"description": "Handle refund requests for orders",
"trigger": "Handle refund requests for orders",
"content": "## Refund Process\n\n1. Ask for order number\n2. Search in {{ kb(KB_ID) }} for the order\n3. Process refund within 48 hours\n4. Use {{ tool(TOOL_ID) }} to issue the refund",
"is_active": true,
"order": 0
}'
Update skill
Creates a new playbook version with the skill modified. All fields are optional.
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/playbooks/BASE_ID/skills/refund-process" \
-X PATCH --body '{"description": "Updated description", "content": "Updated instructions..."}'
Delete skill
Creates a new playbook version without the skill.
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/playbooks/BASE_ID/skills/refund-process" \
-X DELETE
Reorder skills
Creates a new playbook version with updated display order. Body is an ordered array of skill names.
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/playbooks/BASE_ID/skills/reorder" \
-X PUT --body '["password-reset", "refund-process", "billing-inquiry"]'
Skill content supports templates
Skill instructions support the same template macros as playbook instructions:
{{ kb(KB_ID) }}— Reference a knowledge base for search{{ tool(TOOL_ID) }}— Reference an API tool{{ integration(SLUG) }}— Reference a Composio/custom integration{{ composio_tool: TOOL_NAME | Display Name }}— Reference a specific integration tool{{ custom_tool: short_name }}— Reference a configured custom tool
The referenced tools/KBs are registered in the agent even before the skill is loaded, ensuring they're available when needed.
Example Blocks
Example blocks are reference conversations that show the assistant HOW to communicate — tone, style, personality, containment strategies, and approach. They are inline in the instructions or skills via {{ examples: BLOCK_ID }} template macros.
When compiled, examples are injected into the prompt as <example id="xxxxx"> tags. The LLM is instructed to follow the style of matching examples and reference them in its response with <<example_id>> markers.
How examples work
- Create an example block — a block holds one or more reference conversations
- Reference it in instructions or skills — using
{{ examples: BLOCK_ID }} - At runtime — the assistant sees the examples, adopts the style, and cites which example it followed
Example blocks are immutable — editing creates a new block (new ID), and saving the playbook creates a new version pointing to the new block. Previous versions retain their original examples for rollback.
Create example block
curl -s -H "Authorization: Bearer $STUDIO_API_TOKEN" \
-H "Content-Type: application/json" \
-X POST "$API_BASE/projects/$STUDIO_PROJECT_ID/example-blocks" \
-d '{
"examples": [
{
"turns": [
{"role": "user", "content": "Hola, necesito ayuda"},
{"role": "assistant", "content": "¡Hola! Entiendo que necesitás ayuda. Estoy acá para vos. ¿En qué te puedo ayudar?"}
]
},
{
"turns": [
{"role": "user", "content": "Me cobraron de más"},
{"role": "assistant", "content": "Lamento mucho eso. Voy a revisar tu caso ahora mismo. ¿Me pasás tu número de cuenta para verificar?"}
]
}
]
}'
Returns the block with its id. Use this ID in the {{ examples: BLOCK_ID }} macro.
List example blocks
curl -s -H "Authorization: Bearer $STUDIO_API_TOKEN" \
"$API_BASE/projects/$STUDIO_PROJECT_ID/example-blocks"
Get example block
curl -s -H "Authorization: Bearer $STUDIO_API_TOKEN" \
"$API_BASE/projects/$STUDIO_PROJECT_ID/example-blocks/BLOCK_ID"
Update example block
curl -s -H "Authorization: Bearer $STUDIO_API_TOKEN" \
"$API_BASE/projects/$STUDIO_PROJECT_ID/example-blocks/BLOCK_ID" \
-H "Content-Type: application/json" \
-X PATCH -d '{
"examples": [
{
"turns": [
{"role": "user", "content": "Updated user message"},
{"role": "assistant", "content": "Updated assistant response"}
]
}
]
}'
Delete example block
curl -s -H "Authorization: Bearer $STUDIO_API_TOKEN" \
"$API_BASE/projects/$STUDIO_PROJECT_ID/example-blocks/BLOCK_ID" -X DELETE
Using examples in instructions
Add {{ examples: BLOCK_ID }} anywhere in the playbook instructions:
curl -s -H "Authorization: Bearer $STUDIO_API_TOKEN" \
-H "Content-Type: application/json" \
-X PATCH "$API_BASE/playbooks/BASE_ID/latest" \
-d '{
"content": "Sos el asistente de Acme Corp.\n\nSeguí el estilo de estos ejemplos:\n{{ examples: BLOCK_ID }}\n\nConsultá {{ kb(KB_ID) }} para responder consultas."
}'
Using examples in skills
Same syntax works inside skill content:
curl -s -H "Authorization: Bearer $STUDIO_API_TOKEN" \
-H "Content-Type: application/json" \
-X POST "$API_BASE/projects/$STUDIO_PROJECT_ID/playbooks/BASE_ID/skills" \
-d '{
"name": "reclamos",
"description": "Manejar reclamos de clientes",
"trigger": "Manejar reclamos de clientes",
"content": "## Manejo de reclamos\n\n1. Validar la emoción del cliente\n2. Pedir datos del caso\n3. Ofrecer solución\n\n{{ examples: BLOCK_ID }}",
"is_active": true,
"order": 0
}'
Workflow: Import style from a human agent
This is the most powerful use case — take the best CX agent's conversations and replicate their style in the AI assistant.
Step 1: Identify the best conversations (e.g., from Intercom, or from Studio Chat's own chat log)
Step 2: Create example blocks with those conversations, anonymizing user data:
# Example: the best agent's greeting style
curl -s -H "Authorization: Bearer $STUDIO_API_TOKEN" \
-H "Content-Type: application/json" \
-X POST "$API_BASE/projects/$STUDIO_PROJECT_ID/example-blocks" \
-d '{
"examples": [
{
"turns": [
{"role": "user", "content": "Hola"},
{"role": "assistant", "content": "¡Hola! Bienvenido/a a Acme Corp. Soy tu asistente virtual. ¿En qué te puedo ayudar hoy?"}
]
}
]
}'
# Example: how the best agent handles complaints
curl -s -H "Authorization: Bearer $STUDIO_API_TOKEN" \
-H "Content-Type: application/json" \
-X POST "$API_BASE/projects/$STUDIO_PROJECT_ID/example-blocks" \
-d '{
"examples": [
{
"turns": [
{"role": "user", "content": "Me robaron, me cobraron doble"},
{"role": "assistant", "content": "Entiendo tu frustración y lamento mucho lo que pasó. Vamos a revisar tu caso ahora mismo para solucionarlo lo antes posible. ¿Me podrías pasar tu número de cuenta o el ID de la transacción?"}
]
}
]
}'
Step 3: Reference the blocks in the assistant's instructions and skills:
curl -s -H "Authorization: Bearer $STUDIO_API_TOKEN" \
-H "Content-Type: application/json" \
-X PATCH "$API_BASE/playbooks/BASE_ID/latest" \
-d '{
"content": "Sos el asistente de Acme Corp.\n\n{{ examples: GREETING_BLOCK_ID }}\n\nConsultá la base de conocimiento para responder."
}'
Step 4: For specific scenarios (complaints, refunds), add examples to the corresponding skill:
curl -s -H "Authorization: Bearer $STUDIO_API_TOKEN" \
-H "Content-Type: application/json" \
-X PATCH "$API_BASE/projects/$STUDIO_PROJECT_ID/playbooks/BASE_ID/skills/reclamos" \
-d '{"content": "## Reclamos\n\nManejar con empatía.\n\n{{ examples: COMPLAINTS_BLOCK_ID }}"}'
The assistant will now adopt the style of your best human agent for each scenario.
Data placeholder syntax
In example conversations, use # to mark where dynamic data should go. The assistant understands these are placeholders to be filled with real data:
{
"turns": [
{"role": "user", "content": "¿Cuándo llega mi pedido?"},
{"role": "assistant", "content": "Tu pedido está en estado # y se estima que llegue en # días hábiles."}
]
}
API Tools
Custom HTTP integrations the assistant can call during conversations.
# List tools
python3 scripts/api.py "/projects/$STUDIO_PROJECT_ID/api-tools" --params include_playbook_usage=true
# Create tool
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/api-tools" \
-X POST --body '{
"name": "Check Order Status",
"description": "Look up order status by order ID",
"url": "https://api.example.com/orders/{order_id}",
"method": "GET",
"parameters": [
{"name": "order_id", "type": "string", "description": "The order ID", "required": true}
]
}'
Project Settings
# Get settings
python3 scripts/api.py "/projects/$STUDIO_PROJECT_ID/settings"
# Update personality tone
python3 scripts/api.py "/projects/$STUDIO_PROJECT_ID/settings" \
-X PATCH --body '{"personality_tone": "friendly"}'
Personality tones: professional, friendly, casual, expert, playful.
Alerts
Alerts are cron-based condition monitors that evaluate custom conditions and notify via Slack and/or email when triggered. Each alert has a set of conditions (written as natural language instructions), a cron schedule (minimum 10-minute interval), and optional notification channels.
List alerts
python3 scripts/api.py "/projects/$STUDIO_PROJECT_ID/alerts"
Returns all alert definitions for the project with last_run_at and last_triggered_at timestamps.
Create alert
Single condition:
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/alerts" \
-X POST --body '{
"name": "High handoff rate",
"instructions": "Check if the handoff rate exceeds 30% in the last period",
"cron_expression": "*/30 * * * *",
"playbook_base_ids": ["BASE_ID_1"],
"slack_channel": "alerts-channel",
"email_recipients": ["team@example.com"]
}'
Multi-condition — pass instructions as a JSON array of strings. Each condition is evaluated independently by index; the alert triggers if ANY condition is met:
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/alerts" \
-X POST --body '{
"name": "Quality monitor",
"instructions": "[\"Handoff rate exceeds 30%\", \"Negative sentiment above 50%\", \"Conversation volume dropped by more than 40%\"]",
"cron_expression": "0 */6 * * *",
"slack_channel": "alerts-channel"
}'
The executor evaluates each condition independently and returns per-condition results in the run's trigger_summary:
[
{"index": 0, "condition": "Handoff rate exceeds 30%", "triggered": true, "summary": "Handoff at 35%"},
{"index": 1, "condition": "Negative sentiment above 50%", "triggered": false, "summary": "At 12%, within normal range"},
{"index": 2, "condition": "Volume dropped by more than 40%", "triggered": false, "summary": "Volume stable at +2%"}
]
Fields:
name(required) — Alert display nameinstructions(required) — Plain text (single condition) or JSON array of strings (multi-condition). Conditions are written in natural language and evaluated by the data-expert skill against real conversation data.cron_expression(required) — Cron schedule (minimum 10-minute interval)playbook_base_ids(optional) — Filter analysis to specific playbooksslack_channel(optional) — Slack channel name for notificationsemail_recipients(optional) — List of email addresses for notifications
At least one notification channel (slack_channel or email_recipients) should be configured for the alert to be useful.
Get alert
python3 scripts/api.py "/alerts/ALERT_ID"
Update alert
python3 scripts/api.py "/alerts/ALERT_ID" \
-X PATCH --body '{"cron_expression": "0 */6 * * *", "is_enabled": false}'
All fields optional: name, instructions, cron_expression, playbook_base_ids, slack_channel, email_recipients, is_enabled.
Delete alert
python3 scripts/api.py "/alerts/ALERT_ID" -X DELETE
Soft delete — requires human user (API keys cannot delete).
Test run an alert
Triggers a manual test run using the alert's cron interval as the evaluation window. Returns immediately with a run object (status pending); execution happens in the background.
python3 scripts/api.py "/alerts/ALERT_ID/test" -X POST
List alert runs
python3 scripts/api.py "/alerts/ALERT_ID/runs" --params limit=20 offset=0
Returns past executions (newest first) with: status, triggered, trigger_summary, window_start, window_end, execution_log.
Get a specific run
python3 scripts/api.py "/alerts/runs/RUN_ID"
Monitors
Monitors are aggregate triggers (Datadog-style) — a SQL count of conversations matching a structured filter, evaluated on a cron schedule. When the count crosses the threshold the monitor fires and ships a notification to Slack and/or email.
Use monitors when the question is "how many?" (e.g. more than 50 handoffs in the last hour). Use alerts when the question is "is the bot misbehaving?" — alerts run a natural-language check via the data-expert skill, monitors don't read messages at all.
Like alerts, the cron has a 10-minute minimum interval.
Filter shape
The filter is a compound AST over three leaf types — same shape used by other parts of the
platform. All leaves accept negate: true, and group accepts op: "and" or op: "or".
// "tag billing AND skill refund AND NOT has_handoff"
{
"type": "group",
"op": "and",
"children": [
{ "type": "tag", "value": "billing" },
{ "type": "skill", "value": "refund-process" },
{ "type": "handoff", "value": true, "negate": true }
]
}
Pass null (or omit the field) to match every conversation in the window.
Comparison kinds
comparison_kind |
Meaning | Required field |
|---|---|---|
absolute |
Fire when current >= threshold |
threshold |
baseline_relative |
Fire when current >= threshold × baseline (baseline = same window N hours ago) |
threshold, baseline_hours |
Window basis
last_message(default) — the conversation had any activity inside the window.first_message— the conversation was created inside the window.
Preview the filter (no monitor created)
The form preview returns a 7-day daily count + the lookback total. Use this to sanity-check the filter before creating a monitor.
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/monitors/preview" \
-X POST --body '{
"filter": {"type": "tag", "value": "billing"},
"window_basis": "last_message",
"playbook_base_id": "BASE_ID",
"window_minutes": 60
}'
List monitors
python3 scripts/api.py "/projects/$STUDIO_PROJECT_ID/monitors"
Each item carries last_run_at, last_triggered_at, and the last 24 runs as recent_runs
(sparkline data — triggered, current_value, threshold_value, baseline_value).
Create monitor — absolute threshold
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/monitors" \
-X POST --body '{
"name": "Billing handoffs spike",
"filter": {
"type": "group",
"op": "and",
"children": [
{"type": "tag", "value": "billing"},
{"type": "handoff", "value": true}
]
},
"window_basis": "last_message",
"playbook_base_id": "BASE_ID",
"cron_expression": "*/10 * * * *",
"window_minutes": 60,
"comparison_kind": "absolute",
"threshold": 50,
"slack_channel": "ops-alerts",
"email_recipients": ["oncall@example.com"]
}'
Create monitor — baseline-relative
Fires when the current count is at least threshold × what it was baseline_hours ago.
threshold: 2.0 ⇒ "double the baseline". Good for catching spikes that don't have a fixed
absolute number.
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/monitors" \
-X POST --body '{
"name": "Conversation volume 2× baseline",
"filter": null,
"window_basis": "last_message",
"playbook_base_id": "BASE_ID",
"cron_expression": "*/15 * * * *",
"window_minutes": 30,
"comparison_kind": "baseline_relative",
"threshold": 2.0,
"baseline_hours": 24,
"slack_channel": "ops-alerts"
}'
Fields:
name(required)filter(optional) — Conversation filter AST (see above).nullmatches everything.window_basis(defaultlast_message) —last_messageorfirst_message.playbook_base_id(required) — Scope to a single assistant (version-stable base id).cron_expression(required, default*/10 * * * *) — Min 10-minute interval.window_minutes(required, 1..1440) — How far back the count looks.comparison_kind(defaultabsolute) —absoluteorbaseline_relative.threshold(required, > 0) — Absolute count or multiplier of baseline.baseline_hours(required whenbaseline_relative, 1..168) — Hours before "now" to anchor the baseline window.slack_channel(optional) — Slack channel name (no#).email_recipients(optional) — Email addresses.
At least one notification channel should be set or the monitor fires silently.
Get monitor
python3 scripts/api.py "/monitors/MONITOR_ID"
Update monitor
python3 scripts/api.py "/monitors/MONITOR_ID" \
-X PATCH --body '{"threshold": 75, "is_enabled": false}'
All fields optional: name, filter, window_basis, playbook_base_id, cron_expression,
window_minutes, comparison_kind, threshold, baseline_hours, slack_channel,
email_recipients, is_enabled. Pass an empty group ({"type":"group","op":"and","children":[]})
to clear the filter.
Duplicate monitor
Creates a disabled copy named copy-{original.name} with the same scope, schedule, and
delivery — useful for A/B tweaking thresholds without touching the live one.
python3 scripts/api.py "/monitors/MONITOR_ID/duplicate" -X POST
Delete monitor
python3 scripts/api.py "/monitors/MONITOR_ID" -X DELETE
Sandbox (
sbs_) callers: delete is the one operation that needs a human reviewer. The request returns 202 with{"approval_id": "...", "status": "pending", "description": "...", "message": "Request queued for admin approval."}instead of executing immediately. Confirm the deletion with the user (or the admin) before relying on the queue. Every other monitor operation (preview, create, list, get, update, duplicate, test, runs) executes synchronously.
Test run a monitor
Inline manual evaluation. Persists a run row, and delivers Slack/email if it would fire — clearly marked as a TEST run so it doesn't get confused with a real fire. Use it after creating or editing a monitor to sanity-check the wiring.
python3 scripts/api.py "/monitors/MONITOR_ID/test" -X POST
List monitor runs
python3 scripts/api.py "/monitors/MONITOR_ID/runs" --params limit=50 offset=0
Returns past runs (newest first) with status, triggered, current_value,
threshold_value, baseline_value, summary, window_start, window_end,
error_message, started_at, completed_at.
Get a specific run
python3 scripts/api.py "/monitors/runs/RUN_ID"
Trending Topics
Trending topics analysis identifies the top conversation themes for a project over a configurable time window. Analysis runs asynchronously via a background job with progress tracking.
Generate analysis
Starts a new trending topics analysis job. Returns a job_id to poll for progress.
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/conversations/insights/trending-topics/generate" \
-X POST --body '{
"playbook_base_ids": ["BASE_ID_1"],
"time_window_days": 14
}'
Fields (all optional):
playbook_base_ids— Filter to specific playbookstags— Filter by conversation tagstime_window_days— Analysis window in days (1-90, default 7)
Returns 409 if an analysis already exists for today with the same config, or a job is already running.
Check status
Quick status check — returns the current state (completed analysis, running job, or not found).
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/conversations/insights/trending-topics/status" \
--params "time_window_days=14"
Optional query params: playbook_base_ids (comma-separated), tags (comma-separated), time_window_days.
Poll job progress
After generating, poll this endpoint to track progress (0-100%).
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/conversations/insights/trending-topics/job/JOB_ID"
Returns: status (pending/running/completed/failed), progress (0-100), step, progress_message, analysis_id (when completed).
Get analysis
Fetch the full results of a completed analysis.
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/conversations/insights/trending-topics/analysis/ANALYSIS_ID"
Returns: topics (up to 5 trending topics with conversation counts, sentiment breakdown, handoff rates, example conversations), total_conversations, conversations_analyzed, time_window_days, playbook_base_ids.
List past analyses
Browse all past analyses for a project, newest first.
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/conversations/insights/trending-topics/analyses" \
--params limit=20 offset=0
Export analysis as PDF
python3 scripts/api.py \
"/projects/$STUDIO_PROJECT_ID/conversations/insights/trending-topics/analysis/ANALYSIS_ID/pdf" \
-o trending-topics-report.pdf
Important Notes
- KB status flow: ADDED -> (sync) -> ACTIVE. After edits: ACTIVE -> EDITED -> (sync) -> ACTIVE.
- Always sync after KB changes: Creating, updating, or deleting KBs requires syncing.
- Playbook versioning: Every update creates a new version. Use
activeendpoint to control which version is live. - Skills versioning: Skill changes (add/edit/delete) also create new playbook versions. Use the dedicated skill endpoints for individual operations.
- base_id vs playbook_id: Active version endpoints use
base_id(stable across versions). Other endpoints useplaybook_id(specific version). Skill endpoints usebase_id. - Soft deletes: Delete operations are soft — use restore to undo.