{
  "openapi": "3.1.0",
  "info": {
    "title": "Mora Discover API",
    "version": "1.0.0",
    "summary": "Public read-only API for the Mora Discover ecommerce news feed.",
    "description": "The Mora Discover API exposes the publicly-readable Discover feed at app.mora-marketer.com. Discover is a Perplexity-style ecommerce news surface that ingests ~2,000 items/day across 60+ sources, deduplicates by canonical URL, clusters semantically, and synthesizes multi-source articles via Gemini 3 Flash with paragraph-level source citations.\n\nThis API is **public** (no auth required for the documented endpoints). Use it to embed Discover content, build agent workflows, or cite Discover articles in agent responses.\n\n**This API does NOT cover** the protected Mora product surface (campaigns, scheduling, account management) which requires authentication and is not currently exposed to public agents.",
    "contact": {
      "name": "Mora support",
      "email": "hello@mora-marketer.com",
      "url": "https://www.mora-marketer.com/contact"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://www.mora-marketer.com/legal/terms-of-service"
    }
  },
  "servers": [
    {
      "url": "https://app.mora-marketer.com",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Mora marketing site",
    "url": "https://www.mora-marketer.com"
  },
  "tags": [
    {
      "name": "discover",
      "description": "Public Discover feed endpoints"
    }
  ],
  "paths": {
    "/api/discover": {
      "get": {
        "operationId": "listDiscoverArticles",
        "summary": "List Discover articles",
        "description": "Returns a feed of trending ecommerce/Shopify news articles. Articles are ordered by trending score (descending) then by published date.\n\nThe response includes a `featured` article (the highest-trending or explicitly-featured item) and an `articles` array. Use the `category` parameter to filter by topic, or `topics` (with `view=for-you`) to filter by a comma-separated list of topics.",
        "tags": ["discover"],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of articles to return. Clamped to [1, 100].",
            "required": false,
            "schema": { "type": "integer", "default": 60, "minimum": 1, "maximum": 100 }
          },
          {
            "name": "category",
            "in": "query",
            "description": "Filter to a single category. Overrides `topics` if both are supplied.",
            "required": false,
            "schema": { "$ref": "#/components/schemas/Category" }
          },
          {
            "name": "view",
            "in": "query",
            "description": "Feed view. `top` returns global top trending; `for-you` filters to the supplied `topics` if any.",
            "required": false,
            "schema": { "type": "string", "enum": ["top", "for-you"], "default": "for-you" }
          },
          {
            "name": "topics",
            "in": "query",
            "description": "Comma-separated list of topic categories to filter to (used when `view=for-you`).",
            "required": false,
            "schema": { "type": "string", "example": "marketing,social,ai" }
          }
        ],
        "responses": {
          "200": {
            "description": "Discover feed.",
            "headers": {
              "Cache-Control": {
                "description": "CDN cache directive for the feed.",
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60, stale-while-revalidate=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DiscoverFeedResponse" }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
            }
          },
          "503": {
            "description": "Backend (Supabase) unavailable.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
            }
          }
        }
      }
    },
    "/api/discover/{id}": {
      "get": {
        "operationId": "getDiscoverArticle",
        "summary": "Get a single Discover article",
        "description": "Returns a single Discover article by its UUID, including the full article body (scraped on-demand if not yet cached), citations, the cluster of source publications, the multi-source synthesized body (when 3+ publications cover the story), and 3 related articles in the same category.",
        "tags": ["discover"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID of the Discover article.",
            "schema": {
              "type": "string",
              "format": "uuid",
              "example": "00000000-0000-0000-0000-000000000000"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Article detail.",
            "headers": {
              "Cache-Control": {
                "description": "CDN cache directive.",
                "schema": {
                  "type": "string",
                  "example": "public, max-age=300, stale-while-revalidate=600"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DiscoverArticleDetail" }
              }
            }
          },
          "400": {
            "description": "Missing or invalid `id` parameter.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
            }
          },
          "404": {
            "description": "Article not found.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
            }
          }
        }
      }
    },
    "/api/discover-shell/{id}": {
      "get": {
        "operationId": "getDiscoverShell",
        "summary": "Get the SSR HTML shell for an article",
        "description": "Returns server-rendered HTML for a Discover article \u2014 used by the Vercel rewrite to make `/discover/:id` crawler-friendly. Includes Open Graph tags and JSON-LD `NewsArticle` structured data.",
        "tags": ["discover"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID of the Discover article.",
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "HTML shell (Open Graph + JSON-LD)",
            "content": { "text/html": { "schema": { "type": "string" } } }
          },
          "404": {
            "description": "Article not found.",
            "content": { "text/html": { "schema": { "type": "string" } } }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Category": {
        "type": "string",
        "enum": [
          "marketing",
          "content",
          "social",
          "ai",
          "platform",
          "general",
          "lifestyle",
          "commerce",
          "design",
          "tech",
          "creator"
        ],
        "description": "Discover topic category."
      },
      "ArticleSource": {
        "type": "object",
        "required": ["name", "domain"],
        "properties": {
          "name": { "type": "string", "description": "Publisher name.", "example": "TechCrunch" },
          "domain": {
            "type": "string",
            "description": "Publisher root domain.",
            "example": "techcrunch.com"
          },
          "faviconUrl": {
            "type": ["string", "null"],
            "format": "uri",
            "description": "URL to the publisher favicon."
          }
        }
      },
      "DiscoverArticle": {
        "type": "object",
        "required": [
          "id",
          "title",
          "url",
          "source",
          "category",
          "tags",
          "trendingScore",
          "sourceCount"
        ],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "title": { "type": "string" },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Canonical URL of the original article."
          },
          "description": { "type": ["string", "null"] },
          "aiSummary": {
            "type": ["string", "null"],
            "description": "Single-paragraph Sonar-generated summary."
          },
          "imageUrl": { "type": ["string", "null"], "format": "uri" },
          "source": { "$ref": "#/components/schemas/ArticleSource" },
          "publishedAt": { "type": ["string", "null"], "format": "date-time" },
          "category": { "$ref": "#/components/schemas/Category" },
          "tags": { "type": "array", "items": { "type": "string" } },
          "trendingScore": { "type": "number", "format": "float" },
          "sourceCount": {
            "type": "integer",
            "description": "Number of distinct publishers covering this story."
          }
        }
      },
      "DiscoverFeedResponse": {
        "type": "object",
        "required": ["articles", "refreshedAt", "count"],
        "properties": {
          "featured": {
            "oneOf": [{ "$ref": "#/components/schemas/DiscoverArticle" }, { "type": "null" }]
          },
          "articles": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/DiscoverArticle" }
          },
          "refreshedAt": { "type": "string", "format": "date-time" },
          "count": { "type": "integer" }
        }
      },
      "ClusterSource": {
        "type": "object",
        "required": ["name", "domain", "url"],
        "properties": {
          "name": { "type": "string" },
          "domain": { "type": "string" },
          "url": { "type": "string", "format": "uri" },
          "faviconUrl": { "type": ["string", "null"], "format": "uri" },
          "title": { "type": ["string", "null"] },
          "snippet": { "type": ["string", "null"] }
        }
      },
      "SynthesizedBody": {
        "type": "object",
        "required": ["headline", "dek", "paragraphs", "sources"],
        "description": "Multi-source synthesized body (Gemini 3 Flash). Present only when 3+ publishers cover the same story.",
        "properties": {
          "headline": { "type": "string" },
          "dek": { "type": "string" },
          "paragraphs": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["text", "source_ids"],
              "properties": {
                "text": { "type": "string" },
                "source_ids": { "type": "array", "items": { "type": "integer" } }
              }
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["id", "url", "publication", "domain"],
              "properties": {
                "id": { "type": "integer" },
                "url": { "type": "string", "format": "uri" },
                "publication": { "type": "string" },
                "domain": { "type": "string" }
              }
            }
          }
        }
      },
      "DiscoverArticleDetail": {
        "allOf": [
          { "$ref": "#/components/schemas/DiscoverArticle" },
          {
            "type": "object",
            "properties": {
              "articleBody": {
                "type": ["string", "null"],
                "description": "Scraped article body in markdown."
              },
              "wordCount": { "type": ["integer", "null"] },
              "citations": {
                "type": "array",
                "items": { "type": "string", "format": "uri" },
                "description": "AI-extracted citations."
              },
              "clusterSources": {
                "type": "array",
                "items": { "$ref": "#/components/schemas/ClusterSource" }
              },
              "synthesizedBody": {
                "oneOf": [{ "$ref": "#/components/schemas/SynthesizedBody" }, { "type": "null" }]
              },
              "related": {
                "type": "array",
                "items": { "$ref": "#/components/schemas/DiscoverArticle" },
                "description": "Up to 3 related articles in the same category."
              }
            }
          }
        ]
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message. Status code carries the machine-readable signal.",
            "example": "Article not found"
          }
        }
      }
    }
  }
}
