Skip to main content

API Documentation

REST JSON API. The public read endpoints work without a key at the free rate limit; send a customer API key to apply your plan's limit. Built for programmatic access to LeanForge keyword data.

16

Endpoints

JSON

Content type

100/15m

Free rate limit (per user / IP)

Base URL

https://lean-forge.net/api/v1
OpenAPI 3.1 JSONDownload sample CSV

Authentication

The GET endpoints listed below are public: with no key you are bucketed by IP at the free limit. To use a paid plan's limit, send a customer API key with each request. Keys are created in your account (sign-in required) and look like lf_live_…. The full key is shown once, at creation — store it then; it cannot be retrieved later. If you lose it, revoke it and create a new one.

Either header form is accepted:

# Option 1: X-API-Key header
curl https://lean-forge.net/api/v1/keywords/trending?limit=10 \
  -H "X-API-Key: lf_live_YOUR_KEY"

# Option 2: Authorization bearer
curl https://lean-forge.net/api/v1/keywords/trending?limit=10 \
  -H "Authorization: Bearer lf_live_YOUR_KEY"
  • Read access only. A customer key identifies your account so your plan's rate limit applies. It cannot create, update, or delete keywords, categories, or posts, and it cannot manage keys — those require the admin secret or a signed-in session.
  • Rate limit follows the plan. Keyed requests are bucketed per account (not per key) at the plan's limit: Free 100, Starter 1,000, Growth 10,000 requests per 15 minutes. Every response carries an X-Plan header showing which plan was applied.
  • Keys per plan. Free 1, Starter 5, Growth 20 active keys. Revoke a key from your account and it is rejected on the very next request — keys are verified against the database on every call, with no cache.
  • Errors. An invalid or revoked key returns 401 with {"error":"unauthorized","reason":"invalid_api_key"}. A request with no lf_live_ key is not rejected — it simply falls back to the anonymous free bucket.

Endpoints

GET
/api/v1/keywords

List all keywords with pagination

GET
/api/v1/keywords/trending

Trending keywords (rising direction)

GET
/api/v1/keywords/export

Download CSV or JSON keyword data

GET
/api/v1/keywords/:slug

Get a single keyword with trend data

GET
/api/v1/keywords/:slug/next-actions

Get the deterministic content verdict

GET
/api/v1/categories

List all categories

GET
/api/v1/categories/:slug

Get a single category with its keywords

GET
/api/v1/trends

List trend data points, filterable

GET
/api/v1/trends/daily

Google Trends daily feed, proxied. Answers 503 upstream_unavailable when the feed cannot be read (Google has retired it; currently always 503), never an empty 200

GET
/api/v1/trends/realtime

Google Trends "realtime" stories feed, proxied. Answers 503 upstream_unavailable when the feed cannot be read (Google has retired it; currently always 503), never an empty 200

GET
/api/v1/trends/compare

Compare 2-5 keywords side by side

GET
/api/v1/trends/:keywordId/timeline

90-day timeline for a keyword

GET
/health

Liveness probe (Railway healthcheck)

GET
/health/deep

Deep probe: DB + Redis + uptime

GET
/api/v1/blog

List published blog posts

GET
/api/v1/blog/:slug

Get a published blog post

Example: fetch trending

curl https://lean-forge.net/api/v1/keywords/trending?limit=10

{
  "data": [
    {
      "id": "...",
      "term": "AI code review",
      "slug": "ai-code-review",
      "category": "ai",
      "trendScore": 73,
      "velocity": 23.4,
      "direction": "rising",
      "isActive": true,
      "breakdown": {
        "components": [
          { "key": "velocity7d", "label": "7-day velocity", "rawValue": 23.4, "subScore": 55.85, "weight": 0.45, "contribution": 25.13, "source": "Google Trends", "measured": true },
          { "key": "velocity30d", "label": "30-day velocity", "rawValue": 60, "subScore": 80, "weight": 0.2, "contribution": 16, "source": "Google Trends", "measured": true },
          { "key": "currentInterest", "label": "Current interest", "rawValue": 90, "subScore": 90, "weight": 0.35, "contribution": 31.5, "source": "Google Trends", "measured": true }
        ],
        "composite": 72.63,
        "trendScore": 73,
        "measured": true,
        "asOf": "2026-09-08T00:00:00.000Z"
      }
    }
  ],
  "_meta": {
    "basis": "rising",
    "regent_cta": {
      "headline": "Ready to rank for this keyword?",
      "url": "https://seo-ai-regent.com/?ref=keyword-trend-api"
    }
  }
}

JavaScript

const response = await fetch(
  'https://lean-forge.net/api/v1/keywords/trending?limit=10'
);

if (!response.ok) throw new Error(`LeanForge API: ${response.status}`);
const { data, _meta } = await response.json();
console.log(_meta.basis, data);

Python

import requests

response = requests.get(
    "https://lean-forge.net/api/v1/keywords/trending",
    params={"limit": 10},
    timeout=10,
)
response.raise_for_status()
payload = response.json()
print(payload["_meta"]["basis"], payload["data"])

Rate limiting

Requests are rate-limited per identity: requests carrying a customer API key or a signed-in session token are bucketed per account at that account's plan limit, and anonymous requests are bucketed per IP at the free limit. Every response includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix seconds). When a request is rejected with 429, the response also includes a Retry-After header (seconds). The free tier allows 100 requests per 15-minute window; the Starter and Growth plans raise that to 1,000 and 10,000 requests per 15-minute window respectively.