openapi: 3.0.3
info:
  title: meet.archy-ai.com API
  version: 1.0.0
  description: >-
    Public + admin API. Admin endpoints use Azure Static Web Apps (SWA) auth
    with Microsoft Entra ID and Google.


    Auth note: in production, unauthenticated requests typically get a 302
    redirect to /login (configured via staticwebapp.config.json). Swagger UI on
    this site is configured to send cookies (credentials: include).
servers:
  - url: https://05170981985b:8080
tags:
  - name: Health
  - name: Blog
  - name: Site
  - name: Auth
  - name: Admin
  - name: Publishers
  - name: Media
  - name: AI
  - name: Docs
components:
  securitySchemes:
    swaCookie:
      type: apiKey
      in: cookie
      name: StaticWebAppsAuthCookie
      description: SWA auth cookie (browser-based). Use /login to sign in.
    publisherKey:
      type: apiKey
      in: header
      name: x-archy-publisher-key
      description: Per-user publisher key for server-to-server publishing. Only
        supported on /api/integrations/* routes.
  schemas:
    ErrorResponse:
      type: object
      additionalProperties: true
      properties:
        error:
          type: string
        message:
          type: string
        hint:
          type: string
      required:
        - error
    InvalidBodyResponse:
      type: object
      additionalProperties: true
      properties:
        error:
          type: string
          enum:
            - invalid_body
        errors:
          type: array
          items:
            type: string
      required:
        - error
        - errors
    BlogTemplate:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
          nullable: true
        updatedAt:
          type: string
      required:
        - id
        - name
    BlogPostFeatureMedia:
      type: object
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
          nullable: true
        altText:
          type: string
          nullable: true
        caption:
          type: string
          nullable: true
      required:
        - id
    BlogPostSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        title:
          type: string
        excerpt:
          type: string
        author:
          type: string
        authorAvatarUrl:
          type: string
          nullable: true
        categories:
          type: array
          items:
            type: string
        tags:
          type: array
          items:
            type: string
        template:
          $ref: "#/components/schemas/BlogTemplate"
          nullable: true
        featureImage:
          $ref: "#/components/schemas/BlogPostFeatureMedia"
          nullable: true
        featureVideo:
          $ref: "#/components/schemas/BlogPostFeatureMedia"
          nullable: true
        publishedAt:
          type: string
          nullable: true
        createdAt:
          type: string
          nullable: true
        updatedAt:
          type: string
          nullable: true
      required:
        - id
        - slug
        - title
    BlogPost:
      allOf:
        - $ref: "#/components/schemas/BlogPostSummary"
        - type: object
          properties:
            body:
              description: Block-based body (stored as JSON). New format is a
                TipTap/ProseMirror doc (object with type='doc'). Legacy formats
                may be an array of blocks or an HTML string during migration.
              oneOf:
                - type: object
                  additionalProperties: true
                  description: TipTap/ProseMirror JSON doc.
                - type: array
                  description: Legacy array of blocks.
                  items:
                    type: object
                    additionalProperties: true
                - type: string
                  description: Legacy HTML string.
            seo:
              type: object
              additionalProperties: true
          required:
            - body
    AdminPageSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        status:
          type: string
        title:
          type: string
        seo:
          type: object
          additionalProperties: true
        template:
          type: object
          nullable: true
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
        publishedAt:
          type: string
          nullable: true
        createdAt:
          type: string
          nullable: true
        updatedAt:
          type: string
          nullable: true
      required:
        - id
        - slug
        - status
        - title
    AdminPage:
      allOf:
        - $ref: "#/components/schemas/AdminPageSummary"
        - type: object
          properties:
            body:
              type: object
              additionalProperties: true
          required:
            - body
    PublicPageSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        title:
          type: string
        seo:
          type: object
          additionalProperties: true
        publishedAt:
          type: string
          nullable: true
        createdAt:
          type: string
          nullable: true
        updatedAt:
          type: string
          nullable: true
      required:
        - slug
        - title
    PublicPage:
      allOf:
        - $ref: "#/components/schemas/PublicPageSummary"
        - type: object
          properties:
            body:
              type: object
              additionalProperties: true
          required:
            - body
    WorkspaceRef:
      type: object
      properties:
        id:
          type: string
        slug:
          type: string
        name:
          type: string
        ownerUserId:
          type: string
          nullable: true
        ownerExternalAuthId:
          type: string
          nullable: true
        role:
          type: string
          nullable: true
      required:
        - id
        - slug
        - name
    AdminWhoami:
      type: object
      properties:
        userId:
          type: string
        userDetails:
          type: string
        userRoles:
          type: array
          items:
            type: string
        effectiveRoles:
          type: array
          items:
            type: string
        identityProvider:
          type: string
        publisher:
          type: object
          nullable: true
          additionalProperties: true
        activeWorkspace:
          $ref: "#/components/schemas/WorkspaceRef"
        isAdmin:
          type: boolean
        isWriter:
          type: boolean
      required:
        - userRoles
        - effectiveRoles
        - activeWorkspace
        - isAdmin
        - isWriter
    AdminOnboardingSuggested:
      type: object
      properties:
        workspaceName:
          type: string
        workspaceSlug:
          type: string
        blogName:
          type: string
        defaultAuthor:
          type: string
      required:
        - workspaceName
        - workspaceSlug
        - blogName
        - defaultAuthor
    AdminOnboardingChannel:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        defaultAuthor:
          type: string
        roles:
          type: array
          items:
            type: string
        createdAt:
          type: string
          nullable: true
        updatedAt:
          type: string
          nullable: true
      required:
        - id
        - name
        - defaultAuthor
        - roles
    AdminOnboardingSite:
      type: object
      properties:
        id:
          type: string
          format: uuid
        workspaceId:
          type: string
          format: uuid
        slug:
          type: string
        name:
          type: string
        homepagePostId:
          type: string
          format: uuid
          nullable: true
        createdAt:
          type: string
          nullable: true
        updatedAt:
          type: string
          nullable: true
      required:
        - id
        - workspaceId
        - slug
        - name
    AdminOnboardingHomepage:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        title:
          type: string
        status:
          type: string
        templateId:
          type: string
          format: uuid
          nullable: true
        createdAt:
          type: string
          nullable: true
        updatedAt:
          type: string
          nullable: true
      required:
        - id
        - slug
        - title
        - status
    AdminOnboardingState:
      type: object
      properties:
        activeWorkspace:
          $ref: "#/components/schemas/WorkspaceRef"
        publisherCount:
          type: number
        postCount:
          type: number
        siteCount:
          type: number
        needsOnboarding:
          type: boolean
        suggested:
          $ref: "#/components/schemas/AdminOnboardingSuggested"
      required:
        - activeWorkspace
        - publisherCount
        - postCount
        - siteCount
        - needsOnboarding
        - suggested
    AdminOnboardingComplete:
      type: object
      properties:
        activeWorkspace:
          $ref: "#/components/schemas/WorkspaceRef"
        channel:
          $ref: "#/components/schemas/AdminOnboardingChannel"
          nullable: true
        publisherCount:
          type: number
        postCount:
          type: number
        siteCount:
          type: number
        needsOnboarding:
          type: boolean
        site:
          $ref: "#/components/schemas/AdminOnboardingSite"
          nullable: true
        homepage:
          $ref: "#/components/schemas/AdminOnboardingHomepage"
          nullable: true
      required:
        - activeWorkspace
        - publisherCount
        - postCount
        - siteCount
        - needsOnboarding
    AdminSiteHomepageCandidate:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        title:
          type: string
        status:
          type: string
        templateId:
          type: string
          format: uuid
          nullable: true
        publishedAt:
          type: string
          nullable: true
        updatedAt:
          type: string
          nullable: true
      required:
        - id
        - slug
        - title
        - status
    AdminSiteNavItem:
      type: object
      properties:
        id:
          type: string
        label:
          type: string
        kind:
          type: string
        href:
          type: string
        openInNewTab:
          type: boolean
        collectionKey:
          type: string
          nullable: true
        pageId:
          type: string
          format: uuid
          nullable: true
        postId:
          type: string
          format: uuid
          nullable: true
      required:
        - id
        - label
        - kind
        - href
    AdminSiteContentCandidate:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        title:
          type: string
        status:
          type: string
        publishedAt:
          type: string
          nullable: true
        updatedAt:
          type: string
          nullable: true
      required:
        - id
        - slug
        - title
        - status
    SiteSeoSettings:
      type: object
      properties:
        defaultTitle:
          type: string
        defaultDescription:
          type: string
        canonicalBaseUrl:
          type: string
        socialImageUrl:
          type: string
        socialHandle:
          type: string
    AdminSiteSettings:
      type: object
      properties:
        activeWorkspace:
          $ref: "#/components/schemas/WorkspaceRef"
        site:
          $ref: "#/components/schemas/AdminOnboardingSite"
          nullable: true
        homepage:
          $ref: "#/components/schemas/AdminSiteHomepageCandidate"
          nullable: true
        homepageCandidates:
          type: array
          items:
            $ref: "#/components/schemas/AdminSiteHomepageCandidate"
        pageCandidates:
          type: array
          items:
            $ref: "#/components/schemas/AdminSiteContentCandidate"
        postCandidates:
          type: array
          items:
            $ref: "#/components/schemas/AdminSiteHomepageCandidate"
      required:
        - activeWorkspace
        - homepageCandidates
        - pageCandidates
        - postCandidates
    PublicSiteConfig:
      type: object
      properties:
        site:
          type: object
          properties:
            id:
              type: string
              format: uuid
            workspaceId:
              type: string
              format: uuid
            slug:
              type: string
            name:
              type: string
            workspaceSlug:
              type: string
            workspaceName:
              type: string
            siteTitle:
              type: string
            tagline:
              type: string
            logoUrl:
              type: string
            seo:
              $ref: "#/components/schemas/SiteSeoSettings"
            headerNavigation:
              type: array
              items:
                $ref: "#/components/schemas/AdminSiteNavItem"
            footerNavigation:
              type: array
              items:
                $ref: "#/components/schemas/AdminSiteNavItem"
            updatedAt:
              type: string
              nullable: true
          required:
            - siteTitle
            - tagline
            - logoUrl
            - seo
            - headerNavigation
            - footerNavigation
      required:
        - site
    AdminDashboard:
      type: object
      properties:
        posts:
          type: object
          properties:
            total:
              type: number
            byStatus:
              type: object
              additionalProperties:
                type: number
          required:
            - total
            - byStatus
        categories:
          type: object
          properties:
            total:
              type: number
          required:
            - total
        media:
          type: object
          properties:
            total:
              type: number
            byType:
              type: object
              additionalProperties:
                type: object
                properties:
                  count:
                    type: number
                  bytes:
                    type: number
                required:
                  - count
                  - bytes
          required:
            - total
            - byType
        visits:
          type: object
          properties:
            enabled:
              type: boolean
            hint:
              type: string
          required:
            - enabled
      required:
        - posts
        - categories
        - media
    AdminPostCreate:
      type: object
      additionalProperties: false
      properties:
        slug:
          type: string
        title:
          type: string
        excerpt:
          type: string
        author:
          type: string
        authorAvatarUrl:
          type: string
          nullable: true
        categories:
          type: array
          items:
            type: string
        tags:
          type: array
          items:
            type: string
        templateId:
          type: string
          nullable: true
        isDev:
          type: boolean
        body:
          description: Block-based body (stored as JSON). New format is a
            TipTap/ProseMirror doc (object with type='doc'). Legacy formats may
            be an array of blocks or an HTML string during migration.
          oneOf:
            - type: object
              additionalProperties: true
            - type: array
              items:
                type: object
                additionalProperties: true
            - type: string
        seo:
          type: object
          additionalProperties: true
    AdminPostUpdate:
      type: object
      additionalProperties: true
      properties:
        slug:
          type: string
        title:
          type: string
        excerpt:
          type: string
        author:
          type: string
        authorAvatarUrl:
          type: string
          nullable: true
        categories:
          type: array
          items:
            type: string
        tags:
          type: array
          items:
            type: string
        templateId:
          type: string
          nullable: true
        isDev:
          type: boolean
        body:
          description: Block-based body (stored as JSON). New format is a
            TipTap/ProseMirror doc (object with type='doc'). Legacy formats may
            be an array of blocks or an HTML string during migration.
          oneOf:
            - type: object
              additionalProperties: true
            - type: array
              items:
                type: object
                additionalProperties: true
            - type: string
        seo:
          type: object
          additionalProperties: true
    AdminAIDraftRequest:
      type: object
      additionalProperties: false
      properties:
        postId:
          type: string
          format: uuid
        topic:
          type: string
        audience:
          type: string
        tone:
          type: string
        enableAutoImages:
          type: boolean
        enableAutoVideo:
          type: boolean
        title:
          type: string
        excerpt:
          type: string
        author:
          type: string
        categories:
          type: array
          items:
            type: string
        tags:
          type: array
          items:
            type: string
      required:
        - postId
        - topic
    AdminAIRewriteRequest:
      type: object
      additionalProperties: false
      properties:
        mode:
          type: string
          enum:
            - rewriteText
            - improveParagraph
            - rewriteHeading
            - writeParagraphForHeading
        text:
          type: string
        length:
          type: string
          enum:
            - keep
            - shorter
            - longer
          nullable: true
        tone:
          type: string
          enum:
            - neutral
            - professional
            - friendly
            - conversational
            - confident
            - persuasive
            - concise
          nullable: true
        prompt:
          type: string
          nullable: true
      required:
        - mode
        - text
    AdminAIVideoJobAssetRef:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
          nullable: true
      required:
        - id
    AdminAIVideoJob:
      type: object
      additionalProperties: false
      properties:
        requestId:
          type: string
          format: uuid
        postId:
          type: string
          format: uuid
        postSlug:
          type: string
          nullable: true
        postTitle:
          type: string
          nullable: true
        purpose:
          type: string
        providerStatus:
          type: string
        status:
          type: string
        createdAt:
          type: string
        updatedAt:
          type: string
        completedAt:
          type: string
          nullable: true
        failedAt:
          type: string
          nullable: true
        error:
          nullable: true
        asset:
          $ref: "#/components/schemas/AdminAIVideoJobAssetRef"
          nullable: true
      required:
        - requestId
        - postId
        - purpose
        - providerStatus
        - status
        - createdAt
        - updatedAt
    AdminAsset:
      type: object
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          enum:
            - image
            - video
        mimeType:
          type: string
        sizeBytes:
          type: number
        width:
          type: number
          nullable: true
        height:
          type: number
          nullable: true
        durationSeconds:
          type: number
          nullable: true
        blobPath:
          type: string
        url:
          type: string
          nullable: true
        altText:
          type: string
          nullable: true
        caption:
          type: string
          nullable: true
        source:
          type: string
        createdAt:
          type: string
        createdBy:
          type: string
          nullable: true
      required:
        - id
        - type
        - mimeType
        - sizeBytes
        - blobPath
    UploadUrlResponse:
      type: object
      properties:
        assetId:
          type: string
          format: uuid
        type:
          type: string
          enum:
            - image
            - video
        contentType:
          type: string
        sizeBytes:
          type: number
        container:
          type: string
        blobPath:
          type: string
        blobUrl:
          type: string
        uploadUrl:
          type: string
        expiresAt:
          type: string
      required:
        - assetId
        - blobPath
        - uploadUrl
    UploadUrlRequest:
      type: object
      additionalProperties: false
      properties:
        postId:
          type: string
          format: uuid
          nullable: true
        purpose:
          type: string
          enum:
            - inline
            - feature-image
            - feature-video
            - library
        filename:
          type: string
        contentType:
          type: string
        sizeBytes:
          type: number
        type:
          type: string
          enum:
            - image
            - video
          nullable: true
      required:
        - filename
        - contentType
        - sizeBytes
    CompleteAssetRequest:
      type: object
      additionalProperties: true
      properties:
        assetId:
          type: string
          format: uuid
        blobPath:
          type: string
        blobUrl:
          type: string
          nullable: true
        contentType:
          type: string
        sizeBytes:
          type: number
        type:
          type: string
          enum:
            - image
            - video
          nullable: true
        altText:
          type: string
          nullable: true
        caption:
          type: string
          nullable: true
        source:
          type: string
          enum:
            - uploaded
            - ai-generated
          nullable: true
      required:
        - assetId
        - blobPath
        - contentType
        - sizeBytes
    FeatureMediaAttachRequest:
      type: object
      additionalProperties: false
      properties:
        assetId:
          type: string
          format: uuid
      required:
        - assetId
    PublisherKeyItem:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
          format: uuid
        label:
          type: string
        last4:
          type: string
        expiresAt:
          type: string
          nullable: true
        revokedAt:
          type: string
          nullable: true
        createdAt:
          type: string
          nullable: true
      required:
        - id
        - label
        - last4
    PublisherChannel:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
          format: uuid
        ownerUserId:
          type: string
        name:
          type: string
        avatarAssetId:
          type: string
          format: uuid
          nullable: true
        avatarUrl:
          type: string
          nullable: true
        defaultAuthor:
          type: string
        roles:
          type: array
          items:
            type: string
        createdAt:
          type: string
        updatedAt:
          type: string
        keys:
          type: array
          items:
            $ref: "#/components/schemas/PublisherKeyItem"
      required:
        - id
        - name
        - roles
    PublisherChannelCreateRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
        avatarAssetId:
          type: string
          format: uuid
          nullable: true
        avatarUrl:
          type: string
          nullable: true
        defaultAuthor:
          type: string
        roles:
          type: array
          items:
            type: string
        keyLabel:
          type: string
        expiresAt:
          type: string
          nullable: true
      required:
        - name
    PublisherChannelCreateResponse:
      type: object
      additionalProperties: true
      properties:
        channel:
          $ref: "#/components/schemas/PublisherChannel"
        key:
          type: object
          nullable: true
          additionalProperties: false
          properties:
            id:
              type: string
              format: uuid
            label:
              type: string
            last4:
              type: string
            expiresAt:
              type: string
              nullable: true
            createdAt:
              type: string
              nullable: true
            token:
              type: string
          required:
            - id
            - token
            - last4
      required:
        - channel
    PublisherKeyCreateRequest:
      type: object
      additionalProperties: false
      properties:
        label:
          type: string
        expiresAt:
          type: string
          nullable: true
    PublisherKeyCreateResponse:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
          format: uuid
        label:
          type: string
        last4:
          type: string
        expiresAt:
          type: string
          nullable: true
        createdAt:
          type: string
          nullable: true
        token:
          type: string
      required:
        - id
        - token
        - last4
paths:
  /api/_admin/publishers:
    get:
      tags:
        - Publishers
      summary: List publisher channels (current user)
      security:
        - swaCookie: []
      responses:
        "200":
          description: Publisher channels
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/PublisherChannel"
                required:
                  - items
        "401":
          description: Unauthenticated
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Database error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      tags:
        - Publishers
      summary: Create a publisher channel and issue an initial key
      security:
        - swaCookie: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PublisherChannelCreateRequest"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublisherChannelCreateResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthenticated
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Database error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "501":
          description: Missing server configuration
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/_admin/publishers/{id}:
    get:
      tags:
        - Publishers
      summary: Get a publisher channel by id
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Channel
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublisherChannel"
        "401":
          description: Unauthenticated
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Database error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    put:
      tags:
        - Publishers
      summary: Update a publisher channel
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PublisherChannelCreateRequest"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublisherChannel"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthenticated
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Database error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      tags:
        - Publishers
      summary: Delete a publisher channel
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Deleted
          content:
            application/json:
              schema:
                type: object
        "401":
          description: Unauthenticated
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Database error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/_admin/publishers/{id}/keys:
    get:
      tags:
        - Publishers
      summary: List keys for a channel
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Keys
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/PublisherKeyItem"
                required:
                  - items
        "401":
          description: Unauthenticated
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      tags:
        - Publishers
      summary: Create a new key for a channel (token returned once)
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PublisherKeyCreateRequest"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublisherKeyCreateResponse"
        "401":
          description: Unauthenticated
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "501":
          description: Missing server configuration
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/_admin/publishers/{id}/keys/{keyId}:
    delete:
      tags:
        - Publishers
      summary: Revoke a key
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: keyId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Revoked
          content:
            application/json:
              schema:
                type: object
        "401":
          description: Unauthenticated
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/openapi:
    get:
      tags:
        - Docs
      summary: Get OpenAPI 3.0 specification (JSON)
      responses:
        "200":
          description: OpenAPI JSON
          content:
            application/json:
              schema:
                type: object
  /api/openapi.yaml:
    get:
      tags:
        - Docs
      summary: Get OpenAPI 3.0 specification (YAML)
      responses:
        "200":
          description: OpenAPI YAML
          content:
            text/yaml:
              schema:
                type: string
  /api/auth/logout:
    get:
      tags:
        - Auth
      summary: App-only logout (clear SWA session cookie)
      description: Clears the Static Web Apps auth cookie for this app and redirects.
        Does not call the identity provider logout endpoint.
      parameters:
        - name: next
          in: query
          required: false
          description: "Relative path to redirect to after logout (default: /)."
          schema:
            type: string
            default: /
      responses:
        "302":
          description: Redirect to the `next` location after clearing cookies.
  /api/health:
    get:
      tags:
        - Health
      summary: Health check
      responses:
        "200":
          description: OK
  /api/blog/posts:
    get:
      tags:
        - Blog
      summary: List published blog posts
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 10
        - name: cursor
          in: query
          description: ISO timestamp from the last item (publishedAt).
          schema:
            type: string
      responses:
        "200":
          description: A page of posts
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/BlogPostSummary"
                  nextCursor:
                    type: string
                    nullable: true
                required:
                  - items
        "500":
          description: Database error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/blog/posts/{slug}:
    get:
      tags:
        - Blog
      summary: Get a published post by slug
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Post
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BlogPost"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/site/homepage:
    get:
      tags:
        - Site
      summary: Get the published homepage for the default workspace site
      parameters:
        - name: workspaceSlug
          in: query
          required: false
          schema:
            type: string
        - name: siteSlug
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: Published homepage
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/BlogPost"
                  - type: object
                    properties:
                      site:
                        type: object
                        properties:
                          id:
                            type: string
                            format: uuid
                          slug:
                            type: string
                          name:
                            type: string
                          workspaceId:
                            type: string
                            format: uuid
                          workspaceSlug:
                            type: string
                          workspaceName:
                            type: string
                        required:
                          - id
                          - slug
                          - name
                          - workspaceId
                          - workspaceSlug
                          - workspaceName
        "404":
          description: Published homepage not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Database error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/pages:
    get:
      tags:
        - Site
      summary: List published workspace pages
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 2000
            default: 50
      responses:
        "200":
          description: Published pages
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/PublicPageSummary"
                required:
                  - items
        "500":
          description: Database error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/pages/{slug}:
    get:
      tags:
        - Site
      summary: Get a published page by slug
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Published page
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicPage"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Database error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/blog/recent:
    get:
      tags:
        - Blog
      summary: Recent published posts
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 12
            default: 3
      responses:
        "200":
          description: Recent posts
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/BlogPostSummary"
                required:
                  - items
  /api/blog/related:
    get:
      tags:
        - Blog
      summary: Related posts by slug
      parameters:
        - name: slug
          in: query
          required: true
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 12
            default: 3
      responses:
        "200":
          description: Related posts
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/BlogPostSummary"
                required:
                  - items
  /api/_admin/whoami:
    get:
      tags:
        - Admin
      summary: Debug current user identity + roles
      security:
        - swaCookie: []
      responses:
        "200":
          description: User identity
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminWhoami"
        "401":
          description: Unauthenticated (or redirected to /login)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Admin role required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/_admin/onboarding:
    get:
      tags:
        - Admin
      summary: Get onboarding state for the active workspace
      security:
        - swaCookie: []
      responses:
        "200":
          description: Current onboarding state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminOnboardingState"
        "401":
          description: Unauthenticated (or redirected to /login)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      tags:
        - Admin
      summary: Create or update the default site and homepage for the active workspace
      security:
        - swaCookie: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdminOnboardingSuggested"
      responses:
        "200":
          description: Workspace onboarding completed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminOnboardingComplete"
        "400":
          description: Invalid onboarding payload
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Owner role required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Workspace slug already exists
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/_admin/site:
    get:
      tags:
        - Admin
      summary: Get workspace site settings and homepage candidates
      security:
        - swaCookie: []
      responses:
        "200":
          description: Current workspace site mapping
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminSiteSettings"
        "401":
          description: Unauthenticated (or redirected to /login)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    put:
      tags:
        - Admin
      summary: Update workspace site settings, navigation, and homepage
      security:
        - swaCookie: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                homepagePostId:
                  type: string
                  format: uuid
                siteTitle:
                  type: string
                tagline:
                  type: string
                logoUrl:
                  type: string
                seo:
                  $ref: "#/components/schemas/SiteSeoSettings"
                headerNavigation:
                  type: array
                  items:
                    $ref: "#/components/schemas/AdminSiteNavItem"
                footerNavigation:
                  type: array
                  items:
                    $ref: "#/components/schemas/AdminSiteNavItem"
      responses:
        "200":
          description: Updated workspace site mapping
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminSiteSettings"
        "400":
          description: Invalid site settings payload
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Admin role required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Homepage candidate not found in workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/site/config:
    get:
      tags:
        - Site
      summary: Get public site identity and navigation config
      responses:
        "200":
          description: Public site config
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicSiteConfig"
        "404":
          description: No default site found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/_admin/dashboard:
    get:
      tags:
        - Admin
      summary: Admin dashboard stats
      security:
        - swaCookie: []
      responses:
        "200":
          description: Stats
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminDashboard"
  /api/_admin/pages:
    get:
      tags:
        - Admin
      summary: List workspace pages
      security:
        - swaCookie: []
      parameters:
        - name: status
          in: query
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
      responses:
        "200":
          description: Pages
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/AdminPageSummary"
                required:
                  - items
        "401":
          description: Unauthenticated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      tags:
        - Admin
      summary: Create a draft page
      security:
        - swaCookie: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                slug:
                  type: string
                body:
                  type: object
                  additionalProperties: true
                seo:
                  type: object
                  additionalProperties: true
      responses:
        "201":
          description: Draft page created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminPage"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Slug conflict
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/_admin/pages/{id}:
    get:
      tags:
        - Admin
      summary: Get a page by id
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Page
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminPage"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    put:
      tags:
        - Admin
      summary: Update a page
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                slug:
                  type: string
                body:
                  type: object
                  additionalProperties: true
                seo:
                  type: object
                  additionalProperties: true
      responses:
        "200":
          description: Updated page
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminPage"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Slug conflict
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      tags:
        - Admin
      summary: Archive a page
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Archived
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminPageSummary"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/_admin/pages/{id}/publish:
    post:
      tags:
        - Admin
      summary: Publish a page
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Published
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminPageSummary"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/_admin/pages/{id}/unpublish:
    post:
      tags:
        - Admin
      summary: Return a page to draft
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Draft
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminPageSummary"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/_admin/blog/templates:
    get:
      tags:
        - Admin
      summary: List blog templates
      security:
        - swaCookie: []
      responses:
        "200":
          description: Templates
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/BlogTemplate"
                required:
                  - items
  /api/_admin/blog/posts:
    get:
      tags:
        - Admin
      summary: List posts (draft/published/archived)
      security:
        - swaCookie: []
      parameters:
        - name: status
          in: query
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
      responses:
        "200":
          description: Posts
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/BlogPostSummary"
                required:
                  - items
    post:
      tags:
        - Admin
      summary: Create draft post
      security:
        - swaCookie: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdminPostCreate"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BlogPostSummary"
        "409":
          description: Slug exists
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/_admin/blog/ai/draft:
    post:
      tags:
        - AI
      summary: Generate draft body (admin only)
      security:
        - swaCookie: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdminAIDraftRequest"
      responses:
        "200":
          description: Updated post
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BlogPost"
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthenticated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Not found or disabled
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/_admin/blog/ai/rewrite:
    post:
      tags:
        - AI
      summary: Rewrite text (admin only)
      security:
        - swaCookie: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdminAIRewriteRequest"
      responses:
        "200":
          description: Rewritten text
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthenticated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Not found or disabled
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/_admin/blog/ai/video/jobs:
    get:
      tags:
        - AI
      summary: List AI video generation jobs (admin/writer)
      security:
        - swaCookie: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 30
        - name: scope
          in: query
          schema:
            type: string
            enum:
              - mine
              - all
            default: mine
        - name: allowAll
          in: query
          description: Only effective for admin users when scope=all.
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Jobs
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/AdminAIVideoJob"
                required:
                  - items
        "401":
          description: Unauthenticated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Not found or disabled
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/_admin/blog/posts/{id}:
    get:
      tags:
        - Admin
      summary: Get post by id (includes body)
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Post
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BlogPost"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    put:
      tags:
        - Admin
      summary: Update post
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdminPostUpdate"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BlogPost"
        "400":
          description: Invalid body (structured JSON failed validation)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InvalidBodyResponse"
        "409":
          description: Slug exists
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      tags:
        - Admin
      summary: Archive post (admin only)
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Archived
          content:
            application/json:
              schema:
                type: object
  /api/_admin/blog/posts/{id}/publish:
    post:
      tags:
        - Admin
      summary: Publish post (admin only)
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Published
  /api/_admin/blog/posts/{id}/unpublish:
    post:
      tags:
        - Admin
      summary: Unpublish post (admin only)
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Unpublished
  /api/_admin/blog/posts/{id}/feature-image:
    post:
      tags:
        - Media
      summary: Attach feature image asset to post
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FeatureMediaAttachRequest"
      responses:
        "200":
          description: Updated
  /api/_admin/blog/posts/{id}/feature-video:
    post:
      tags:
        - Media
      summary: Attach feature video asset to post
      security:
        - swaCookie: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FeatureMediaAttachRequest"
      responses:
        "200":
          description: Updated
  /api/_admin/blog/assets:
    get:
      tags:
        - Media
      summary: List assets (media library)
      security:
        - swaCookie: []
      parameters:
        - name: type
          in: query
          schema:
            type: string
            enum:
              - image
              - video
        - name: q
          in: query
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: cursor
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Assets
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/AdminAsset"
                  nextCursor:
                    type: string
                    nullable: true
                required:
                  - items
  /api/_admin/blog/assets/upload-url:
    post:
      tags:
        - Media
      summary: Generate a signed Azure Blob upload URL (SAS)
      description: Returns a time-limited `uploadUrl` you can PUT bytes to. After
        upload, call `/api/_admin/blog/assets/complete` to register the asset in
        DB.
      security:
        - swaCookie: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UploadUrlRequest"
      responses:
        "200":
          description: Upload URL
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UploadUrlResponse"
        "413":
          description: Upload too large
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/_admin/blog/assets/complete:
    post:
      tags:
        - Media
      summary: Register an uploaded blob as an Asset
      security:
        - swaCookie: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CompleteAssetRequest"
      responses:
        "200":
          description: Upserted asset
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminAsset"
  /api/_admin/blog/assets/upload:
    post:
      tags:
        - Media
      summary: Upload an asset via the API (server-side upload)
      description: This endpoint expects raw bytes in the request body and uses
        headers for metadata (filename, size). Prefer the signed upload-url flow
        for large files.
      security:
        - swaCookie: []
      parameters:
        - name: postId
          in: query
          required: true
          schema:
            type: string
            format: uuid
        - name: purpose
          in: query
          schema:
            type: string
            enum:
              - inline
              - feature-image
              - feature-video
              - library
        - name: type
          in: query
          schema:
            type: string
            enum:
              - image
              - video
        - name: aiGenerated
          in: query
          schema:
            type: boolean
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "200":
          description: Uploaded asset
          content:
            application/json:
              schema:
                type: object
                properties:
                  asset:
                    $ref: "#/components/schemas/AdminAsset"
                required:
                  - asset
