Use whispers
Official AI agent skill and documentation for Southwind Whispers.
npx -y skills add southwind-ai/use-whispers --skill use-whispersAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Guide for building products and integrations on top of Southwind Whispers — an AI-powered analytics and report generation platform. Covers domain concepts (data origins, data sources, reports, agents), API key authentication, and end-to-end developer workflows. Use when a developer asks how to integrate with Whispers, build a product on top of it, use the Whispers API, generate reports programmatically, or connect data sources.
SKILL.md
14.7 KB, as published. Nobody here has run it
Southwind Whispers — Developer Integration Guide
You are a Whispers integration specialist. Your job is to guide developers through building clean, production-ready products on top of the Whispers API.
IMPORTANT — Always reference the authoritative API spec before answering any endpoint-specific question or generating code:
- Live Redoc:
https://app.southwind.ai/api/docs - See reference.md for a curated endpoint catalog and workflow patterns
API base URL: https://app.southwind.ai/api
Never invent endpoint paths, field names, or parameter shapes that are not in the spec.
Step 1 — Understand the Domain Model
Before writing any code, internalize these five concepts. They map to everything in the API.
Organization (Tenant)
The top-level container for all data. Every API key belongs to one organization; the org is inferred automatically from the key — no extra header needed. An org has:
- A subscription plan with limits (data sources, reports, AI sections, etc.)
- Settings: language, currency, glossary, logo
- Members with role-based permissions
Data Origin
A source of truth for how data entered the system. Think of it as the upstream connection. Three types you'll work with:
| Type | Description |
|---|---|
file | Uploaded spreadsheet (CSV, XLSX) or document batch |
api | External REST API (base URL + auth header + endpoints) |
document | PDF / TXT / MD ingested as searchable text chunks |
An origin is created once and can contain multiple data sources.
Data Source
A specific, queryable dataset derived from an origin:
- One spreadsheet → one source per sheet
- One API origin → one source per configured endpoint
- One document batch → one source (chunked and indexed)
Data sources are what agents analyze. You reference them by UUID when creating a report. Sources store metadata in Postgres; actual rows live in MongoDB.
Agent
A report-generation mode (not a chatbot). Three built-in agents:
agent_id | Behavior |
|---|---|
custom_report | User provides a prompt; agent gathers data and writes a full report |
rover_report | Autonomous investigation: profiles data, generates research questions, iterates |
blank_report | Creates an empty report immediately; sections added via ai-section API |
List available agents: GET /api/v1/agents/
Report (Task)
A report is a task — async by design. Lifecycle: queued → running → completed | failed.
The result is a structured block list: text paragraphs, ECharts chart configs, lists, columns. All blocks are JSON — suitable for custom renderers. Reports also support: chat, manual edits, public sharing, export (PDF / PPTX / DOCX), provenance records.
Step 2 — Get an API Key
Every external API call requires an X-API-Key header.
Keys are per-organization. The full secret is shown only once at creation.
Before performing any API operation, always do this first:
- Ask the user whether they already have a Southwind API key (
ak_...). - If they have one, ask them to share it so you can continue.
- If they do not have one, send them to the UI link to create it:
https://app.southwind.ai/settings/api-keys- Fallback path:
https://app.southwind.ai→ Settings → API Keys → Create
Use this prompt pattern at the start of integrations:
"Do you already have a Southwind API key (
ak_...)? If yes, share it and I can continue. If not, create one here: https://app.southwind.ai/settings/api-keys"
Create a key
POST /api/v1/organization/api-keys
X-API-Key: <existing-key-with-manage-api-keys-permission>
Body: { "name": "My Integration" }
Response: { "id": "...", "key_value": "ak_...", "name": "My Integration", ... }
Or create one in the UI: Settings → API Keys → Create.
Use the key
GET /api/v1/me
X-API-Key: ak_...
No Authorization header. No X-Organization-Id header. The org is resolved from the key.
Error responses
| Status | Meaning |
|---|---|
| 401 | Invalid or revoked key |
| 402 | Plan limit reached |
| 422 | Validation error (check request body) |
Error body: { "error": { "detail": "...", "request_id": "..." } }
Step 3 — Choose Your Workflow
Pick the pattern that matches your use case, then follow the workflow below.
Workflow A — File upload → report (most common)
Use when you have CSV / XLSX data you want analyzed.
1. POST /api/v1/origins/file/
{ "files": [{ "name": "data.csv", "url": "https://..." }] }
← Returns: { "created_data_origins": [...], "unprocessed_files": [...] }
File type (spreadsheet vs document) is auto-detected by extension —
.csv/.xlsx → tabular source, .pdf/.txt/.md → document source
2. POST /api/v1/reports/
{
"agent_id": "custom_report",
"data_sources_ids": ["<source-uuid>"],
"params": {
"language": "english",
"currency": "USD",
"prompt": "Analyze Q1 sales trends by region",
"data_provenance": false
},
"improve_prompt": false
}
← Returns: { "id": "<task_id>", "status": "queued", ... }
3. Poll: GET /api/v1/reports/<task_id>
Or SSE: GET /api/v1/reports/<task_id>/events (real-time thoughts + status)
4. When status = "completed":
Result is in response.result[] — array of typed blocks
For files larger than a few MB, use the S3 multipart upload helpers under
/api/v1/origins/s3/… before calling the file ingest endpoint.
Workflow B — External API data source
Use when data lives in an external REST API and should be refreshed over time.
1. POST /api/v1/origins/data-api/
{
"display_name": "Sales API",
"base_url": "https://api.acme.com",
"api_key": "secret",
"api_key_header": "X-API-Key",
"endpoints": [{
"display_name": "Monthly Sales",
"path": "/v1/sales/monthly.json",
"format": "json",
"query_params": {}
}]
}
← Returns: { "success": true, "data_sources": [...] }
2. POST /api/v1/reports/ (same as Workflow A, step 2)
3. Refresh data before re-running:
POST /api/v1/sources/<source_id>/sync
POST /api/v1/reports/<task_id>/redo
Workflow C — Autonomous investigation (Rover)
Use for open-ended research where the agent decides what to investigate.
POST /api/v1/reports/
{
"agent_id": "rover_report",
"data_sources_ids": ["<source-uuid>"],
"params": {
"target_sections": 7,
"data_provenance": true,
"seeded_question": "optional: first question to investigate"
}
}
Rover takes longer. The top-level decision_tree field in the response shows the investigation path.
data_provenance: true adds citation records (fetch with /provenance/{id}).
Workflow D — Blank report + incremental AI sections
Use when you want to build a report section-by-section, or embed a report builder UI.
1. POST /api/v1/reports/
{ "agent_id": "blank_report", "data_sources_ids": [...], "params": {} }
← Completes immediately with an empty report
2. POST /api/v1/reports/<task_id>/ai-section
{ "prompt": "Add a section on Q1 revenue breakdown" }
← Returns: { "blocks": [...] }
3. PUT /api/v1/reports/<task_id> to save manual edits + merged blocks
Workflow E — Chat with a report
Once a report is complete, you can hold a conversational Q&A over its data.
POST /api/v1/reports/<task_id>/chat
{ "message": "Which region had the highest growth?" }
← Streaming SSE response
GET /api/v1/reports/<task_id>/chat/history ← full chat log
DELETE /api/v1/reports/<task_id>/chat/history ← clear session
Workflow F — Share a report publicly
PUT /api/v1/reports/<task_id>/share
{ "is_public": true }
← Returns: { "is_public": true, "token": "..." }
# Anyone can then access (no auth):
GET /api/v1/public/reports/<token>
Step 4 — Handle the Report Result
The result is a BlockNote document
result is an array of BlockNote block objects — the same
format BlockNote uses natively. Each block follows the standard BlockNote JSON shape:
{
"id": "...",
"type": "paragraph",
"props": { ... },
"content": [ { "type": "text", "text": "...", "styles": {} } ],
"children": []
}
You have two options for rendering:
-
Use BlockNote directly — initialize a
BlockNoteEditorwithinitialContent: resultand render a<BlockNoteView>. The Whispers schema includes custom block types (see below) that you would need to register. -
Walk the blocks manually — iterate
resultand render each block type yourself. Straightforward for most types; charts require extra handling (see below).
Block types in use
type | Standard BlockNote? | Key props / content |
|---|---|---|
paragraph | yes | content[] inline text |
heading | yes | props.level (1–3), content[] |
bulletListItem | yes | content[], nestable via children[] |
numberedListItem | yes | content[], nestable via children[] |
table | yes | standard BlockNote table |
image | yes | props.url, props.caption |
columnList / column | xl-multi-column | children[] of column blocks |
chart | custom | props.config — ECharts option as JSON string |
alert | custom | content[] inline text |
statistic | custom | props vary |
Rendering chart blocks
Chart blocks use Apache ECharts. The chart config lives at
block.props.config and is a JSON string — always parse it before use.
import * as echarts from "echarts";
function renderChart(block, containerEl) {
// config lives in props, and is a JSON string — always parse it
const option = JSON.parse(block.props.config);
const chart = echarts.init(containerEl, null, { renderer: "svg" });
chart.setOption({ ...option, backgroundColor: "transparent" });
const observer = new ResizeObserver(() => chart.resize());
observer.observe(containerEl);
return () => { observer.disconnect(); chart.dispose(); };
}
Key rules:
- Config is at
block.props.config, notblock.config props.configis always a JSON string —JSON.parse()it first- Use the SVG renderer (
renderer: "svg") — consistent with how Whispers renders for export - Set
backgroundColor: "transparent"so the chart respects your app's theme - Attach a
ResizeObserverso the chart redraws when its container is resized - Never mutate the parsed option — pass a spread copy to
setOption
Recommended container dimensions:
| Chart type | Width | Height |
|---|---|---|
| Pie / gauge / funnel | 600px | 400px |
| All other types | 800px | 400px |
Legacy format note: Very old reports may have a Chart.js config in props.config
(detectable by a top-level type + data.datasets structure). New reports always produce
native ECharts options.
Polling strategy
- Create report → save
task_id - Subscribe to
GET /api/v1/reports/<task_id>/events(SSE):event: status→data: { "status": "...", "progress": 0-100 }event: thought→ agent reasoning stepevent: done→ stream ended (task completed or failed)
- On
status: completed, fetchGET /api/v1/reports/<task_id>→ readresult - On
status: failed—params.errorexplains why
Important list-vs-detail behavior
GET /api/v1/reports/is for listing and returns summary records (commonly underreports[])- when a user selects a report from history/list, always call
GET /api/v1/reports/<task_id>before rendering content
Step 5 — Build Correctly
Things Whispers owns — do NOT replicate in your product
- Dataset storage and schema inference (MongoDB)
- Agent logic, prompts, and tool execution
- Chart spec generation
- Report structure and block format
- Plan limits enforcement (honor 402 responses)
Things your product owns
- UI rendering of blocks
- Scheduling / triggering of report runs
- User-facing report titles and organization
- Any downstream usage of result data
- Any manipulation of the blocks to display them in the desired way
Common mistakes
- Caching availability: Plan limits and source data change. Always query live.
- UUID handling:
task_id,origin_id,source_idare all UUIDs. Store and pass them as strings. - Key security: Never expose
ak_...keys to browser clients. Use your backend as a proxy. - Async assumption: Report creation always returns
queued. Never assumecompletedwithout checking. - Rigid response parsing: list endpoints may return envelope keys like
data_sources/reports, not onlyitemsor raw arrays. - Chart config assumptions:
chart.props.configcan be a JSON string or an object; support both before calling ECharts. - Wrong chart config path: chart option is in
block.props.config, notblock.config.
Additional Resources
- Full endpoint catalog with request/response shapes: reference.md
- Plan limits and billing:
GET /api/v1/billing/usage,GET /api/v1/billing/plans - Organization settings:
GET/PATCH /api/v1/organization/settings(language, currency, glossary)
Skill Self-Update Protocol
Before giving endpoint-specific guidance, perform this update check once per session:
- Read local version from
VERSION(canonical source). - Fetch latest skill from:
https://raw.githubusercontent.com/southwind-ai/use-whispers/main/skills/use-whispers/SKILL.md
- Fetch latest version from:
https://raw.githubusercontent.com/southwind-ai/use-whispers/main/skills/use-whispers/VERSION
- Compare semantic versions:
- If remote <= local: continue normally.
- If remote > local: sync local skill to latest remote content.
If you cannot edit files (read-only mode), do not attempt sync. Instead, tell the user:
- local version
- latest version
- update URL to pull from
When sync happens, update all release files together:
SKILL.mdVERSIONCHANGELOG.md(when release notes are available)
Version
Canonical version is stored in VERSION.
Check for updates at: https://github.com/southwind-ai/use-whispers