Files
jarvis/API/contract.md

324 lines
6.0 KiB
Markdown
Raw Permalink Normal View History

# Jarvis API Contract
Base URL: `http://<host>:<port>/api/v1`
---
## Authentication
None. Local network / self-hosted only.
All endpoints are open. Secure at the network level (VPN, firewall).
---
## REST Endpoints
### GET /stories
Returns paginated stories sorted by signal score descending.
**Query params**
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| limit | int | 20 | Max stories per page (max 100) |
| after | string | — | Cursor for next page |
| topic | string | — | Filter by topic slug e.g. `finance`, `tech` |
| min_signal | int | — | Only return stories above this score |
**Response 200**
```json
{
"data": [
{
"id": "story_2847",
"headline": "Bank of Uganda cuts interest rates",
"summary": "The Bank of Uganda reduced its benchmark lending rate by 50 basis points, citing easing inflation and the need to stimulate credit growth.",
"topic": "finance",
"signalScore": 91,
"scoreBreakdown": {
"sourceAuthority": 28,
"freshness": 22,
"localRelevance": 15,
"crossSourceConfirmation": 16,
"topicImportance": 10
},
"sourceCount": 7,
"sources": [
{
"id": "src_001",
"name": "Daily Monitor",
"url": "https://monitor.co.ug",
"publishedAt": "2026-06-18T08:03:00Z",
"isBreaking": true
}
],
"consensus": "All 7 sources confirm the rate cut. Daily Monitor and NilePost agree on the 50bp figure.",
"conflict": null,
"cachedAt": null,
"updatedAt": "2026-06-18T09:45:00Z",
"createdAt": "2026-06-18T08:03:00Z"
}
],
"nextCursor": "cursor_abc123",
"hasMore": true,
"total": 248
}
```
---
### GET /stories/:id
Returns full story detail including article timeline.
**Response 200**
```json
{
"id": "story_2847",
"headline": "Bank of Uganda cuts interest rates",
"summary": "...",
"topic": "finance",
"signalScore": 91,
"scoreBreakdown": {
"sourceAuthority": 28,
"freshness": 22,
"localRelevance": 15,
"crossSourceConfirmation": 16,
"topicImportance": 10
},
"sourceCount": 7,
"consensus": "All 7 sources confirm the rate cut.",
"conflict": "Two regional outlets dispute impact on mortgage rates.",
"timeline": [
{
"articleId": "art_001",
"source": "Daily Monitor",
"headline": "BoU cuts lending rate by 50 basis points",
"publishedAt": "2026-06-18T08:03:00Z",
"isBreaking": true
},
{
"articleId": "art_002",
"source": "NilePost",
"headline": "Central bank eases policy amid inflation slowdown",
"publishedAt": "2026-06-18T08:21:00Z",
"isBreaking": false
}
],
"updatedAt": "2026-06-18T09:45:00Z",
"createdAt": "2026-06-18T08:03:00Z"
}
```
---
### GET /articles/:id
Returns full article content for offline caching.
**Response 200**
```json
{
"id": "art_001",
"storyId": "story_2847",
"source": "Daily Monitor",
"sourceUrl": "https://monitor.co.ug",
"headline": "BoU cuts lending rate by 50 basis points",
"body": "Full article text...",
"imageUrl": "https://...",
"author": "John Doe",
"publishedAt": "2026-06-18T08:03:00Z"
}
```
---
### GET /feeds
Returns all configured RSS feeds with health state.
**Response 200**
```json
{
"data": [
{
"id": "feed_001",
"name": "Daily Monitor",
"url": "https://monitor.co.ug/rss",
"health": "active",
"pollIntervalSeconds": 300,
"failureCount": 0,
"lastFetchedAt": "2026-06-18T09:40:00Z",
"articleCountToday": 44
}
]
}
```
---
### POST /feeds
Add a new RSS feed.
**Request**
```json
{
"url": "https://example.com/rss",
"name": "Example News",
"pollIntervalSeconds": 900
}
```
**Response 201**
Returns the created feed object.
---
### DELETE /feeds/:id
Remove a feed.
**Response 204** No content.
---
### GET /health
Server health check. Used by the app on launch to verify connection.
**Response 200**
```json
{
"status": "ok",
"version": "1.0.0",
"storiesCount": 248,
"feedsCount": 37,
"uptime": 86400
}
```
---
## WebSocket
### Connect
`ws://<host>:<port>/ws`
No auth headers required.
---
### Events (server → client)
#### story.updated
Signal score changed or new article joined the story.
```json
{
"type": "story.updated",
"storyId": "story_2847",
"signalScore": 94,
"sourceCount": 9,
"updatedAt": "2026-06-18T10:01:00Z"
}
```
App behaviour: refetch `GET /stories/story_2847`, update local cache.
---
#### story.created
New story formed from a new cluster.
```json
{
"type": "story.created",
"storyId": "story_2901",
"headline": "Parliament passes new budget",
"signalScore": 42,
"topic": "politics",
"createdAt": "2026-06-18T10:05:00Z"
}
```
App behaviour: insert into feed, resort by signal score.
---
#### story.stale
Story has gone cold — no new articles in decay window.
```json
{
"type": "story.stale",
"storyId": "story_2800",
"updatedAt": "2026-06-18T10:10:00Z"
}
```
App behaviour: dim story in feed or remove based on user preference.
---
#### feed.health
A feed's health state changed.
```json
{
"type": "feed.health",
"feedId": "feed_012",
"health": "failing",
"failureCount": 3,
"updatedAt": "2026-06-18T10:12:00Z"
}
```
App behaviour: update feed health indicator in feed manager.
---
### Ping / Pong
Server sends `{"type":"ping"}` every 30s.
Client must respond with `{"type":"pong"}`.
No response in 60s → server closes connection.
Client should reconnect with exponential backoff: 1s → 2s → 4s → 8s → max 60s.
---
## Error shapes
All errors follow this shape:
```json
{
"error": {
"code": "story_not_found",
"message": "No story with id story_9999",
"status": 404
}
}
```
| Code | Status | Meaning |
|------|--------|---------|
| `story_not_found` | 404 | Story ID does not exist |
| `feed_not_found` | 404 | Feed ID does not exist |
| `invalid_cursor` | 400 | Pagination cursor is malformed |
| `invalid_url` | 400 | Feed URL is not a valid RSS feed |
| `server_error` | 500 | Internal server error |