# signaldaemon API contract

> Narrative & signal intelligence for AI agents — crypto / AI / macro.
> REST base: `https://api.signaldaemon.com` · MCP: `https://api.signaldaemon.com/mcp` (Streamable HTTP).
> Auth: `x-api-key` header on every call. Field names are stable.

## Keys & quotas

| Tier | How to get | narratives/day | feed/day | Lifetime |
|---|---|---|---|---|
| demo | `POST /v1/request-key` — no signup, 1 per IP per day | 200 | 5 | 7 days |
| account | https://signaldaemon.com/console — Google/GitHub sign-in, key shown once | 500 | 20 | until revoked |
| institutional | ask via https://signaldaemon.com/#access | custom | custom | — |

Check your remaining quota anytime (free, does not consume a call):

```
GET /v1/quota            → { "tier": "demo", "narratives": {"used":3,"limit":200,"remaining":197},
  -H "x-api-key: <KEY>"      "feed": {...}, "resets": "daily 00:00 UTC" }
```

## POST /v1/narratives

The day's ranked narratives with derived signals. Precomputed every ~20 min; returns instantly.

Request: `{"limit": 8}` (max 12)

Response shape (one narrative):

```json
{
  "name": "Ethereum Development & EIPs",
  "category": "protocol",
  "rank": 4,
  "strength": 11.9,
  "gist": "one-sentence summary",
  "members": 21,
  "distinct_sources": 9,
  "source_diversity": 0.43,
  "momentum": { "members_24h": 17, "members_prior": 25 },
  "divergence": {
    "code": "narrative_price_aligned",
    "direction": "down",
    "vs_market": "outperform",
    "asset": "ETH",
    "price_change_7d": -12.65,
    "price_change_24h": -1.2,
    "rel_flow": 4.1,
    "breadth": 0.6
  },
  "sources": ["..."],
  "representative_items": [ { "title": "...", "url": "...", "source": "...", "published_at": "..." } ]
}
```

Top-level `market_snapshot`: `{ market_regime: crash|range|bull, market_7d, fear_greed, btc, eth, sol }`.
Every relative signal must be read against `market_regime` / `market_7d`.

### Divergence semantics (the part agents get wrong)

Two independent signed axes — **never infer one from the other, always report both**:

- `direction` ∈ `up` / `down` — the asset's **absolute** 7d move.
- `vs_market` ∈ `outperform` / `underperform` — flow **relative** to `market_7d`.
- `code` ∈ `narrative_price_aligned` · `narrative_no_flow` · `neutral` · `no_asset`.

⚠ In a crash an asset can be `direction=down` AND `vs_market=outperform`: it is falling
*slower than the market* — relative strength, not a bullish move. Saying only "outperform"
misleads; saying only "down" hides the signal. Say both.

`no_asset` = the narrative has no single tradeable asset (politics, enforcement, IPO spillover).
Report the story; do not invent a ticker.

## POST /v1/feed

Clean, de-noised, source-attributed article feed for a topic. Live retrieval — the expensive call.

Request: `{"query": "restaking", "category": null, "limit": 8}`

Response includes `coverage`: when it is `"thin"`, treat results as partial — signaldaemon
states gaps instead of padding them.

## Public (no key)

- `GET /v1/ping` — liveness.
- `GET /v1/status` — operational facts: source count, narratives freshness.
- `GET /v1/preview` — redacted top-4 teaser of the current narratives (marketing surface).

## MCP

Remote (preferred): `https://api.signaldaemon.com/mcp`, Streamable HTTP, header `x-api-key`.
Anonymous handshake/discovery is allowed (initialize, tools/list, resources/list, prompts/list);
`tools/call` requires a key. Tools (both `readOnlyHint: true`):

- `get_market_narratives(limit)` — same payload as /v1/narratives.
- `get_clean_feed(query, category, limit)` — same payload as /v1/feed.

stdio bridge (for clients without remote MCP support):
https://github.com/bevanding/signaldaemon → `mcp_server.py` (`pip install mcp`, env `SIGNALDAEMON_API_KEY`).

## Errors

- `401` invalid/missing key → mint one: `POST /v1/request-key`.
- `429` quota or rate limit → check `GET /v1/quota`; per-IP limit 60 req/min.
- Quota responses carry `reason`: `per_key_narratives` / `per_key_feed` / `global_feed` / `expired`.

## Fail-safe contract

signaldaemon never invents: no coverage → it says so (`coverage: "thin"`, `no_asset`, empty lists).
Output passes an invariant gate before publishing — a stale-but-correct snapshot is served over a
fresh-but-broken one.

---

Human docs: https://signaldaemon.com/api · Everything in one file: https://signaldaemon.com/llms-full.txt
