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

TierRate limitHistorical data
Public (no key)60 req/minLast 1 year only
Standard300 req/minUnlimited
Premium1,000 req/minUnlimited

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 100500 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

ParamTypeNotes
pageintdefault 1
limitintdefault 20, max 100
querystringCase-insensitive search across title, summary, source name
tickerstringPrimary LLM-validated ticker, case-insensitive exact match
scopesstringComma-separated scope IDs; OR semantics
categoriesstringComma-separated category IDs (case-sensitive); OR semantics
min_significance / max_significancefloat0–10
min_sentiment / max_sentimentint0–10
analyzedtrue | falseOmit to include both
start_datetime / end_datetimeRFC3339inclusive
sort_bypublished_date | significancedefault published_date
sort_orderasc | descdefault 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

ParamTypeNotes
pageintdefault 1
limitintdefault 5, max 20
categorystringComma-separated category IDs
tickerstringComma-separated tickers
statusactive | archivedOmit for both
sort_byscore | updated_atdefault 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

ParamDefaultNotes
period30d7d, 30d, 90d, 1y
min_reach_score3.5Filter low-reach sources out of the calculation
page / limit1 / 20For 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

FieldQuestionRange
priceUSDDaily close price in USD≥ 0
itemVolumeHow much is the market talking?int ≥ 0
significancePerArticleHow impactful was the average story?0–10
sentimentMomentumTotal bullish/bearish force todayunbounded
narrativeConvictionVolume-normalized intensity-10 to +10, may be null
thematicVolatilityDid 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

ParamDefaultNotes
timeframe1dCurrently 1d
startYYYY-MM-DD
endYYYY-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" }

Reddit

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

ParamNotes
page, limitPagination (limit max 100)
subredditFilter by subreddit
tickerFilter by candidate ticker
queryText search across title and self-text
min_impact / max_impactFloat
start_datetime / end_datetimeRFC3339
sort_bye.g. posted_at, impact_score
sort_orderasc | 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

EndpointDescription
GET /reddit/subredditsPaginated subreddit metadata. Filters: category, priority, ticker, query, is_active
GET /reddit/subreddits/activeOnly active subreddits
GET /reddit/subreddits/by-priorityFlattened, ordered high → medium → low
GET /reddit/subreddits/by-categoryFlattened, grouped by category. Use limit for top-N per category
GET /reddit/subreddits/categoriesArray of unique category strings
GET /reddit/subreddits/statsAggregate stats across all active subreddits
GET /reddit/subreddits/{name}/statsPer-subreddit stats by name
GET /reddit/subreddits/{name}/metadataSubreddit + 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

ParamNotes
page, limitPagination
channel_idExact YouTube channel ID
channelChannel name(s), comma-separated for multiple
channel_nameBackwards-compatible partial-match channel name
tickerFilter by candidate ticker (uppercased)
querySearch across title, description, transcript, channel name
min_engagement / max_engagement0–10
start_datetime / end_datetimeRFC3339
sort_bypublish_date, engagement_score, views, likes
sort_orderasc | 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

ParamNotes
page, limitdefault 1 / 20, max 500
tickerFilter by ticker
sinceRFC3339 — posts since this time
source_idFilter by source ID
source_namePartial match on source name
channel_idsComma-separated Telegram channel IDs
start_date / end_dateRFC3339
sort_byposted_at (default) | id
sort_orderasc | 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.

EndpointDescription
GET /flash/sourcesPaginated list. Filter by channel_ids (comma-separated)
GET /flash/sources/activeOnly active sources
GET /flash/sources/statsAggregate 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

StatusMeaning
400 Bad RequestMalformed or out-of-range parameter — body explains which
404 Not FoundResource doesn't exist
429 Too Many RequestsRate limit exceeded — see X-RateLimit-Remaining
500 Internal Server ErrorSomething went wrong on our end
503 Service UnavailableHealth 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.