Cobenian Steward REST API

Programmatic access to the Advisor and your business data.

Two surfaces

Steward exposes two parallel REST surfaces. Pick the one that matches what you're after:

SurfaceBase pathWhat 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.

Authentication

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.

Rate Limits

100 requests per minute per API key. When exceeded, you'll receive a 429 Too Many Requests response.

Response Format

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"}}

Pagination

Every list endpoint supports the same limit/offset pagination contract, and reports its position in the meta object described above.

ParameterTypeDescription
limitintegerPage size. Default 20 (25 for invoices). Clamped to 1..100.
offsetintegerNumber of rows to skip. Default 0. Clamped to >= 0.
Meta fieldDescription
totalTotal number of rows matching the request's filters, ignoring limit/offset.
countNumber of rows in this response (equals the length of data).
limitThe effective page size applied (after clamping).
offsetThe effective offset applied.
has_moretrue 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.)

Permissions

Each API key has specific permissions. Available permissions:

PermissionGrants access to
read:advisorThe entire Advisor API (/api/advisor/v1) — findings, ask, and capture
read:clientsList and view clients
read:eventsList calendar events
read:invoicesList invoices
read:teamList team members
read:wikiList and view wiki/KB entries
write:wikiCreate, 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.

Advisor API

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.

GET /api/advisor/v1/findings

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.

POST /api/advisor/v1/findings/:id/dismiss

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": {...}}.

POST /api/advisor/v1/ask

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.

FieldTypeDescription
questionstringRequired, 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.

POST /api/advisor/v1/leads

One-tap capture of a sales lead. Returns 201 with the created lead as {"data": {...}}; validation errors return 422 invalid with a details map.

FieldTypeDescription
namestringRequired. Lead / prospect name.
value_centsintegerOptional. Estimated value in cents.
stagestringOptional. Pipeline stage.
expected_closestringOptional. Expected close date (YYYY-MM-DD).
notesstringOptional. Free-form notes.
client_idintegerOptional. Linked client id.

POST /api/advisor/v1/commitments

One-tap capture of a commitment/promise. Returns 201 with the created commitment as {"data": {...}}; validation errors return 422 invalid.

FieldTypeDescription
promisestringRequired. What was promised.
subject_typestringOptional. Subject type (e.g. client).
subject_idintegerOptional. Subject id.
deadlinestringOptional. Deadline (YYYY-MM-DD).
project_idintegerOptional. Linked project id.

PUT /api/advisor/v1/destination

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.

FieldTypeDescription
statementstringRequired. The destination statement.
horizonstringOptional. Time horizon (e.g. "12 months").

PUT /api/advisor/v1/objectives

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.

FieldTypeDescription
objectivesarrayRequired. Each item: name (required), weight, measure_key, target (all optional).
{"objectives": [
  {"name": "Retain revenue", "weight": 0.4, "measure_key": "mrr", "target": 50000}
]}

POST /api/advisor/v1/owner_facts

Append one fact to the owner model without clobbering prior facts. Returns the updated profile as {"data": {"profile": {...}}}.

FieldTypeDescription
facetstringRequired. One of patterns, blind_spots, preferences.
factstringRequired for patterns/blind_spots. The fact text.
key / valuestringRequired 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.

Domain reads

Plain reads of the business data the Advisor reasons over, under /api/v1. All list endpoints honour the shared Pagination contract above.

GET /api/v1/clients

List clients with health scores. Returns all clients (confirmed + unconfirmed auto-discovered), but non-archived only by default. Paginated (see Pagination).

ParameterTypeDescription
confirmedstringtrue = confirmed only, false = unconfirmed only, all = both (default).
archivedstringfalse = non-archived only (default), true = archived only, all = both.
limitintegerPage size (default 20, max 100)
offsetintegerRows to skip (default 0)

GET /api/v1/clients/:id

Get a single client with health score breakdown.

GET /api/v1/events

List upcoming calendar events. Paginated (see Pagination).

ParameterTypeDescription
limitintegerPage size (default 20, max 100)
offsetintegerRows to skip (default 0)

GET /api/v1/invoices

List invoices. Paginated (see Pagination).

ParameterTypeDescription
statusstringFilter by status: sent, paid, overdue
client_idintegerFilter by client
limitintegerPage size (default 25, max 100)
offsetintegerRows to skip (default 0)

GET /api/v1/team

List team members. Paginated (see Pagination).

ParameterTypeDescription
limitintegerPage size (default 20, max 100)
offsetintegerRows to skip (default 0)

Knowledge base

GET /api/v1/wiki

List wiki/KB entries (playbooks, observations, baselines, hand-typed facts). Paginated (see Pagination). Requires read:wiki.

ParameterTypeDescription
client_idintegerOptional — scope results to a specific client.
limitintegerPage size (default 20, max 100)
offsetintegerRows to skip (default 0)

POST /api/v1/wiki

Create a new KB entry under an atomic (non-aggregate) topic. Reserved aggregate index topics are refused. Requires write:wiki.

FieldTypeDescription
topicstringRequired. Topic path for the new entry.
contentstringRequired. Entry content (markdown allowed).
tagsarrayOptional initial tags. kind:guidebook is added automatically.

PUT /api/v1/wiki/:id

Replace the content of an existing KB entry. Reserved index topics refused. Requires write:wiki.

FieldTypeDescription
new_contentstringRequired. Replacement content.

DELETE /api/v1/wiki/:id

Delete a KB entry by id. Reserved index topics refused. Requires write:wiki.