{
  "name": "NOOPS API",
  "version": "1.0",
  "description": "Public API for NOOPS — real-time intelligence on the AI transformation of business and technology. Data covers AI models, stocks, GitHub repos, Hugging Face, OpenRouter, AI provider status, editorial signals, and long-form research posts.",
  "base_url": "https://noops.au",
  "documentation": "https://noops.au/api-docs.md",
  "llms_txt": "https://noops.au/llms.txt",
  "rss": "https://noops.au/rss",
  "authors": [
    "John Allsopp",
    "Mark Pesce"
  ],
  "authentication": {
    "type": "session_cookie",
    "note": "Most endpoints return data without authentication. Unauthenticated requests are treated as free-tier and receive limited data. For full access, authenticate via magic link (POST /api/auth/login) and include the session cookie."
  },
  "tiers": {
    "free": "Limited data — summaries, recent items only, some endpoints restricted",
    "plus": "Full signal data, history, editorial signals",
    "pro": "Everything in plus, plus exclusive research and weekly wrap-ups"
  },
  "endpoints": {
    "GET /api/search": {
      "description": "Hybrid keyword + semantic search across posts and editorial signals. Default mode runs both FTS5 full-text and vector similarity search, boosting results found by both.",
      "auth": "none",
      "params": {
        "q": "string — required, search query (natural language or keywords)",
        "mode": "\"hybrid\" (default), \"keyword\" (FTS5 only), or \"semantic\" (vector only)",
        "type": "\"post\" or \"signal\" — filter by content type. Omit for both.",
        "limit": "number (default 20, max 50)"
      },
      "returns": "{ query, mode, results: [{ type, id, slug, title, excerpt, score, match_source }], total }"
    },
    "GET /api/posts/feed": {
      "description": "Paginated listing of published posts (title, slug, excerpt, author, tags — no body)",
      "auth": "none",
      "params": {
        "limit": "number (default 20, max 50)",
        "offset": "number (default 0)",
        "category": "string — filter by category"
      },
      "returns": "{ posts: [...], total: number }"
    },
    "GET /api/posts/:slug": {
      "description": "Single post by slug. Returns full body_html for authorized tiers; excerpt + gated:true for others. Add ?format=md to get body as markdown instead of HTML.",
      "auth": "optional — tier determines body access",
      "params": {
        "format": "\"md\" for markdown body, \"html\" (default) for HTML body"
      },
      "returns": "{ post: { id, slug, title, subtitle, body_html|body_md, author, tags, category, tier_access, excerpt, published_at }, gated: boolean }"
    },
    "GET /api/tags": {
      "description": "All unique tags used across posts and editorial signals",
      "auth": "none",
      "returns": "{ tags: string[] }"
    },
    "GET /rss": {
      "description": "Combined RSS 2.0 feed of free-tier posts and editorial signals",
      "auth": "none",
      "returns": "application/rss+xml"
    },
    "GET /api/signals/editorial/feed": {
      "description": "Published editorial signals — short-form analysis written by the NOOPS team. Free tier sees the latest 3 only.",
      "auth": "optional — free gets 3 items, plus/pro get full feed",
      "params": {
        "limit": "number (default 20, max 50)",
        "offset": "number (default 0)"
      },
      "returns": "{ signals: [{ id, title, body, source_url, tags, published_at }], total, tier, limited }"
    },
    "GET /api/signals/overview": {
      "description": "Combined dashboard of all signal types — stocks, GitHub, downloads, AI models, RSS, clippings, OpenRouter, AI status, Hugging Face, people. Tier-gated: free users see summaries and limited counts.",
      "auth": "optional — tier determines depth of data",
      "returns": "{ tier, generated_at, data_freshness, stocks, github, downloads, ai_models, rss, clippings, openrouter, ai_status, huggingface, people, entity_links }"
    },
    "GET /api/signals/ai-models": {
      "description": "AI model benchmark catalog — intelligence, coding, math, pricing, speed. Ranked by intelligence score. Data from artificialanalysis.ai.",
      "auth": "required — plus or pro",
      "returns": "{ tier, models: [{ model_name, provider, intelligence, coding, math, price_blended, output_speed, value_score, ... }], total }"
    },
    "GET /api/signals/openrouter": {
      "description": "OpenRouter model catalog with pricing — input/output costs, context length, modality, free models.",
      "auth": "required — plus or pro",
      "returns": "{ tier, models: [...], total, total_free }"
    },
    "GET /api/signals/huggingface": {
      "description": "Hugging Face trending models — downloads, likes, params, pipeline tags, top authors.",
      "auth": "required — plus or pro",
      "params": {
        "sort": "\"trending\" (default), \"downloads\", \"likes\", \"newest\", \"params\""
      },
      "returns": "{ tier, sort, models: [...], total, pipeline_tags, top_authors }"
    },
    "GET /api/signals/ai-status": {
      "description": "AI provider operational status — current status per provider, 30-day uptime %, recent incidents, 7-day status history.",
      "auth": "required — plus or pro",
      "returns": "{ tier, providers: [...], incidents: [...], history, total_incidents }"
    },
    "GET /api/signals/stocks/history": {
      "description": "Historical stock OHLCV data for tracked AI/tech tickers. Grouped by ticker.",
      "auth": "required — plus or pro",
      "params": {
        "ticker": "string — filter to single ticker (optional)"
      },
      "returns": "{ tier, history_days, tickers: { TICKER: [{ date, open, high, low, close, volume }] } }"
    },
    "GET /api/signals/github/history": {
      "description": "GitHub repo tracking history — stars, forks, issues over time. Grouped by repo.",
      "auth": "required — plus or pro",
      "returns": "{ tier, history_days, repos: { owner/repo: [{ date, stars, forks, open_issues }] } }"
    },
    "GET /api/signals/downloads/history": {
      "description": "Package download history — daily download counts for tracked npm/PyPI packages. Grouped by package:registry.",
      "auth": "required — plus or pro",
      "returns": "{ tier, history_days, packages: { name:registry: [{ date, downloads }] } }"
    },
    "GET /api/signals/people": {
      "description": "Tracked AI/tech voices — people we follow, their latest blog/RSS posts.",
      "auth": "optional — free gets limited list",
      "returns": "{ tier, people: { items: [{ slug, name, role, avatarUrl, latestPost }], total_available } }"
    },
    "GET /api/signals/person": {
      "description": "Single person detail view — bio, links, and their RSS posts.",
      "auth": "optional — free gets 5 posts, plus/pro get 100",
      "params": {
        "slug": "string — required, person slug"
      },
      "returns": "{ tier, person: { slug, name, bio, role, avatarUrl, links, posts } }"
    },
    "GET /api/signals/company": {
      "description": "Single company detail view — stock data, RSS posts, GitHub repos for a tracked company.",
      "auth": "optional — tier affects data depth",
      "params": {
        "slug": "string — required, company slug"
      },
      "returns": "{ tier, company: { slug, name, description, category, ticker, stock, posts, repos } }"
    },
    "GET /api/signals/feed": {
      "description": "RSS feed items and clippings — aggregated from tracked blogs, news sources. Includes entity cross-links.",
      "auth": "optional — tier affects limits",
      "returns": "{ tier, rss: { items, total_available }, clippings: { items, total_available }, entity_links }"
    },
    "GET /api/signals/status": {
      "description": "Signal pipeline health — last fetch time per source.",
      "auth": "none",
      "returns": "{ sources: { source: last_fetched_at } }"
    }
  },
  "usage_notes": [
    "All endpoints return JSON with Content-Type: application/json",
    "Endpoints that require auth return { error: \"upgrade_required\", upgrade_url: \"...\" } for unauthorized access",
    "Pagination uses limit/offset pattern. Default limit is 20, max is 50",
    "Dates are in ISO 8601 / SQLite datetime format (YYYY-MM-DD HH:MM:SS)",
    "Tags are JSON arrays stored as strings — parse with JSON.parse()",
    "The /rss endpoint returns XML, not JSON",
    "For markdown content from posts, use ?format=md on /api/posts/:slug",
    "This directory is also referenced from /llms.txt for LLM/agent discovery"
  ]
}