{
 "openapi": "3.1.0",
 "info": {
  "title": "NYPTID Trends API",
  "version": "1.0",
  "description": "Free, read-only JSON API behind trends.nyptid.com. Human docs: https://trends.nyptid.com/developers"
 },
 "servers": [
  {
   "url": "https://trends.nyptid.com"
  }
 ],
 "paths": {
  "/api/trends": {
   "get": {
    "operationId": "trends",
    "summary": "Trending searches for one country and phase",
    "description": "Fields: geo, phase: echo of the request; updated: last snapshot time (ISO); historySince, snapshots: when we started recording this country and how many snapshots exist; items[]: trends, sorted by volume (now), start time (before) or last seen (after); items[].key: normalized term key, use it with /api/sources; items[].title, traffic, trafficNum: term and approximate searches (\"2000+\" and 2000); items[].peakTraffic, peakNum, peakAt: highest volume we recorded and when; items[].share: volume relative to the country's top term (0 to 1); items[].started, firstSeen, lastSeen: feed start time and our first/last sighting; items[].history: [[epoch_ms, searches], ...] one point per snapshot; items[].news[]: {title, url, source} headlines from the Google Trends feed; items[].sources: cross-source summary: sources, articles, platforms{}, coverage{last6h, prev24h, accel}, wiki, social{platform: results}, youtubeTrending{videos, countries}; items[].signal: before phase only: why it is an early signal; youtubeTrending: {available, fetchedAt, stale, note, items[{id, title, channel, url, views, likes, comments, publishedAt, thumbnail}]}: YouTube's most-popular chart for the country (when configured). Example: curl \"https://trends.nyptid.com/api/trends?geo=US&phase=now\"",
    "parameters": [
     {
      "name": "geo",
      "in": "query",
      "required": false,
      "description": "ISO 3166-1 alpha-2 country code, default US. 125 supported: see ?list=geos",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "phase",
      "in": "query",
      "required": false,
      "description": "now (default) | before | after",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "list",
      "in": "query",
      "required": false,
      "description": "geos: returns the list of supported countries instead",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Envelope"
        }
       }
      }
     },
     "400": {
      "description": "Bad parameter",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "405": {
      "description": "Wrong method",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "503": {
      "description": "Warming up or source unavailable",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/api/radar": {
   "get": {
    "operationId": "radar",
    "summary": "Trend Radar: scored candidates, calls ledger, control picks and record",
    "description": "Fields: model_version: scoring model, e.g. \"radar-1.2 (...)\"; cycles, since, geosTracked, termsLive, noSignal, warming: model state; warming is true below 6 snapshots; weights: feature weights, sum 1; candidates[]: {k, term, score 0-100, confidence low|medium|high, measured 0-1, features{name: 0-1}, unknown[], why[], geos[], maxLabel, firstSeen, observations, news}; calls[], model_calls[], control_picks[]: {id, model, term, made_at, score, confidence, geos, why, group model|control, status pending|hit|miss, graded_at, outcome}; record: {model:{calls, hit, miss, pending, hitRate}, control:{...}}; ledger: same as record (legacy name); health: source status, same as /api/sources?health=1. Example: curl \"https://trends.nyptid.com/api/radar\"",
    "parameters": [],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Envelope"
        }
       }
      }
     },
     "400": {
      "description": "Bad parameter",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "405": {
      "description": "Wrong method",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "503": {
      "description": "Warming up or source unavailable",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/api/search": {
   "get": {
    "operationId": "search",
    "summary": "Search every country, every phase and the last 7 days of history",
    "description": "Fields: q, total: query and number of matches; results[]: {key, title, geo, geoName, phase before|now|after, live, traffic, trafficNum, peakTraffic, started, firstSeen, lastSeen, news[], sources, rel}; results[].rel: match strength: 4 exact, 3 prefix, 2 substring, 1 all words, 0.5 headline only. Example: curl \"https://trends.nyptid.com/api/search?q=barcelona\"",
    "parameters": [
     {
      "name": "q",
      "in": "query",
      "required": false,
      "description": "search text, 2-120 characters, accent and case insensitive",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "limit",
      "in": "query",
      "required": false,
      "description": "1-200, default 60",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Envelope"
        }
       }
      }
     },
     "400": {
      "description": "Bad parameter",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "405": {
      "description": "Wrong method",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "503": {
      "description": "Warming up or source unavailable",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/api/sources": {
   "get": {
    "operationId": "sources",
    "summary": "Every source we have for one term, or the health of every source",
    "description": "Fields: found: false if we have no lookups for this term yet; summary: counts, as on trend items; outlets: {platform: [outlet names]}; groups: {platform: {outlet: [{title, url, at, geo, author, engagement, views}]}}; wiki: Wikipedia article daily pageviews and spike ratio; gdelt: [[hour_ms, articles]] GDELT volume, 3 days; social, socialTimeline, socialVelocity: Social Fetch per-platform results, history and pace; timeline: [[ms, articles, outlets]] per snapshot; health: {source: {ok true|false|null, note, error}}. socialFetch also has state (live, \"paused: daily cap\", \"paused: low balance\"), cap, usedToday, balance and platforms{}. Example: curl \"https://trends.nyptid.com/api/sources?term=roma\"",
    "parameters": [
     {
      "name": "term",
      "in": "query",
      "required": false,
      "description": "the trend text or its key",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "health",
      "in": "query",
      "required": false,
      "description": "1: return live status of every source instead",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Envelope"
        }
       }
      }
     },
     "400": {
      "description": "Bad parameter",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "405": {
      "description": "Wrong method",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "503": {
      "description": "Warming up or source unavailable",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/api/evergreen": {
   "get": {
    "operationId": "evergreen",
    "summary": "Terms that persist across many snapshots, days and countries",
    "description": "Fields: criteria: the exact rules used; historyDays, enoughHistory, note: honest status while history is shorter than 7 days; evergreen[]: {key, title, firstSeen, lastSeen, spanDays, daysSeen, snapshotsSeen, countriesEver, maxCountriesAtOnce, liveNow, countries[]}; emerging[]: same shape: terms seen on 2+ days in 2+ countries; termsTracked: how many terms are in history. Example: curl \"https://trends.nyptid.com/api/evergreen\"",
    "parameters": [],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Envelope"
        }
       }
      }
     },
     "400": {
      "description": "Bad parameter",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "405": {
      "description": "Wrong method",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "503": {
      "description": "Warming up or source unavailable",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/api/donate": {
   "get": {
    "operationId": "donate",
    "summary": "Support checkout configuration (POST creates a Stripe Checkout Session from the browser)",
    "description": "Fields: enabled: whether checkout is available; min, max, currency, presets: allowed amounts in USD. Example: curl \"https://trends.nyptid.com/api/donate\"",
    "parameters": [],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Envelope"
        }
       }
      }
     },
     "400": {
      "description": "Bad parameter",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "405": {
      "description": "Wrong method",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "503": {
      "description": "Warming up or source unavailable",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  }
 },
 "components": {
  "schemas": {
   "Envelope": {
    "type": "object",
    "required": [
     "ok",
     "version",
     "generated_at",
     "data"
    ],
    "additionalProperties": true,
    "properties": {
     "ok": {
      "type": "boolean"
     },
     "version": {
      "type": "string",
      "example": "1.0"
     },
     "generated_at": {
      "type": "string",
      "format": "date-time"
     },
     "data": {
      "type": "object",
      "additionalProperties": true
     }
    }
   },
   "Error": {
    "type": "object",
    "required": [
     "ok",
     "version",
     "generated_at",
     "error",
     "error_code"
    ],
    "properties": {
     "ok": {
      "const": false
     },
     "version": {
      "type": "string"
     },
     "generated_at": {
      "type": "string",
      "format": "date-time"
     },
     "error": {
      "type": "string"
     },
     "error_code": {
      "type": "string",
      "enum": [
       "unsupported_geo",
       "bad_phase",
       "missing_term",
       "method_not_allowed",
       "warming_up",
       "upstream_unavailable"
      ]
     },
     "data": {
      "type": "null"
     }
    }
   }
  }
 }
}