Skip to content

Statistics API

Retrieve system statistics and analytics about posts, streams, and review progress.

Overview

The Statistics API provides aggregated metrics about FenLiu's content curation pipeline. Use these endpoints to monitor system health, track curation progress, and analyze content patterns.

Key Metrics

  • Post Counts: Total, approved, rejected, pending export
  • Spam Scoring: Distribution of spam scores
  • Stream Performance: Posts per stream, activity levels
  • Review Progress: Reviewed vs. unreviewed, approval rates
  • Export Pipeline: Queue status breakdown

Authentication

All Statistics API endpoints require API key authentication via X-API-Key header:

curl -H "X-API-Key: your-api-key" \
  http://localhost:8000/api/v1/stats

See Authentication Guide for details.

Endpoints

GET /api/v1/stats

Get overall system statistics and metrics.

Request

curl -H "X-API-Key: your-api-key" \
  http://localhost:8000/api/v1/stats

Response (200 OK)

{
  "total_posts": 1245,
  "approved_posts": 892,
  "rejected_posts": 245,
  "unreviewed_posts": 108,
  "total_streams": 12,
  "active_streams": 10,
  "queue_status": {
    "pending": 234,
    "reserved": 5,
    "delivered": 650,
    "error": 3
  },
  "spam_score_distribution": {
    "very_low": 456,
    "low": 289,
    "medium": 178,
    "high": 156,
    "very_high": 166
  },
  "approval_rate": 71.5,
  "last_fetch": "2026-03-03T15:45:00Z",
  "created_at": "2026-02-15T10:00:00Z"
}

Response Fields

Field Type Description
total_posts integer Total posts in database
approved_posts integer Posts marked as approved
rejected_posts integer Posts marked as rejected
unreviewed_posts integer Posts awaiting review
total_streams integer Total hashtag streams
active_streams integer Active streams (enabled)
queue_status object Export queue breakdown by status
spam_score_distribution object Count by spam level
approval_rate number Percentage of approved posts
last_fetch string ISO 8601 timestamp of last fetch
created_at string ISO 8601 database creation time

GET /api/v1/stats/posts

Get detailed post statistics.

Request

curl -H "X-API-Key: your-api-key" \
  http://localhost:8000/api/v1/stats/posts

Response (200 OK)

{
  "total_count": 1245,
  "status": {
    "approved": 892,
    "rejected": 245,
    "unreviewed": 108
  },
  "by_spam_score": {
    "very_low_0_to_25": 456,
    "low_25_to_50": 289,
    "medium_50_to_75": 178,
    "high_75_to_100": 322
  },
  "by_queue_status": {
    "pending": 234,
    "reserved": 5,
    "delivered": 650,
    "error": 3
  },
  "with_media": 678,
  "without_media": 567,
  "by_source": {
    "mastodon.social": 456,
    "fosstodon.org": 234,
    "pixelfed.social": 178,
    "other": 377
  },
  "average_spam_score": 38.5,
  "median_spam_score": 35,
  "posts_fetched_today": 127,
  "posts_reviewed_today": 89,
  "posts_approved_today": 62
}

Response Fields

Field Type Description
total_count integer Total posts in database
status object Breakdown by approval status
by_spam_score object Distribution across score ranges
by_queue_status object Export queue status breakdown
with_media integer Posts containing media attachments
without_media integer Posts without attachments
by_source object Posts per source instance
average_spam_score number Mean spam score
median_spam_score number Median spam score
posts_fetched_today integer Posts fetched in last 24h
posts_reviewed_today integer Posts reviewed in last 24h
posts_approved_today integer Posts approved in last 24h

GET /api/v1/stats/streams

Get stream-level statistics.

Request

curl -H "X-API-Key: your-api-key" \
  http://localhost:8000/api/v1/stats/streams

Response (200 OK)

