Authentication
Send your organization API key in the X-API-Key header. Keep it in a secret manager or environment variable; the full secret is shown only once when the key is created. Building a startup? Email startup@southwind.ai to unlock an API key with 100 free Rover reports.
curl https://app.southwind.ai/api/v1/me \
--header 'X-API-Key: $SOUTHWIND_API_KEY'
Core concepts
Three resources carry data from an upstream connection into an evidence-backed investigation.
- Data Origin
- The upstream connection or uploaded file. One origin can contain several data sources.
- Data Source
- A specific queryable dataset derived from an origin. Reports reference data source UUIDs, not origin IDs.
- Report
- An asynchronous analysis task created from data sources that are ready to use.
Quickstart: origin to report
Create an origin, resolve its source IDs, and wait until every required source is ready before creating a report.
- Create an origin with POST /api/v1/origins/file/, POST /api/v1/origins/data-api/, or POST /api/v1/origins/postgres/.
- Resolve the data source IDs from the connector-specific receipt.
- For asynchronous file and API origins, wait for every required source to become completed. Prefer the origin ingest event stream; source polling is the fallback.
- If a source reaches awaiting_confirmation, inspect metadata.cleaning_plan and submit the required decisions.
- If a file or API source reaches failed, inspect ingest_error_code and retry ingestion.
- Create the report with POST /api/v1/reports/ and the completed source IDs.
A file or API origin creation response is a receipt: tabular sources are queued, not immediately ready. Creating a report from a tabular source that is not completed returns 409 Conflict. PDF, TXT, and Markdown document sources are exempt from this readiness gate.
Connector receipts
The three origin connectors intentionally return different receipt shapes.
| Connector | Receipt | How to resolve sources |
|---|
| File | created_data_origins[].id | Source discovery is asynchronous. Stream the origin or call GET /api/v1/sources/?origin_id=<origin-id>; do not expect source IDs in the receipt. |
| Data API | data_sources[].id | Poll each returned source with GET /api/v1/sources/?source_id=<source-id>. |
| PostgreSQL | origin_id | Call GET /api/v1/sources/?origin_id=<origin-id>. Live sources are registered ready for querying. |
Ingestion lifecycle
A tabular source moves through a readiness lifecycle before it can be used in a report.
Create origin
│
▼
queued → ingesting → analyzing
│
┌──────────┴──────────┐
▼ ▼
awaiting_confirmation failed
│ │
▼ ▼
cleaning ingest/retry
│ │
└──────────┬──────────┘
▼
completed
│
▼
Create report
| Status | Meaning | Usable in a report? |
|---|
queued | Accepted and waiting for a worker. | No |
ingesting | Reading and materializing upstream data. | No |
analyzing | Profiling types and cleaning requirements. | No |
awaiting_confirmation | Ambiguous conversions require your decision. | No |
cleaning | Applying confirmed conversions. | No |
completed | Ready for analysis. | Yes |
failed | Ingestion stopped; inspect ingest_error_code. | No |
Data source responses also expose ingest_run_id, row_count, and size_bytes. Nullable fields such as ingest_error_code are omitted when there is no value. collection_name is deprecated and no longer identifies a queryable location.
Live PostgreSQL sources are queried directly through DuckDB rather than materialized, so size_bytes is 0. Origin-level lifecycle propagation for PostgreSQL is still in progress; the origin status is not yet a stable readiness contract.
Wait with SSE or polling
The preferred origin stream emits per-source status snapshots approximately every two seconds and closes at completed, awaiting_confirmation, or failed.
event: status
data: [{"id":"<source-id>","ingest_status":"analyzing","ingest_error_code":null}]
event: done
data: {}
As a fallback, call GET /api/v1/sources/?origin_id=<origin-id> every 2–5 seconds.
Do not treat a successful source sync, update, or creation response as proof that fresh data is ready. Wait for the new ingestion run to finish.
Create a report
Rover is the recommended starting point for autonomous analysis. Run it across one or more completed data sources and orient the investigation with a goal.
curl --request POST 'https://app.southwind.ai/api/v1/reports/' \
--header 'X-API-Key: $SOUTHWIND_API_KEY' \
--header 'Content-Type: application/json' \
--data '{"agent_id":"rover_report","data_sources_ids":["DATA_SOURCE_ID"],"title":"Performance investigation","params":{"language":"english","currency":"USD","data_provenance":true,"target_sections":7,"goal":"Identify the main performance drivers and prioritize actions."}}'
Report status normally progresses from queued to running, then to completed or failed. waiting_for_data is also an in-progress state and commonly appears when redoing a report while its sources refresh. Clients should tolerate new in-progress values.
Inspect the OpenAPI schemaCleaning confirmation
Every column marked ambiguous in metadata.cleaning_plan needs a decision. Send the offered temporal format, a numeric interpretation, or keep_text.
{
"decisions": [
{
"column": "order_date",
"kind": "temporal",
"target_type": "date",
"format": "%d/%m/%Y"
}
],
"locale": "eu"
}
The source moves to cleaning, then completed. Decisions are retained for later refreshes when they still match the incoming data.
Report translations
Translate a completed report with POST /api/v1/reports/{task_id}/translate, then poll the report until the language appears in available_languages. While work is pending, translation_jobs exposes the per-language state.
POST /api/v1/reports/{task_id}/translate
GET /api/v1/reports/{task_id}?language=italian
The language query parameter is also available for report save, rename, export, and public report reads. These writes affect only the selected language. If the primary report changes later, translated views remain available with translation_stale: true until replaced.
Submitting an existing or active translation without replace: true returns 409 with translation_exists or translation_in_progress. Unsupported languages return 400 invalid_language; non-completed reports return 409 report_not_ready. Translation usage is metered separately.
Errors
API errors use a stable JSON envelope with a human-readable detail, a machine-readable code, and a request ID.
{"error":{"detail":"...","code":"...","request_id":"..."}}
Branch on the stable machine-readable code, not the human-readable detail. Server-side failures normally include a request ID.