Programmatic access to the Advisor and your business data.
Steward exposes two parallel REST surfaces. Pick the one that matches what you're after:
| Surface | Base path | What it covers |
|---|---|---|
| Advisor API | /api/advisor/v1 |
The Advisor's own concepts — findings (what it has noticed), ask (grounded Q&A), and one-tap capture of leads, commitments, and the owner's Mind (destination, objectives, owner facts). This is the primary surface and the one the Companion uses. |
| Domain reads | /api/v1 |
Plain reads of the underlying business data the Advisor reasons over — clients, calendar events, invoices, team — plus the knowledge base (read/write). |
Both surfaces share the same Bearer-key auth, rate limiter, and JSON envelope.
The legacy pipeline endpoints (/brief, /hypotheses,
/actions, /signals, /objectives,
/assistant/ask) have been removed — use the Advisor API instead.
All API requests require a Bearer token. Generate an API key in Settings → API Keys in the Steward dashboard.
curl -H "Authorization: Bearer ovst_your_key_here" \
https://steward.cobenian.com/api/advisor/v1/findings
API keys are scoped to your account (org). All data returned is limited to your account only.
100 requests per minute per API key. When exceeded, you'll receive a 429 Too Many Requests response.
All responses return JSON. List endpoints return a data array plus a meta object carrying pagination metadata:
{
"data": [...],
"meta": {
"total": 137,
"count": 20,
"limit": 20,
"offset": 0,
"has_more": true
}
}
Single resource endpoints return:
{"data": {...}}
Errors return:
{"error": {"code": "unauthorized", "message": "Invalid API key"}}
Every list endpoint supports the same limit/offset pagination contract, and reports its position in the meta object described above.
| Parameter | Type | Description |
|---|---|---|
limit | integer | Page size. Default 20 (25 for invoices). Clamped to 1..100. |
offset | integer | Number of rows to skip. Default 0. Clamped to >= 0. |
| Meta field | Description |
|---|---|
total | Total number of rows matching the request's filters, ignoring limit/offset. |
count | Number of rows in this response (equals the length of data). |
limit | The effective page size applied (after clamping). |
offset | The effective offset applied. |
has_more | true when offset + count < total — i.e. another page is available. |
To page through results, increase offset by limit until has_more is false:
curl -H "Authorization: Bearer ovst_..." \
"https://steward.cobenian.com/api/v1/invoices?limit=20&offset=40"
Note: meta.total reflects the true total match count. (Prior to this change it returned only the number of rows on the current page.)
Each API key has specific permissions. Available permissions:
| Permission | Grants access to |
|---|---|
read:advisor | The entire Advisor API (/api/advisor/v1) — findings, ask, and capture |
read:clients | List and view clients |
read:events | List calendar events |
read:invoices | List invoices |
read:team | List team members |
read:wiki | List and view wiki/KB entries |
write:wiki | Create, edit, and delete KB entries |
Advisor access is backward-compatible. A key reaches the Advisor
API if its permission set is empty (the full-access default), contains the
* wildcard, or explicitly lists read:advisor. Only a key
that was deliberately scoped to a non-empty set omitting
read:advisor is refused with 403 forbidden. Existing keys
and Companion-linked keys keep working without change.
The Advisor's own surface, under /api/advisor/v1. Every endpoint is
gated on read:advisor (see Permissions) and scoped to
the authenticated org. The Advisor is advisory: ask never sends
anything to clients, and the capture/Mind endpoints only record state.
List the Advisor's current findings — what it has noticed across finance, delivery, and sales, with "the one thing" surfaced first. Returns a bare {"data": [...]} array (not paginated). Each finding:
{
"data": [
{
"id": 42,
"stance": "concern",
"conviction": "high",
"tier": "act",
"title": "ACME is going quiet",
"body": "No reply in 18 days; cadence was weekly.",
"subject_type": "client",
"subject_id": 7,
"status": "surfaced",
"one_thing": true,
"evidence": [ ... ],
"surfaced_at": "2026-06-15T12:00:00Z",
"inserted_at": "2026-06-15T11:59:00Z"
}
]
}
one_thing is true for the single finding whose status is surfaced — the one the Advisor is leading with.
Dismiss a finding. Records the dismissal (with its conviction) on the LEARN ledger and flips the finding to dismissed. Conviction — not approval — governs whether it re-surfaces: a low-conviction subject goes quiet; a high-conviction one persists and, after repeated dismissals, spawns a meta-finding. A finding from another org returns 404 not_found. Returns the updated finding as {"data": {...}}.
Ask the Advisor a question. It answers only from your org's structured advisor data (findings, measured vital signs, open/overdue invoices) — never raw message content — and cites the real ids it used. Read-only; it never sends anything.
| Field | Type | Description |
|---|---|---|
question | string | Required, non-empty. Your question for the Advisor. |
{
"data": {
"conversation_id": "...",
"answer": "ACME and Globex are your two quiet accounts ...",
"refs": [ ... ],
"suggested_action": { ... }
}
}
Missing/empty question returns 400 bad_request; an upstream failure returns 502 ask_failed.
One-tap capture of a sales lead. Returns 201 with the created lead as {"data": {...}}; validation errors return 422 invalid with a details map.
| Field | Type | Description |
|---|---|---|
name | string | Required. Lead / prospect name. |
value_cents | integer | Optional. Estimated value in cents. |
stage | string | Optional. Pipeline stage. |
expected_close | string | Optional. Expected close date (YYYY-MM-DD). |
notes | string | Optional. Free-form notes. |
client_id | integer | Optional. Linked client id. |
One-tap capture of a commitment/promise. Returns 201 with the created commitment as {"data": {...}}; validation errors return 422 invalid.
| Field | Type | Description |
|---|---|---|
promise | string | Required. What was promised. |
subject_type | string | Optional. Subject type (e.g. client). |
subject_id | integer | Optional. Subject id. |
deadline | string | Optional. Deadline (YYYY-MM-DD). |
project_id | integer | Optional. Linked project id. |
Set or refine the org's Destination — where the owner is steering the business. Returns the destination as {"data": {"statement", "horizon", "refined_at"}}; validation errors return 422 invalid.
| Field | Type | Description |
|---|---|---|
statement | string | Required. The destination statement. |
horizon | string | Optional. Time horizon (e.g. "12 months"). |
Replace the org's Destination objectives with a small, curated set (this replaces the prior set wholesale). Returns the new set as {"data": [...]}; validation errors return 422 invalid.
| Field | Type | Description |
|---|---|---|
objectives | array | Required. Each item: name (required), weight, measure_key, target (all optional). |
{"objectives": [
{"name": "Retain revenue", "weight": 0.4, "measure_key": "mrr", "target": 50000}
]}
Append one fact to the owner model without clobbering prior facts. Returns the updated profile as {"data": {"profile": {...}}}.
| Field | Type | Description |
|---|---|---|
facet | string | Required. One of patterns, blind_spots, preferences. |
fact | string | Required for patterns/blind_spots. The fact text. |
key / value | string | Required for preferences. A non-empty key and its value. |
// list facet
{"facet": "patterns", "fact": "decides fast on price"}
// preferences
{"facet": "preferences", "key": "tone", "value": "warm"}
An unknown facet or a missing fact/key returns 400 invalid.
Plain reads of the business data the Advisor reasons over, under
/api/v1. All list endpoints honour the shared
Pagination contract above.
List clients with health scores. Returns all clients (confirmed + unconfirmed auto-discovered), but non-archived only by default. Paginated (see Pagination).
| Parameter | Type | Description |
|---|---|---|
confirmed | string | true = confirmed only, false = unconfirmed only, all = both (default). |
archived | string | false = non-archived only (default), true = archived only, all = both. |
limit | integer | Page size (default 20, max 100) |
offset | integer | Rows to skip (default 0) |
Get a single client with health score breakdown.
List upcoming calendar events. Paginated (see Pagination).
| Parameter | Type | Description |
|---|---|---|
limit | integer | Page size (default 20, max 100) |
offset | integer | Rows to skip (default 0) |
List invoices. Paginated (see Pagination).
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by status: sent, paid, overdue |
client_id | integer | Filter by client |
limit | integer | Page size (default 25, max 100) |
offset | integer | Rows to skip (default 0) |
List team members. Paginated (see Pagination).
| Parameter | Type | Description |
|---|---|---|
limit | integer | Page size (default 20, max 100) |
offset | integer | Rows to skip (default 0) |
List wiki/KB entries (playbooks, observations, baselines, hand-typed facts). Paginated (see Pagination). Requires read:wiki.
| Parameter | Type | Description |
|---|---|---|
client_id | integer | Optional — scope results to a specific client. |
limit | integer | Page size (default 20, max 100) |
offset | integer | Rows to skip (default 0) |
Create a new KB entry under an atomic (non-aggregate) topic. Reserved aggregate index topics are refused. Requires write:wiki.
| Field | Type | Description |
|---|---|---|
topic | string | Required. Topic path for the new entry. |
content | string | Required. Entry content (markdown allowed). |
tags | array | Optional initial tags. kind:guidebook is added automatically. |
Replace the content of an existing KB entry. Reserved index topics refused. Requires write:wiki.
| Field | Type | Description |
|---|---|---|
new_content | string | Required. Replacement content. |
Delete a KB entry by id. Reserved index topics refused. Requires write:wiki.