CryptoMinute API
The CryptoMinute API serves the same data that powers cryptominute.ai: AI-analyzed crypto news, clustered stories, real-time flash updates, Reddit and YouTube signals, prices, and the 5-dimensional analytics engine.
Base URL
https://api.cryptominute.ai
All responses are JSON. The API is read-only — every endpoint is GET.
Authentication
API keys are optional. Without a key you get public access subject to lower rate limits and a one-year historical data cap.
Pass the key in either of two equivalent ways:
# Header
curl -H "X-API-Key: cm_your_key_here" https://api.cryptominute.ai/news
# Query parameter
curl "https://api.cryptominute.ai/news?api_key=cm_your_key_here"
Keys are formatted cm_ followed by 64 hex characters. Need a key? Get in touch.
Tiers
| Tier | Rate limit | Historical data |
|---|---|---|
| Public (no key) | 60 req/min | Last 1 year only |
| Standard | 300 req/min | Unlimited |
| Premium | 1,000 req/min | Unlimited |
Rate-limit state is exposed on every response via the X-RateLimit-Limit and X-RateLimit-Remaining headers.
For public access, requests for data older than one year are silently clipped — the server adjusts start_datetime to one year ago rather than returning an error.
Conventions
Pagination. Most list endpoints accept page (default 1) and limit (default 20, max 100 — 500 for /flash). Responses include a pagination object:
{
"data": [ ... ],
"pagination": {
"total": 1843,
"page": 1,
"per_page": 20,
"total_pages": 93
}
}
Datetimes. All datetime parameters use RFC3339 (YYYY-MM-DDTHH:MM:SSZ). URL-encode + in timezone offsets as %2B. The /prices endpoint is the one exception — it takes plain dates (YYYY-MM-DD).
IDs vs. names. Endpoints that return entity IDs (e.g. scopes, category) leave name resolution to the client. Use /scopes and /categories to fetch the lookup tables.
Errors. Errors return an appropriate HTTP status and a JSON body:
{ "error": "min_significance cannot be greater than max_significance" }
Quickstart
# Health check
curl https://api.cryptominute.ai/health
# Latest 5 Bitcoin news items
curl "https://api.cryptominute.ai/news?ticker=BTC&limit=5"
# Top 10 stories right now
curl "https://api.cryptominute.ai/stories?limit=10"
# Five-dimensional analytics for ETH over the last 30 days
curl "https://api.cryptominute.ai/analytics?ticker=ETH&period=30d"
Endpoints
Health
GET / · GET /health
Returns API and database health.
{
"status": "healthy 💚",
"message": "All systems operational ✨",
"timestamp": "2026-05-12T10:30:00Z",
"checks": {
"database": { "healthy": true, "error": "" }
}
}
Returns 503 Service Unavailable if the database is unreachable.
Stats
GET /stats
Aggregated counters and distributions across all news.
Response (truncated):
{
"total_news_count": 482311,
"analyzed_news_count": 451220,
"unanalyzed_news_count": 31091,
"last_24h_news_count": 1284,
"last_week_news_count": 9120,
"oldest_news_date": "2022-04-01T00:00:00Z",
"newest_news_date": "2026-05-12T10:28:11Z",
"news_by_scope": { "1": 12483, "5": 9821 },
"news_by_category": { "P": 41122, "T": 22910 },
"news_by_ticker_count":{ "BTC": 50122, "ETH": 28911 },
"news_by_source": { "CoinDesk": 18221, "The Block": 14002 },
"news_avg_sentiment": 5.21,
"news_avg_market_impact": 4.83,
"news_avg_entity_scale": 5.04,
"news_avg_confidence": 0.78,
"news_avg_significance": 4.92,
"news_sentiment_distribution": { "0": 12, "10": 88 },
"news_market_impact_distribution": { "0": 11 },
"news_entity_scale_distribution": { "0": 21 },
"news_confidence_distribution": { "0.0-0.1": 12, "0.9-1.0": 84221 },
"news_significance_distribution": { "0": 41 },
"total_sources_count": 134,
"active_sources_count": 121
}
Top-50 only for news_by_ticker_count and news_by_source.
News
The news pipeline ingests RSS feeds, deduplicates, and runs each article through the AI feature extractor to produce sentiment (0–10), market_impact (0–10), entity_scale (0–10), confidence (0–1), and a composite significance score (0–10).
GET /news
Paginated list of articles, newest first by default.
Query parameters
| Param | Type | Notes |
|---|---|---|
page | int | default 1 |
limit | int | default 20, max 100 |
query | string | Case-insensitive search across title, summary, source name |
ticker | string | Primary LLM-validated ticker, case-insensitive exact match |
scopes | string | Comma-separated scope IDs; OR semantics |
categories | string | Comma-separated category IDs (case-sensitive); OR semantics |
min_significance / max_significance | float | 0–10 |
min_sentiment / max_sentiment | int | 0–10 |
analyzed | true | false | Omit to include both |
start_datetime / end_datetime | RFC3339 | inclusive |
sort_by | published_date | significance | default published_date |
sort_order | asc | desc | default desc |
Mismatched ranges (min > max) return 400.
Example
curl "https://api.cryptominute.ai/news?ticker=BTC&min_significance=7&start_datetime=2026-05-01T00:00:00Z&sort_by=significance&sort_order=desc"
Response
{
"data": [
{
"id": 482310,
"unique_id": "5b1f...",
"title": "Bitcoin reclaims $80k as ETF inflows accelerate",
"source_name": "CoinDesk",
"source_id": 5,
"url": "https://www.coindesk.com/...",
"image_url": "https://...",
"summary": "Bitcoin rallied past $80,000...",
"tags": "etf,inflows",
"published_date": "2026-05-12T10:15:00Z",
"tickers": "BTC,ETH",
"ticker": "BTC",
"scopes": "1,5",
"category": "P",
"market_impact": 8,
"entity_scale": 7,
"sentiment": 8,
"confidence": 0.91,
"significance": 8.42,
"analyzed": true,
"created_at": "2026-05-12T10:16:02Z",
"updated_at": "2026-05-12T10:18:55Z"
}
],
"pagination": { "total": 1284, "page": 1, "per_page": 20, "total_pages": 65 }
}
summary is truncated to 1000 characters with … appended if longer.
GET /news/{id}
Single article. Returns 404 if the article doesn't exist.
Stories
A story is a cluster of related articles unified into a single narrative by the stories pipeline. Each story carries an aggregated score, sentiment, scopes, ticker(s), and a short + long summary.
GET /stories
Top stories with their hydrated articles.
Query parameters
| Param | Type | Notes |
|---|---|---|
page | int | default 1 |
limit | int | default 5, max 20 |
category | string | Comma-separated category IDs |
ticker | string | Comma-separated tickers |
status | active | archived | Omit for both |
sort_by | score | updated_at | default score |
Response
{
"data": [
{
"story_id": 9213,
"story_title": "ETF inflows push Bitcoin to new highs",
"story_summary": "Three days of record inflows...",
"story_long_summary": "Bitcoin spot ETFs...",
"story_score": 9.12,
"sentiment": 7.4,
"category": "P",
"scopes": [1, 5],
"ticker": "BTC",
"tickers": ["BTC", "ETH"],
"start_time": "2026-05-09T12:00:00Z",
"end_time": "2026-05-12T10:00:00Z",
"status": "active",
"image_url": "https://...",
"articles": [
{
"id": 482310,
"title": "Bitcoin reclaims $80k...",
"source_name": "CoinDesk",
"url": "https://...",
"scopes": "1,5",
"category": "P",
"significance": 8.42
}
]
}
],
"pagination": { "total": 318, "page": 1, "per_page": 5, "total_pages": 64 },
"last_updated_all": "2026-05-12T10:18:55Z"
}
GET /story/{storyID}
Single story by ID (active or archived). Same shape as a data[] entry above.
Sources
The list of news outlets the ETL pipeline ingests.
GET /sources
Paginated list, sorted by reach score.
GET /sources/active
Same as /sources but filtered to active = true.
Response shape
{
"data": [
{
"id": 5,
"name": "CoinDesk",
"url": "https://www.coindesk.com/...",
"active": true,
"reach_score": 9.2
}
],
"pagination": { "total": 134, "page": 1, "per_page": 20, "total_pages": 7 }
}
GET /sources/stats
{ "total_count": 134, "active_count": 121 }
Scopes
Scopes are high-level news categories (e.g. Regulation, Markets, Tech). Use them to resolve scope IDs returned by /news and /stories.
GET /scopes
{
"data": [
{ "id": 1, "short_name": "REG", "long_name": "Regulation", "description": "..." },
{ "id": 5, "short_name": "MKT", "long_name": "Markets", "description": "..." }
]
}
GET /scopes/{id}
Single scope.
Categories
Event types that classify what kind of event an article reports (Partnership, Technology, etc.). Category IDs are short strings (P, T, ...).
GET /categories
{
"data": [
{ "id": "P", "name": "Partnership", "description": "..." },
{ "id": "T", "name": "Technology", "description": "..." }
]
}
GET /categories/{id}
Single category.
Analytics
The Five-Dimensional Narrative Analysis Engine. For a single ticker, returns a daily time series of six signals plus the underlying articles.
GET /analytics
Required
ticker(string) — e.g.BTC
Optional
| Param | Default | Notes |
|---|---|---|
period | 30d | 7d, 30d, 90d, 1y |
min_reach_score | 3.5 | Filter low-reach sources out of the calculation |
page / limit | 1 / 20 | For the items array; limit capped at 100 |
Example
curl "https://api.cryptominute.ai/analytics?ticker=BTC&period=90d&min_reach_score=4"
Response
{
"chartData": [
{
"date": "2026-05-12",
"priceUSD": 80214.50,
"itemVolume": 42,
"significancePerArticle": 6.18,
"sentimentMomentum": 38.4,
"narrativeConviction": 6.2,
"thematicVolatility": 0.21
}
],
"items": [],
"pagination": { "total": 1842, "page": 1, "per_page": 20, "total_pages": 93 }
}
Signals
| Field | Question | Range |
|---|---|---|
priceUSD | Daily close price in USD | ≥ 0 |
itemVolume | How much is the market talking? | int ≥ 0 |
significancePerArticle | How impactful was the average story? | 0–10 |
sentimentMomentum | Total bullish/bearish force today | unbounded |
narrativeConviction | Volume-normalized intensity | -10 to +10, may be null |
thematicVolatility | Did the theme of the conversation change? (Jensen–Shannon divergence vs. trailing 7 days) | 0 to 1, may be null |
Prices
Historical OHLCV from CoinGecko. Uses plain dates (YYYY-MM-DD), not RFC3339.
GET /prices/{symbol}
Daily OHLCV for the given ticker.
Query parameters
| Param | Default | Notes |
|---|---|---|
timeframe | 1d | Currently 1d |
start | — | YYYY-MM-DD |
end | — | YYYY-MM-DD, inclusive |
Example
curl "https://api.cryptominute.ai/prices/BTC?start=2026-01-01&end=2026-05-12"
Response — bare array, newest first:
[
{
"symbol": "BTC",
"date": "2026-05-12",
"open_usd": 79410.22,
"high_usd": 80520.18,
"low_usd": 79008.50,
"close_usd": 80214.50,
"volume_usd": 42118392910.0
}
]
GET /prices/{symbol}/latest
Most recent daily price. Returns a single object (same shape as above). 404 if no data exists for the symbol.
GET /prices/health
{ "status": "ok", "service": "prices", "timestamp": "2026-05-12T10:30:00Z" }
The Reddit service tracks posts across 100+ crypto subreddits and computes impact_score, attention, sentiment, and a list of candidate_tickers.
Posts
GET /reddit/posts
Query parameters
| Param | Notes |
|---|---|
page, limit | Pagination (limit max 100) |
subreddit | Filter by subreddit |
ticker | Filter by candidate ticker |
query | Text search across title and self-text |
min_impact / max_impact | Float |
start_datetime / end_datetime | RFC3339 |
sort_by | e.g. posted_at, impact_score |
sort_order | asc | desc |
Response
{
"data": [
{
"id": 88122,
"reddit_id": "t3_xyz",
"subreddit": "Bitcoin",
"title": "Bitcoin breaks $80k!",
"url": "https://reddit.com/r/Bitcoin/...",
"is_self": false,
"self_text": "",
"upvotes": 14210,
"num_comments": 882,
"num_awards": 14,
"flair": "News",
"impact_score": 8.4,
"sentiment": 8,
"attention": 6.1,
"candidate_tickers": ["BTC"],
"image_url": "https://...",
"is_gallery": false,
"created_utc": "2026-05-12T08:11:00Z",
"updated_at": "2026-05-12T10:18:55Z"
}
],
"pagination": { "total": 9412, "page": 1, "per_page": 20, "total_pages": 471 }
}
GET /reddit/posts/{id}
Single Reddit post.
Subreddits
| Endpoint | Description |
|---|---|
GET /reddit/subreddits | Paginated subreddit metadata. Filters: category, priority, ticker, query, is_active |
GET /reddit/subreddits/active | Only active subreddits |
GET /reddit/subreddits/by-priority | Flattened, ordered high → medium → low |
GET /reddit/subreddits/by-category | Flattened, grouped by category. Use limit for top-N per category |
GET /reddit/subreddits/categories | Array of unique category strings |
GET /reddit/subreddits/stats | Aggregate stats across all active subreddits |
GET /reddit/subreddits/{name}/stats | Per-subreddit stats by name |
GET /reddit/subreddits/{name}/metadata | Subreddit + stats bundle |
GET /reddit/subreddits/{id} | Subreddit by numeric ID |
Subreddit shape
{
"id": 42,
"name": "Bitcoin",
"display_name": "r/Bitcoin",
"ticker": "BTC",
"members": 6800000,
"description": "...",
"priority": "high",
"category": "major_cryptocurrencies",
"is_active": true,
"post_count": 14210,
"avg_impact": 5.83,
"last_post_at": "2026-05-12T10:11:00Z",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2026-05-12T10:18:55Z"
}
GET /reddit/healthz
{ "status": "ok", "message": "Reddit service is healthy" }
YouTube
The YouTube service ingests videos from a curated list of channels, transcribes, and scores engagement_score.
GET /youtube/videos
Query parameters
| Param | Notes |
|---|---|
page, limit | Pagination |
channel_id | Exact YouTube channel ID |
channel | Channel name(s), comma-separated for multiple |
channel_name | Backwards-compatible partial-match channel name |
ticker | Filter by candidate ticker (uppercased) |
query | Search across title, description, transcript, channel name |
min_engagement / max_engagement | 0–10 |
start_datetime / end_datetime | RFC3339 |
sort_by | publish_date, engagement_score, views, likes |
sort_order | asc | desc |
Response
{
"data": [
{
"id": 1842,
"video_id": "dQw4w9WgXcQ",
"channel_id": "UC...",
"channel_name": "Coin Bureau",
"subscriber_count": 2400000,
"title": "Bitcoin's next move?",
"description": "...",
"publish_date": "2026-05-12T07:00:00Z",
"views": 312000,
"likes": 18200,
"comment_count": 2104,
"candidate_tickers": ["BTC"],
"transcript": "...",
"engagement_score": 8.2,
"embed_url": "https://www.youtube.com/embed/dQw4w9WgXcQ",
"watch_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"updated_at": "2026-05-12T10:18:55Z"
}
],
"pagination": { "total": 8211, "page": 1, "per_page": 20, "total_pages": 411 }
}
GET /youtube/videos/{id}
Single video.
GET /youtube/channels
Aggregated per-channel stats with pagination and sorting (sort_by: video_count, total_views, avg_engagement, latest_video).
GET /youtube/stats
Global YouTube stats including total_videos, total_channels, total_views, total_likes, average_engagement, top_channels (top 10), videos_by_channel, ticker_mentions, and 24h/week counts.
GET /youtube/healthz
{ "status": "healthy", "service": "youtube", "timestamp": "2026-05-12T10:30:00Z", "video_count": 8211 }
Flash Feed
Real-time flash posts ingested from Telegram channels. Supports both REST polling and a WebSocket push channel.
GET /flash
Query parameters
| Param | Notes |
|---|---|
page, limit | default 1 / 20, max 500 |
ticker | Filter by ticker |
since | RFC3339 — posts since this time |
source_id | Filter by source ID |
source_name | Partial match on source name |
channel_ids | Comma-separated Telegram channel IDs |
start_date / end_date | RFC3339 |
sort_by | posted_at (default) | id |
sort_order | asc | desc (default desc) |
Response
{
"data": [
{
"id": 991823,
"channel_id": -1001234567890,
"channel_alias": "crypto_news",
"post_text": "Bitcoin surges past $80,000!",
"post_link": "https://t.me/crypto_news/456",
"tickers": ["BTC"],
"posted_at": "2026-05-12T10:30:00Z",
"created_at": "2026-05-12T10:30:05Z",
"updated_at": "2026-05-12T10:30:05Z"
}
],
"pagination": { "total": 100, "page": 1, "per_page": 20, "total_pages": 5 }
}
When advanced filters are used (source_id, source_name, channel_ids, dates, sort_by), each post additionally includes a nested flash_source object.
GET /flash/{id}
Single flash post.
GET /flash/ws (WebSocket)
Pushes each new flash post to subscribed clients as a JSON message.
const ws = new WebSocket('wss://api.cryptominute.ai/flash/ws')
ws.onmessage = (event) => {
const post = JSON.parse(event.data)
console.log('flash:', post.post_text)
}
Connect with wss:// in production; the server also accepts ws:// for local development.
Flash sources
The Telegram channels feeding the flash feed.
| Endpoint | Description |
|---|---|
GET /flash/sources | Paginated list. Filter by channel_ids (comma-separated) |
GET /flash/sources/active | Only active sources |
GET /flash/sources/stats | Aggregate stats |
GET /flash/sources/{id} | Single source |
Source shape
{
"id": 1,
"channel_id": -1001234567890,
"channel_alias": "crypto_news",
"name": "Crypto News Channel",
"description": "Latest crypto updates",
"type": "telegram",
"active": true,
"priority": 1,
"post_count": 14210,
"last_posted_at": "2026-05-12T10:30:00Z",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2026-05-12T10:30:00Z"
}
Error responses
| Status | Meaning |
|---|---|
400 Bad Request | Malformed or out-of-range parameter — body explains which |
404 Not Found | Resource doesn't exist |
429 Too Many Requests | Rate limit exceeded — see X-RateLimit-Remaining |
500 Internal Server Error | Something went wrong on our end |
503 Service Unavailable | Health check failed (e.g. database unreachable) |
All errors are JSON: { "error": "..." }.
Support
Questions, bug reports, or want a higher rate-limit tier? Reach out via the about page or open an issue on GitHub.