The KOVA API lets you manage strategies, submit portfolios, and generate briefs programmatically. Same evaluation engine as the web app, accessible over REST.
Base URL: https://kovatools.com/api/v1
All API requests require a Bearer token in the Authorization header.
Authorization: Bearer kova_tk_xxxxxxxxxxxxxxxxxxxx
Getting a token: Log in to KOVA, go to Settings, and generate an API token. The token is shown once at creation — copy it immediately. Tokens can be revoked from the same page.
Every response wraps the resource in a named key plus a meta object:
{
"strategy": { ... },
"meta": {
"request_id": "abc-123",
"timestamp": "2026-02-15T12:00:00Z"
}
}
Collections use the plural key ("strategies", "briefs"). Errors use "error":
{
"error": {
"code": "not_found",
"message": "Strategy not found"
},
"meta": { ... }
}
GET /api/v1/strategies
Returns all strategies owned by the authenticated user.
Response (200):
{
"strategies": [
{
"uuid": "a1b2c3d4-...",
"name": "Long-Term Growth",
"content_summary": "My strategy focuses on...",
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-02-01T00:00:00Z"
}
],
"meta": { ... }
}
GET /api/v1/strategies/:uuid
Returns full details of a strategy, including the complete text.
Response (200):
{
"strategy": {
"uuid": "a1b2c3d4-...",
"name": "Long-Term Growth",
"content": "Full strategy text...",
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-02-01T00:00:00Z"
},
"meta": { ... }
}
POST /api/v1/strategies
Creates a new strategy for the authenticated user.
Request body:
{
"content": "Buy and hold growth stocks. Max 20% per position. Rebalance quarterly.",
"name": "Long-Term Growth"
}
| Field | Type | Required | Description |
|---|---|---|---|
content |
string | Yes | Full strategy text (supports Markdown) |
name |
string | No | Display name for the strategy |
Response (201 Created):
{
"strategy": {
"uuid": "a1b2c3d4-...",
"name": "Long-Term Growth",
"content": "Buy and hold growth stocks. Max 20% per position. Rebalance quarterly.",
"created_at": "2026-02-15T12:00:00Z",
"updated_at": "2026-02-15T12:00:00Z"
},
"meta": { ... }
}
PUT /api/v1/strategies/:uuid
Updates an existing strategy. Only provided fields are changed.
Request body:
{
"content": "Updated strategy text...",
"name": "Renamed Strategy"
}
Response (200): Same shape as Get Strategy.
DELETE /api/v1/strategies/:uuid
Permanently deletes a strategy.
Response: 204 No Content
GET /api/v1/strategies/:uuid/portfolio
Returns the current portfolio on file for a strategy — the most recent normalized holdings (stocks, options, cash) and any manually tracked assets (Bitcoin, real estate, etc.), plus performance metrics. Returns 404 if the strategy has never been evaluated and has no manual assets.
Note: The portfolio is updated implicitly when you submit a new portfolio via POST /strategies/:uuid/briefs. There is no separate endpoint to upload a portfolio without generating a brief.
Response (200):
{
"portfolio": {
"holdings": {
"schema_version": "1.0.0",
"stocks": [
{ "ticker": "VOO", "shares": 50.0, "cost_basis": 400.0, "current_price": 450.0, "market_value": 22500.0, "price_source": "marketstack" }
],
"options": [],
"cash": 1000.0,
"timestamp": "2026-02-15T10:30:00Z",
"notes": [],
"assets": [
{ "name": "Bitcoin", "type": "bitcoin", "ticker": "BTC", "quantity": 0.5, "current_value": 25000.0, "price_source": "coingecko" }
]
},
"metrics": {
"performance_score": 0.72,
"alignment_score": 0.85,
"consistency_score": 0.78,
"absolute_return_percent": 12.3,
"portfolio_value": 48500.0
},
"snapshot_at": "2026-02-15T10:30:00Z",
"evaluated_at": "2026-02-15T10:35:00Z"
}
}
POST /api/v1/strategies/:uuid/briefs
Submit a portfolio and trigger asynchronous brief generation. Returns immediately with a brief ID — poll the show endpoint for results.
Request body:
{
"portfolio": {
"source": "text",
"content": "AAPL 100 shares at $150, MSFT 50 shares at $380, $12,000 cash"
},
"context": "Risk tolerance: aggressive. Current bias: { AAPL: bearish, MSFT: neutral }. RSI(14): AAPL 72, MSFT 45. VIX: 18.5"
}
Portfolio source types:
| Source | Content | content_type |
|---|---|---|
text |
Plain text description | Not needed |
file |
Base64-encoded file | Required (application/pdf, text/csv, image/png) |
json |
Structured JSON (portfolio_v1 schema) | Not needed |
context (optional): Any string (ideally markdown) that helps Kova produce a more accurate evaluation. Use it to provide risk tolerance, market outlook, technical indicators, or any signal relevant to your strategy. Ephemeral — applies to this brief only.
Response (202 Accepted):
{
"brief": {
"id": "brief-uuid",
"status": "processing",
"poll_url": "/api/v1/strategies/strategy-uuid/briefs/brief-uuid"
},
"meta": { ... }
}
GET /api/v1/strategies/:uuid/briefs/:id
Returns the current state of a brief. Poll this endpoint after creating a brief until status is completed or error.
Status: processing
{
"brief": {
"id": "brief-uuid",
"status": "processing"
},
"meta": { ... }
}
Status: completed
{
"brief": {
"id": "brief-uuid",
"status": "completed",
"alignment": {
"score": 0.82,
"label": "Aligned"
},
"content": "## Full markdown brief...",
"summary": "Portfolio is broadly aligned with growth strategy.",
"target_allocations": [...],
"compliance_checks": [...],
"context_at_generation": "FOMC raised rates 25bps today.",
"generated_at": "2026-02-15T12:00:00Z"
},
"meta": { ... }
}
Status: error
{
"brief": {
"id": "brief-uuid",
"status": "error",
"error": {
"code": "processing_failed",
"message": "Could not process portfolio data."
}
},
"meta": { ... }
}
GET /api/v1/strategies/:uuid/briefs
Returns all completed briefs for a strategy, most recent first.
Response (200):
{
"briefs": [
{
"id": "brief-uuid",
"alignment": {
"score": 0.82,
"label": "Aligned"
},
"summary": "Portfolio is broadly aligned...",
"generated_at": "2026-02-15T12:00:00Z"
}
],
"meta": { ... }
}
Creating a brief is asynchronous. The flow:
202 Accepted with status: processingcompleted (brief is ready) or error (something failed)processing → completed
processing → error
Recommended polling interval: start at 2 seconds, back off to 5 seconds.
When using "source": "json" in the create brief endpoint, the content must match the portfolio_v1 schema.
Required fields: schema_version, stocks, options, cash, timestamp, notes
{
"schema_version": "1.0.0",
"stocks": [
{
"ticker": "AAPL",
"shares": 100,
"cost_basis": 150.00
},
{
"ticker": "MSFT",
"shares": 50,
"cost_basis": 380.00
}
],
"options": [
{
"ticker": "AAPL",
"type": "call",
"strike": 200,
"expiry": "2026-06-20",
"contracts": 2,
"side": "short",
"premium": 5.50
}
],
"cash": 12000.00,
"timestamp": "2026-02-15T12:00:00Z",
"notes": ["Cost basis estimated for MSFT"]
}
| Field | Type | Required | Description |
|---|---|---|---|
ticker |
string | Yes | Ticker symbol (1-8 chars, letters/numbers/dots) |
shares |
number | Yes | Number of shares (>= 0) |
cost_basis |
number | No | Average cost per share |
| Field | Type | Required | Description |
|---|---|---|---|
ticker |
string | Yes | Underlying ticker symbol |
type |
string | Yes | "call" or "put" |
strike |
number | No | Strike price |
expiry |
string | No | Expiration date (YYYY-MM-DD) |
contracts |
integer | No | Number of contracts (>= 1) |
side |
string | No | "long" or "short" |
premium |
number | No | Premium paid/received per share |
| Field | Type | Description |
|---|---|---|
cash |
number | Cash balance in dollars (negative for margin) |
timestamp |
string | ISO 8601 UTC timestamp (YYYY-MM-DDTHH:MM:SSZ) |
notes |
array of strings | Notes about ambiguities or missing data |
| Score | Label |
|---|---|
| 0.85 – 1.00 | Strongly Aligned |
| 0.70 – 0.84 | Aligned |
| 0.55 – 0.69 | Partially Aligned |
| 0.40 – 0.54 | Drifting |
| 0.00 – 0.39 | Off-Track |
| HTTP Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized |
Missing or invalid API token |
| 404 | not_found |
Resource doesn't exist or doesn't belong to you |
| 422 | invalid_params |
Invalid request parameters (for example, malformed portfolio payload or unsupported live_inputs) |
| 422 | validation_failed |
Model validation failed (for example, missing strategy content on create/update) |