Official AI agent skill and documentation for Southwind Whispers.
SaferSkills independently audited use-whispers (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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:
https://app.southwind.ai/api/docsAPI base URL: https://app.southwind.ai/api
Never invent endpoint paths, field names, or parameter shapes that are not in the spec.
Before writing any code, internalize these five concepts. They map to everything in the API.
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 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.
A specific, queryable dataset derived from an origin:
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.
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/
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.
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:
ak_...).https://app.southwind.ai/settings/api-keyshttps://app.southwind.ai → Settings → API Keys → CreateUse 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"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.
GET /api/v1/me
X-API-Key: ak_...No Authorization header. No X-Organization-Id header. The org is resolved from the key.
| Status | Meaning |
|---|---|
| 401 | Invalid or revoked key |
| 402 | Plan limit reached |
| 422 | Validation error (check request body) |
Error body: { "error": { "detail": "...", "request_id": "..." } }
Pick the pattern that matches your use case, then follow the workflow below.
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 blocksFor files larger than a few MB, use the S3 multipart upload helpers under /api/v1/origins/s3/… before calling the file ingest endpoint.
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>/redoUse 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}).
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 blocksOnce 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 sessionPUT /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>result is an array of [BlockNote](https://www.blocknotejs.org/) 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:
BlockNoteEditor with initialContent: resultand render a <BlockNoteView>. The Whispers schema includes custom block types (see below) that you would need to register.
result and render each block type yourself.Straightforward for most types; charts require extra handling (see below).
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 |
Chart blocks use [Apache ECharts](https://echarts.apache.org/). 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:
block.props.config, not block.configprops.config is always a JSON string — JSON.parse() it firstrenderer: "svg") — consistent with how Whispers renders for exportbackgroundColor: "transparent" so the chart respects your app's themeResizeObserver so the chart redraws when its container is resizedsetOptionRecommended 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.
task_idGET /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)status: completed, fetch GET /api/v1/reports/<task_id> → read resultstatus: failed — params.error explains whyGET /api/v1/reports/ is for listing and returns summary records (commonly under reports[])GET /api/v1/reports/<task_id> before rendering contenttask_id, origin_id, source_id are all UUIDs. Store and pass them as strings.ak_... keys to browser clients. Use your backend as a proxy.queued. Never assume completed without checking.data_sources / reports, not only items or raw arrays.chart.props.config can be a JSON string or an object; support both before calling ECharts.block.props.config, not block.config.GET /api/v1/billing/usage, GET /api/v1/billing/plansGET/PATCH /api/v1/organization/settings (language, currency, glossary)Before giving endpoint-specific guidance, perform this update check once per session:
VERSION (canonical source).https://raw.githubusercontent.com/southwind-ai/use-whispers/main/skills/use-whispers/SKILL.mdhttps://raw.githubusercontent.com/southwind-ai/use-whispers/main/skills/use-whispers/VERSIONIf you cannot edit files (read-only mode), do not attempt sync. Instead, tell the user:
When sync happens, update all release files together:
SKILL.mdVERSIONCHANGELOG.md (when release notes are available)Canonical version is stored in VERSION.
Check for updates at: https://github.com/southwind-ai/use-whispers
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.