openapi: 3.1.0
info:
  title: OpenContent API
  version: 1.0.0
  description: "Attribution-first media and content ingestion, metadata, share-card, discovery, and MCP/API layer for OpenContent."
  contact:
    name: OpenContent
    url: "https://opencontent.platphormnews.com"
servers:
  -
    url: "https://opencontent.platphormnews.com/api/v1"
    description: Production
  -
    url: "https://opencontent.platphormnews.com"
    description: Canonical site root for platform discovery routes
security:
  -
    bearerAuth: []
paths:
  "/documents":
    get:
      summary: List documents
      description: List all published documents with pagination and filtering
      tags:
        - Documents
      security: []
      parameters:
        -
          name: page
          in: query
          schema:
            type: integer
            default: 1
        -
          name: per_page
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
        -
          name: status
          in: query
          schema:
            type: string
            default: published
        -
          name: category
          in: query
          schema:
            type: string
        -
          name: q
          in: query
          schema:
            type: string
          description: Search query
        -
          name: tag
          in: query
          schema:
            type: string
      responses:
        200:
          description: List of documents
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/DocumentList"
    post:
      summary: Create document
      description: Create a new document
      tags:
        - Documents
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/DocumentCreate"
      responses:
        201:
          description: Document created
        401:
          description: Unauthorized
  "/documents/{slug}":
    get:
      summary: Get document
      description: Get a specific document by slug
      tags:
        - Documents
      security: []
      parameters:
        -
          name: slug
          in: path
          required: true
          schema:
            type: string
      responses:
        200:
          description: Document details
        404:
          description: Not found
    put:
      summary: Update document
      description: Update an existing document
      tags:
        - Documents
      parameters:
        -
          name: slug
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/DocumentUpdate"
      responses:
        200:
          description: Document updated
        401:
          description: Unauthorized
        404:
          description: Not found
    delete:
      summary: Delete document
      description: Soft delete a document
      tags:
        - Documents
      parameters:
        -
          name: slug
          in: path
          required: true
          schema:
            type: string
      responses:
        200:
          description: Document deleted
        401:
          description: Unauthorized
        403:
          description: "Forbidden - requires admin scope"
        404:
          description: Not found
  "/submissions":
    get:
      summary: List submissions
      description: List all submissions
      tags:
        - Submissions
      parameters:
        -
          name: status
          in: query
          schema:
            type: string
            enum:
              - pending
              - approved
              - rejected
        -
          name: source_url
          in: query
          schema:
            type: string
      responses:
        200:
          description: List of submissions
        401:
          description: Unauthorized
    post:
      summary: Create submission
      description: Submit new content for review (open endpoint)
      tags:
        - Submissions
      security: []
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/SubmissionCreate"
      responses:
        201:
          description: Submission created
        400:
          description: Validation error
  "/submissions/{id}":
    get:
      summary: Get submission
      tags:
        - Submissions
      parameters:
        -
          name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        200:
          description: Submission details
        404:
          description: Not found
    post:
      summary: Review submission
      description: Approve or reject a submission
      tags:
        - Submissions
      parameters:
        -
          name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/SubmissionReview"
      responses:
        200:
          description: Submission reviewed
  "/search":
    get:
      summary: Search documents
      description: "Full-text search across all documents"
      tags:
        - Search
      security: []
      parameters:
        -
          name: q
          in: query
          required: true
          schema:
            type: string
            minLength: 2
        -
          name: category
          in: query
          schema:
            type: string
        -
          name: tag
          in: query
          schema:
            type: string
        -
          name: page
          in: query
          schema:
            type: integer
        -
          name: per_page
          in: query
          schema:
            type: integer
      responses:
        200:
          description: Search results
  "/content":
    get:
      summary: List content entries
      description: "List public media/content archive entries with filtering by type, platform, category, tag, and search term."
      tags:
        - Content
      security: []
      parameters:
        -
          name: type
          in: query
          schema:
            type: string
        -
          name: platform
          in: query
          schema:
            type: string
        -
          name: category
          in: query
          schema:
            type: string
        -
          name: tags
          in: query
          schema:
            type: string
        -
          name: search
          in: query
          schema:
            type: string
        -
          name: page
          in: query
          schema:
            type: integer
            default: 1
        -
          name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
      responses:
        200:
          description: Content archive entries
    post:
      summary: Create uploaded content
      description: "Publicly create an uploaded content entry with generated attribution, SEO/AEO, OG/Twitter, JSON-LD, metatags, share-card metadata, and dynamic share links."
      tags:
        - Content
      security: []
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/ContentCreate"
      responses:
        200:
          description: Content created
        400:
          description: Validation error
  "/content/ingest":
    post:
      summary: Add URL content
      description: "Public OpenContent URL ingestion with SSRF protection. Creates or refreshes a content archive entry with attribution, SEO/AEO, OG/Twitter, JSON-LD, metatags, share-card metadata, and dynamic share links."
      tags:
        - Content
      security: []
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - url
              properties:
                url:
                  type: string
                  format: uri
                  description: Public URL to ingest
                title:
                  type: string
                description:
                  type: string
                category:
                  type: string
                  description: Category slug
                tags:
                  type: array
                  items:
                    type: string
                targetAudience:
                  type: string
                  default: everyone
                autoPublish:
                  type: boolean
                  default: true
                  description: Publish immediately when true
                refreshExisting:
                  type: boolean
                  default: false
                  description: "Protected: refresh an existing entry instead of returning a duplicate error. Requires PLATPHORM_API_KEY."
                creatorName:
                  type: string
                creatorUrl:
                  type: string
                  format: uri
                attributionText:
                  type: string
      responses:
        200:
          description: Existing content refreshed
        201:
          description: Content ingested
        400:
          description: Invalid or unsafe URL
        401:
          description: PLATPHORM_API_KEY required when refreshExisting is true
        409:
          description: Content already exists
  "/content/{id}":
    get:
      summary: Get content entry
      description: Get a single content entry by slug or id.
      tags:
        - Content
      security: []
      parameters:
        -
          name: id
          in: path
          required: true
          schema:
            type: string
          description: Content slug or UUID
      responses:
        200:
          description: Content entry
        404:
          description: Not found
  "/content/{id}/metadata":
    get:
      summary: Get generated metadata
      description: "Retrieve or dynamically generate SEO, AEO, OpenGraph, Twitter/X card, JSON-LD, metatags, share-card, share links, canonical URL, and attribution for an existing entry without re-ingesting it."
      tags:
        - Metadata
      security: []
      parameters:
        -
          name: id
          in: path
          required: true
          schema:
            type: string
          description: Content slug or UUID
      responses:
        200:
          description: Generated metadata bundle
        404:
          description: Not found
  "/content/{id}/metadata/regenerate":
    post:
      summary: Regenerate generated metadata
      description: "Protected action that regenerates and persists SEO, AEO, OpenGraph, Twitter/X card, JSON-LD, metatags, share-card, and share links for an existing entry. Requires PLATPHORM_API_KEY."
      tags:
        - Metadata
      parameters:
        -
          name: id
          in: path
          required: true
          schema:
            type: string
          description: Content slug or UUID
      responses:
        200:
          description: Metadata regenerated
        401:
          description: PLATPHORM_API_KEY required
        404:
          description: Not found
  "/content/{id}/share-links":
    get:
      summary: Get dynamic share links
      description: "Get post-creation share links for X, LinkedIn, Facebook, Reddit, Bluesky, Threads, Mastodon, email, generic share, and copy link."
      tags:
        - Metadata
      security: []
      parameters:
        -
          name: id
          in: path
          required: true
          schema:
            type: string
          description: Content slug or UUID
      responses:
        200:
          description: Share links
        404:
          description: Not found
  "/content/{id}/share-links/regenerate":
    post:
      summary: Regenerate share links
      description: "Protected alias for metadata regeneration focused on share-link persistence. Requires PLATPHORM_API_KEY."
      tags:
        - Metadata
      parameters:
        -
          name: id
          in: path
          required: true
          schema:
            type: string
          description: Content slug or UUID
      responses:
        200:
          description: Share links regenerated
        401:
          description: PLATPHORM_API_KEY required
  "/content/{id}/share-card":
    get:
      summary: "Get share-card metadata"
      description: "Get the share-card preview metadata for an existing entry."
      tags:
        - Metadata
      security: []
      parameters:
        -
          name: id
          in: path
          required: true
          schema:
            type: string
          description: Content slug or UUID
      responses:
        200:
          description: "Share-card metadata"
        404:
          description: Not found
  "/content/{id}/share-card/regenerate":
    post:
      summary: "Regenerate share-card metadata"
      description: "Protected action that persists refreshed share-card metadata. Requires PLATPHORM_API_KEY."
      tags:
        - Metadata
      parameters:
        -
          name: id
          in: path
          required: true
          schema:
            type: string
          description: Content slug or UUID
      responses:
        200:
          description: Share card regenerated
        401:
          description: PLATPHORM_API_KEY required
  "/url-preview":
    get:
      summary: Preview URL metadata
      description: "Public-safe URL preview with SSRF protection. Does not persist content."
      tags:
        - Content
      security: []
      parameters:
        -
          name: url
          in: query
          required: true
          schema:
            type: string
            format: uri
      responses:
        200:
          description: URL preview metadata
        400:
          description: Invalid or unsafe URL
    post:
      summary: Preview URL metadata
      description: "Public-safe URL preview with SSRF protection. Does not persist content."
      tags:
        - Content
      security: []
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - url
              properties:
                url:
                  type: string
                  format: uri
      responses:
        200:
          description: URL preview metadata
        400:
          description: Invalid or unsafe URL
  "/content/preview":
    get:
      summary: Preview content URL metadata
      description: "Canonical public-safe content preview alias for /url-preview. Uses SSRF protection and does not persist content."
      tags:
        - Content
      security: []
      parameters:
        -
          name: url
          in: query
          required: true
          schema:
            type: string
            format: uri
      responses:
        200:
          description: URL preview metadata
        400:
          description: Invalid or unsafe URL
    post:
      summary: Preview content URL metadata
      description: "Canonical public-safe content preview alias for /url-preview. Uses SSRF protection and does not persist content."
      tags:
        - Content
      security: []
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - url
              properties:
                url:
                  type: string
                  format: uri
      responses:
        200:
          description: URL preview metadata
        400:
          description: Invalid or unsafe URL
  "/categories":
    get:
      summary: List categories
      tags:
        - Categories
      security: []
      responses:
        200:
          description: List of categories
    post:
      summary: Create category
      tags:
        - Categories
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/CategoryCreate"
      responses:
        201:
          description: Category created
  "/webhooks":
    get:
      summary: List webhooks
      tags:
        - Webhooks
      responses:
        200:
          description: List of webhooks
    post:
      summary: Create webhook
      tags:
        - Webhooks
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/WebhookCreate"
      responses:
        201:
          description: Webhook created
  "/keys":
    get:
      summary: List API keys
      tags:
        - Authentication
      responses:
        200:
          description: List of API keys
    post:
      summary: Create API key
      tags:
        - Authentication
      responses:
        201:
          description: API key created
    put:
      summary: Bootstrap API key
      description: Create initial admin key (only works if no keys exist)
      tags:
        - Authentication
      security: []
      responses:
        201:
          description: Bootstrap key created
        403:
          description: Keys already exist
  "/ingest":
    post:
      summary: Legacy protected document URL ingest
      description: "Legacy protected document-ingest endpoint. Public OpenContent archive ingestion is available at /content/ingest."
      tags:
        - Documents
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - url
              properties:
                url:
                  type: string
                  format: uri
                  description: URL to ingest
                category:
                  type: string
                  description: Category slug
                tags:
                  type: array
                  items:
                    type: string
                auto_publish:
                  type: boolean
                  default: false
                target_audience:
                  type: string
      responses:
        201:
          description: Content ingested successfully
        400:
          description: Invalid URL or fetch error
  "/network":
    get:
      summary: Get network status
      description: Get MCP network status and node configuration
      tags:
        - Network
      security: []
      parameters:
        -
          name: action
          in: query
          schema:
            type: string
            enum:
              - graph
              - discover
        -
          name: type
          in: query
          schema:
            type: string
          description: Service type filter
      responses:
        200:
          description: Network status
    post:
      summary: Network actions
      description: "Register with network, send heartbeat, or score documents"
      tags:
        - Network
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - action
              properties:
                action:
                  type: string
                  enum:
                    - register
                    - heartbeat
                    - "score-documents"
                    - "score-sources"
                    - "call-mcp"
                slugs:
                  type: array
                  items:
                    type: string
                identifiers:
                  type: array
                  items:
                    type: string
                endpoint:
                  type: string
                tool:
                  type: string
                params:
                  type: object
      responses:
        200:
          description: Action completed
  "/scores":
    get:
      summary: Get document/source scores
      description: Get quality scores for documents or sources
      tags:
        - Scoring
      security: []
      parameters:
        -
          name: type
          in: query
          schema:
            type: string
            enum:
              - document
              - source
              - documents
              - sources
              - top
        -
          name: slug
          in: query
          schema:
            type: string
        -
          name: source
          in: query
          schema:
            type: string
        -
          name: limit
          in: query
          schema:
            type: integer
            maximum: 100
      responses:
        200:
          description: Score data
    post:
      summary: Batch score items
      description: Score multiple documents or sources
      tags:
        - Scoring
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - type
              properties:
                type:
                  type: string
                  enum:
                    - documents
                    - sources
                    - "external-documents"
                    - "external-sources"
                slugs:
                  type: array
                  items:
                    type: string
                identifiers:
                  type: array
                  items:
                    type: string
      responses:
        200:
          description: Scores calculated
  "/api/web/status":
    get:
      summary: Get Web Status
      description: "Public-safe Web Status response for OpenContent route, discovery, metric, MCP, and attribution readiness. Counts include source labels and degraded states."
      tags:
        - Web Status
      security: []
      responses:
        200:
          description: Web Status
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/WebStatus"
  "/api/web/scorecard":
    get:
      summary: Get Web Status scorecard
      description: "Public-safe Web Status scorecard generated from readiness and compliance state."
      tags:
        - Web Status
      security: []
      responses:
        200:
          description: Web Status scorecard
  "/api/web/manifest":
    get:
      summary: Get Web manifest
      description: "Public-safe manifest of Web Status APIs, discovery routes, MCP registry names, and auth boundary."
      tags:
        - Web Status
      security: []
      responses:
        200:
          description: Web manifest
  "/api/web/fingerprints":
    get:
      summary: "Get public-safe Web fingerprints"
      description: "Public-safe SHA-256 fingerprints for Web Status, manifest, route evidence, metrics, and MCP manifest. Never includes auth tokens, raw IPs, cookies, session IDs, or raw headers."
      tags:
        - Web Status
      security: []
      responses:
        200:
          description: "Public-safe Web fingerprints"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: "Use PLATPHORM_API_KEY as Authorization: Bearer $PLATPHORM_API_KEY. X-PlatPhorm-API-Key is also accepted by protected endpoints."
    platphormApiKey:
      type: apiKey
      in: header
      name: "X-PlatPhorm-API-Key"
      description: PLATPHORM_API_KEY header form for protected platform actions.
  schemas:
    ContentCreate:
      type: object
      required:
        - title
        - content
      properties:
        title:
          type: string
        description:
          type: string
        content:
          type: string
        category:
          type: string
        tags:
          type: array
          items:
            type: string
        sourceUrl:
          type: string
          format: uri
        authorName:
          type: string
        authorEmail:
          type: string
          format: email
        targetAudience:
          type: string
    DocumentCreate:
      type: object
      required:
        - title
        - content
      properties:
        slug:
          type: string
        title:
          type: string
        description:
          type: string
        content:
          type: string
        content_format:
          type: string
          enum:
            - markdown
            - html
            - text
        source_url:
          type: string
          format: uri
        source_identifier:
          type: string
        author_name:
          type: string
        author_email:
          type: string
          format: email
        category:
          type: string
        tags:
          type: array
          items:
            type: string
        status:
          type: string
          enum:
            - draft
            - published
    DocumentUpdate:
      type: object
      properties:
        title:
          type: string
        description:
          type: string
        content:
          type: string
        status:
          type: string
          enum:
            - draft
            - published
            - archived
    DocumentList:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: array
          items:
            type: object
        meta:
          type: object
    SubmissionCreate:
      type: object
      required:
        - source_url
        - title
        - content
      properties:
        source_url:
          type: string
          format: uri
        source_identifier:
          type: string
        title:
          type: string
        content:
          type: string
        author_name:
          type: string
        author_email:
          type: string
          format: email
    SubmissionReview:
      type: object
      required:
        - action
      properties:
        action:
          type: string
          enum:
            - approve
            - reject
        slug:
          type: string
        category:
          type: string
        tags:
          type: array
          items:
            type: string
        publish_immediately:
          type: boolean
        reason:
          type: string
    CategoryCreate:
      type: object
      required:
        - slug
        - name
      properties:
        slug:
          type: string
        name:
          type: string
        description:
          type: string
        parent_id:
          type: string
          format: uuid
        icon:
          type: string
        order_index:
          type: integer
    WebhookCreate:
      type: object
      required:
        - url
      properties:
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
        active:
          type: boolean
    WebStatus:
      type: object
      required:
        - schemaVersion
        - site
        - service
        - generatedAt
        - storage
        - readiness
        - counts
        - metrics
        - webReadiness
        - webCompliance
        - webScorecard
        - warnings
        - errors
      properties:
        schemaVersion:
          type: string
          const: web.status.v1
        site:
          type: string
          format: uri
        service:
          type: string
          const: OpenContent
        generatedAt:
          type: string
          format: "date-time"
        storage:
          type: object
          properties:
            databaseRequired:
              type: boolean
              const: false
            mode:
              type: string
              enum:
                - configured_live_database
                - generated_snapshot
                - degraded_snapshot
            degraded:
              type: boolean
        readiness:
          type: object
          additionalProperties:
            type: string
            enum:
              - pass
              - partial
              - degraded
              - fail
        counts:
          type: object
          additionalProperties:
            type: number
        metrics:
          type: object
          additionalProperties:
            type: object
            required:
              - key
              - label
              - value
              - source
              - sourceUrl
              - generatedAt
              - fetchedAt
              - state
              - traceId
            properties:
              key:
                type: string
              label:
                type: string
              value:
                type: number
              source:
                type: string
              sourceUrl:
                type:
                  - string
                  - null
                format: uri
              generatedAt:
                type: string
                format: "date-time"
              fetchedAt:
                type:
                  - string
                  - null
                format: "date-time"
              state:
                type: string
                enum:
                  - live
                  - cached
                  - generated
                  - degraded
                  - sample
              traceId:
                type:
                  - string
                  - null
        warnings:
          type: array
          items:
            type: string
        errors:
          type: array
          items:
            type: string
tags:
  -
    name: Documents
    description: Document management
  -
    name: Submissions
    description: Content submissions from external sources
  -
    name: Search
    description: "Full-text search"
  -
    name: Categories
    description: Category management
  -
    name: Webhooks
    description: Webhook management
  -
    name: Authentication
    description: API key management
  -
    name: Content
    description: "Content ingestion, archive, URL preview, and processing"
  -
    name: Metadata
    description: "Post-creation SEO/AEO/social metadata, share links, and share-card regeneration"
  -
    name: Network
    description: MCP network and service discovery
  -
    name: Scoring
    description: Document and source scoring
  -
    name: Web Status
    description: "Public-safe Web Status, scorecard, manifest, and fingerprints"