{
  "total_streams": 12,
  "active_streams": 10,
  "inactive_streams": 2,
  "streams": [
    {
      "id": 1,
      "hashtag": "python",
      "instance": "mastodon.social",
      "active": true,
      "post_count": 245,
      "approved_count": 189,
      "rejected_count": 34,
      "unreviewed_count": 22,
      "approval_rate": 77.1,
      "last_fetch": "2026-03-03T15:30:00Z"
    },
    {
      "id": 2,
      "hashtag": "django",
      "instance": "fosstodon.org",
      "active": true,
      "post_count": 178,
      "approved_count": 134,
      "rejected_count": 28,
      "unreviewed_count": 16,
      "approval_rate": 75.3,
      "last_fetch": "2026-03-03T14:45:00Z"
    }
  ],
  "total_posts_all_streams": 1245,
  "average_posts_per_stream": 103.75,
  "most_active_stream": {
    "id": 1,
    "hashtag": "python",
    "post_count": 245
  },
  "least_active_stream": {
    "id": 12,
    "hashtag": "rust",
    "post_count": 8
  }
}

Response Fields

Field Type Description
total_streams integer Total streams created
active_streams integer Currently active streams
inactive_streams integer Disabled streams
streams array Per-stream statistics
total_posts_all_streams integer Total posts across all streams
average_posts_per_stream number Mean posts per stream
most_active_stream object Stream with most posts
least_active_stream object Stream with fewest posts

Per-Stream Fields

Field Type Description
id integer Stream identifier
hashtag string Hashtag being monitored
instance string Source instance
active boolean Is stream active
post_count integer Total posts from stream
approved_count integer Approved posts
rejected_count integer Rejected posts
unreviewed_count integer Awaiting review
approval_rate number Percentage approved
last_fetch string ISO 8601 last fetch time

Examples

Python

import httpx

api_key = "your-api-key"
base_url = "http://localhost:8000/api/v1"
headers = {"X-API-Key": api_key}

# Get overall statistics
async with httpx.AsyncClient() as client:
    response = await client.get(f"{base_url}/stats", headers=headers)
    stats = response.json()

    print(f"Total posts: {stats['total_posts']}")
    print(f"Approved: {stats['approved_posts']}")
    print(f"Approval rate: {stats['approval_rate']:.1f}%")
    print(f"Pending export: {stats['queue_status']['pending']}")

# Get post statistics
async with httpx.AsyncClient() as client:
    response = await client.get(f"{base_url}/stats/posts", headers=headers)
    post_stats = response.json()

    print(f"Average spam score: {post_stats['average_spam_score']:.1f}")
    print(f"Posts fetched today: {post_stats['posts_fetched_today']}")
    print(f"Posts approved today: {post_stats['posts_approved_today']}")

# Get stream statistics
async with httpx.AsyncClient() as client:
    response = await client.get(f"{base_url}/stats/streams", headers=headers)
    stream_stats = response.json()

    print(f"Active streams: {stream_stats['active_streams']}")
    for stream in stream_stats['streams']:
        print(f"  #{stream['hashtag']}: {stream['post_count']} posts ({stream['approval_rate']:.1f}% approved)")

JavaScript

const apiKey = "your-api-key";
const baseUrl = "http://localhost:8000/api/v1";
const headers = { "X-API-Key": apiKey };

// Overall statistics
const stats = await fetch(`${baseUrl}/stats`, { headers })
  .then(r => r.json());

console.log(`Total posts: ${stats.total_posts}`);
console.log(`Approval rate: ${stats.approval_rate.toFixed(1)}%`);
console.log(`Pending export: ${stats.queue_status.pending}`);

// Post statistics
const postStats = await fetch(`${baseUrl}/stats/posts`, { headers })
  .then(r => r.json());

console.log(`Average spam score: ${postStats.average_spam_score.toFixed(1)}`);
console.log(`Reviewed today: ${postStats.posts_reviewed_today}`);

// Stream statistics
const streamStats = await fetch(`${baseUrl}/stats/streams`, { headers })
  .then(r => r.json());

console.log(`Active streams: ${streamStats.active_streams}`);
streamStats.streams.forEach(stream => {
  console.log(`  #${stream.hashtag}: ${stream.post_count} posts`);
});

cURL

export API_KEY="your-api-key"
export BASE_URL="http://localhost:8000/api/v1"

# Overall statistics
curl -H "X-API-Key: $API_KEY" "$BASE_URL/stats" | jq '.'

# Post statistics
curl -H "X-API-Key: $API_KEY" "$BASE_URL/stats/posts" | jq '.'

# Stream statistics
curl -H "X-API-Key: $API_KEY" "$BASE_URL/stats/streams" | jq '.'

# Just approval rate
curl -H "X-API-Key: $API_KEY" "$BASE_URL/stats" | jq '.approval_rate'

# Stream with most posts
curl -H "X-API-Key: $API_KEY" "$BASE_URL/stats/streams" | jq '.most_active_stream'

Use Cases

Dashboard Widgets

Use statistics endpoints to populate dashboard displays:

// Update dashboard cards every 30 seconds
setInterval(async () => {
  const stats = await fetch(`${baseUrl}/stats`, { headers })
    .then(r => r.json());

  updateCard('total-posts', stats.total_posts);
  updateCard('approval-rate', `${stats.approval_rate.toFixed(1)}%`);
  updateCard('pending-export', stats.queue_status.pending);
}, 30000);

Health Monitoring

Check system health and alert on anomalies:

# Alert if approval rate drops below threshold
stats = await client.get(f"{base_url}/stats").json()
if stats['approval_rate'] < 50:
    send_alert("Warning: Approval rate dropped below 50%")

# Alert if queue is backing up
if stats['queue_status']['pending'] > 1000:
    send_alert("Warning: Export queue has 1000+ pending posts")

Performance Metrics

Track curation pipeline performance:

# Calculate posts per hour
post_stats = await client.get(f"{base_url}/stats/posts").json()
pph = post_stats['posts_fetched_today'] / 24
print(f"Average posts per hour: {pph:.1f}")

# Calculate review rate
review_rate = post_stats['posts_reviewed_today'] / post_stats['posts_fetched_today']
print(f"Review rate: {review_rate:.1%}")

Stream Analysis

Identify underperforming or overactive streams:

stream_stats = await client.get(f"{base_url}/stats/streams").json()

# Find streams with low approval rate
for stream in stream_stats['streams']:
    if stream['approval_rate'] < 50:
        print(f"⚠️  #{stream['hashtag']}: Low approval ({stream['approval_rate']:.0f}%)")

# Find inactive streams
for stream in stream_stats['streams']:
    if not stream['active']:
        print(f"💤 #{stream['hashtag']}: Inactive for {days_since_fetch(stream)} days")

Interpretation Guide

Approval Rate

The percentage of reviewed posts that were approved:

  • 70%+: High-quality stream, good filtering
  • 50-70%: Moderate quality, some spam getting through
  • <50%: Stream needs attention, possible spam flooding

Spam Score Distribution

Posts spread across score ranges (0-25, 25-50, 50-75, 75-100):

  • Skewed toward 0-25: High-quality content, good moderation
  • Skewed toward 75-100: Lots of spam, needs more filtering
  • Balanced 25-75: Mixed content, manual review needed

Queue Status

Posts progressing through export pipeline:

  • Pending: Ready for export, waiting for consumer
  • Reserved: Currently being processed
  • Delivered: Successfully exported
  • Error: Failed export, needs investigation

Performance Tips

  1. Cache Results: Statistics change slowly; cache for 5-10 minutes
  2. Pagination: For large streams, paginate stream statistics if available
  3. Batch Queries: Get all stats in single request (e.g., /stats) rather than multiple calls
  4. Time Ranges: Consider adding date filters for historical trends (future enhancement)


Best Practices

  1. Regular Monitoring: Check statistics daily for trends
  2. Alert on Changes: Monitor approval rate and queue size
  3. Stream Health: Track stream-level metrics for inactive streams
  4. Feedback Loop: Use statistics to adjust filters and detection rules
  5. Archive History: Store periodic snapshots for trend analysis