openapi: 3.1.0
info:
  title: Auto Content Engine API
  description: |
    The GEN Auto Content Engine API takes a raw idea all the way to a published video — agent setup, idea research, vidsheet production, layer composition, render, and publish.

    ## The 5-step journey

    Every operation belongs to one phase of a 5-step user journey. Each operation carries an `x-phase` extension you can filter on:

    | Phase | Meaning |
    |-------|---------|
    | `setup` | Step 1 — Set Up Your Agent (identity, voice, personality, API keys, workspaces) |
    | `ideas` | Step 2 — Generate Content Ideas (trending data, research, refinement, preferences) |
    | `convert` | Step 3 — Convert Idea to Vidsheet (clone template or build custom engine) |
    | `edit` | Step 4 — Edit & Generate (cells, layers, creation cards, trigger + poll) |
    | `export` | Step 5 — Export & Publish (render, download, publish to social) |

    Full journey-oriented docs: https://api.gen.pro

    ## Authentication

    All endpoints require authentication via one of:

    - **API Key** (recommended): Pass your API key in the `X-API-Key` header. Create keys on the agent's API page in the GEN dashboard.
    - **JWT**: Pass a Bearer JWT in the `Authorization: Bearer <token>` header.

    ## Base URLs

    ```
    https://api.gen.pro/v1       — agent setup, vidsheets, generations, renders, publishing
    https://agent.gen.pro/v1     — content ideas, research, conversations, chat
    ```

    ## Credits

    All `/v1/autocontentengine/` endpoints require an active GEN credit balance.
    If your organization has no usable credits, requests return `422` with error code `usable_gen_credit_required`.

  version: 1.0.0
  contact:
    name: GEN Support
    url: https://gen.pro
  license:
    name: Proprietary

servers:
  - url: https://api.gen.pro/v1
    description: Auto Content Engine API
  - url: https://agent.gen.pro/v1
    description: Agent Chat API

security:
  - apiKey: []
  - bearerAuth: []

tags:
  - name: Discovery
    description: Identify the authenticated user, their workspaces, and agents.
  - name: API Keys
    description: Create and manage Personal Access Tokens (PATs) for API authentication.
  - name: Sheets
    description: Create and retrieve Auto Content Engine sheets (vidsheets).
  - name: Rows
    description: Manage rows within a sheet.
  - name: Columns
    description: Manage columns within a sheet.
  - name: Cells
    description: Read and update individual cells.
  - name: Layers
    description: Manage video layers within cells.
  - name: Generations
    description: Trigger AI content generation on cells and layers.
  - name: Variables
    description: Global variables for template substitution.
  - name: Automation
    description: Agent-level webhook and callback configuration.
  - name: Content Monitoring
    description: Monitor and discover social media content across multiple platforms.
  - name: Publishing
    description: Post and schedule content to social media platforms.
  - name: Templates
    description: Browse and clone pre-configured engine templates.
  - name: Agents
    description: Create, read, update, and delete agents and their avatars.
  - name: Organizations
    description: Manage organizations (workspaces).
  - name: Content Resources
    description: Upload and manage content resources (images, videos, audio).
  - name: Asset Libraries
    description: Browse asset libraries and manage asset folders.
  - name: Direct Upload
    description: Get pre-signed S3 URLs for large file uploads.
  - name: Agent Profile
    description: (Legacy) Manage the agent's identity, voice, and brand profile. Prefer Agent Core for new work.
  - name: Agent Core
    description: |
      **Preferred** single-endpoint read/write for the agent setup canvas. One flat PATCH updates every brand field, personality, and default voice in a single call. Field names mirror what the FE Setup canvas saves, so developers and the FE speak the same language.
  - name: Agent Voice
    description: |
      Voice library, ElevenLabs integration, prompt-based design (4-step flow), audio-sample cloning, async TTS preview.
  - name: Agent Chat
    description: |
      Conversational AI agent interface. All endpoints use base URL `https://agent.gen.pro/v1`.

paths:
  # ── Discovery ──────────────────────────────────────────────
  /me:
    get:
      operationId: getMe
      x-phase: setup
      summary: Get current user
      description: Returns the authenticated user's profile. Use this to verify your API key works.
      tags: [Discovery]
      responses:
        '200':
          description: Current user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
              example:
                id: "abc123"
                email: "user@example.com"
                name: "Jane Doe"
                username: "janedoe"
                created_at: "2025-01-15T10:30:00Z"
        '401':
          $ref: '#/components/responses/Unauthorized'

  /workspaces:
    get:
      operationId: listWorkspaces
      x-phase: setup
      summary: List workspaces
      description: Returns organizations where the authenticated user is an owner or manager.
      tags: [Discovery]
      responses:
        '200':
          description: List of workspaces
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Workspace'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /agents:
    get:
      operationId: listAgents
      x-phase: setup
      summary: List agents
      description: Returns all agents the authenticated user has access to across their workspaces.
      tags: [Discovery]
      parameters:
        - name: workspace_id
          in: query
          description: Filter by workspace (organization) ID
          schema:
            type: string
      responses:
        '200':
          description: List of agents
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Agent'
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      operationId: createAgent
      x-phase: setup
      summary: Create an agent
      description: Creates a new agent in the specified organization.
      tags: [Agents]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                organization_id:
                  type: string
                  description: Organization to create the agent in.
                agent:
                  type: object
                  required: [name]
                  properties:
                    name:
                      type: string
                      example: "My Brand Agent"
                    description:
                      type: string
                    time_zone:
                      type: string
                      example: "America/New_York"
                    eleven_lab_api_key:
                      type: string
                    hume_ai_api_key:
                      type: string
                    agent_avatars_attributes:
                      type: array
                      items:
                        type: object
      responses:
        '201':
          description: Agent created
          content:
            application/json:
              schema:
                type: object
                properties:
                  agent:
                    $ref: '#/components/schemas/AgentDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          description: Agent creation failed or organization not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /agents/{agent_id}:
    get:
      operationId: getAgent
      x-phase: setup
      summary: Get an agent
      description: Returns detailed information about a specific agent.
      tags: [Agents]
      parameters:
        - $ref: '#/components/parameters/AgentIdPath'
        - name: with_organization_uuid
          in: query
          schema:
            type: boolean
          description: Include organization UUID in response.
      responses:
        '200':
          description: Agent details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentDetail'
        '403':
          description: Permission denied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    patch:
      operationId: updateAgent
      x-phase: setup
      summary: Update an agent
      description: Updates agent properties including name, description, timezone, API keys, avatars, and default voice.
      tags: [Agents]
      parameters:
        - $ref: '#/components/parameters/AgentIdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                agent:
                  type: object
                  properties:
                    name:
                      type: string
                    description:
                      type: string
                    time_zone:
                      type: string
                    eleven_lab_api_key:
                      type: string
                    hume_ai_api_key:
                      type: string
                    agent_avatars_attributes:
                      type: array
                      items:
                        type: object
                    default_user_voice_attributes:
                      type: object
                      properties:
                        source:
                          type: string
                        voice_id:
                          type: string
      responses:
        '200':
          description: Updated agent
          content:
            application/json:
              schema:
                type: object
                properties:
                  agent:
                    $ref: '#/components/schemas/AgentDetail'
        '403':
          description: Permission denied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Agent not found or validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    delete:
      operationId: deleteAgent
      x-phase: setup
      summary: Delete an agent
      description: Permanently deletes an agent and all associated data.
      tags: [Agents]
      parameters:
        - $ref: '#/components/parameters/AgentIdPath'
      responses:
        '200':
          description: Agent deleted
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /agents/{agent_id}/avatars:
    get:
      operationId: listAgentAvatars
      x-phase: setup
      summary: List agent avatars
      description: Returns all avatars for the agent. Cursor-based pagination (20 per page). Primary avatar listed first.
      tags: [Agents]
      parameters:
        - $ref: '#/components/parameters/AgentIdPath'
        - name: cursor
          in: query
          schema:
            type: string
          description: Pagination cursor.
      responses:
        '200':
          description: List of avatars
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Avatar'

    post:
      operationId: createAgentAvatar
      x-phase: setup
      summary: Upload agent avatar
      description: Upload one or more avatar images (JPEG, PNG, WebP) or link a DeGod avatar.
      tags: [Agents]
      parameters:
        - $ref: '#/components/parameters/AgentIdPath'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                "agent_avatars_attributes[]":
                  type: string
                  format: binary
                  description: Avatar image file (JPEG, PNG, WebP) or degod_avatar_id.
      responses:
        '200':
          description: Avatar(s) created
        '422':
          description: Avatar creation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /agents/{agent_id}/avatars/{id}:
    patch:
      operationId: setPrimaryAvatar
      x-phase: setup
      summary: Set avatar as primary
      description: Sets this avatar as the primary avatar. All other avatars are marked non-primary.
      tags: [Agents]
      parameters:
        - $ref: '#/components/parameters/AgentIdPath'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Avatar updated
        '422':
          description: Avatar update failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    delete:
      operationId: deleteAgentAvatar
      x-phase: setup
      summary: Delete avatar(s)
      description: Delete one or more avatars. Pass multiple IDs separated by underscores (e.g. `7_8_9`).
      tags: [Agents]
      parameters:
        - $ref: '#/components/parameters/AgentIdPath'
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Avatar ID or multiple IDs separated by underscores.
      responses:
        '200':
          description: Avatar(s) deleted

  # ── API Keys ───────────────────────────────────────────────
  /persisted_tokens:
    get:
      operationId: listApiKeys
      x-phase: setup
      summary: List API keys
      description: Returns all API keys for the authenticated user, including revoked and expired ones.
      tags: [API Keys]
      responses:
        '200':
          description: List of API keys
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ApiKey'
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      operationId: createApiKey
      x-phase: setup
      summary: Create API key
      description: |
        Creates a new API key. The plain-text key is returned **only once** in the response.
        Store it securely — it cannot be retrieved again.
      tags: [API Keys]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: Human-readable label. Auto-generated if omitted.
                  example: "My n8n workflow"
                expires_in:
                  type: integer
                  description: Seconds until expiration. Omit for non-expiring keys.
                  example: 2592000
      responses:
        '201':
          description: API key created. The `token` field contains the plain-text key.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiKey'
                  - type: object
                    properties:
                      token:
                        type: string
                        description: The plain-text API key. Store it now — it won't be shown again.
                        example: "gen_abc123def456..."
        '401':
          $ref: '#/components/responses/Unauthorized'

  /persisted_tokens/{id}:
    patch:
      operationId: updateApiKey
      x-phase: setup
      summary: Rename API key
      tags: [API Keys]
      parameters:
        - $ref: '#/components/parameters/TokenId'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [persisted_token]
              properties:
                persisted_token:
                  type: object
                  properties:
                    name:
                      type: string
                      example: "Renamed key"
      responses:
        '200':
          description: Updated API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKey'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /persisted_tokens/{id}/revoke:
    delete:
      operationId: revokeApiKey
      x-phase: setup
      summary: Revoke API key
      description: Permanently revokes a single API key. This cannot be undone.
      tags: [API Keys]
      parameters:
        - $ref: '#/components/parameters/TokenId'
      responses:
        '200':
          description: Key revoked
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /persisted_tokens/revoke_all:
    delete:
      operationId: revokeAllApiKeys
      x-phase: setup
      summary: Revoke all API keys
      description: Revokes every active API key for the authenticated user.
      tags: [API Keys]
      responses:
        '200':
          description: All keys revoked
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: "Successfully revoked 3 tokens"
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ── Sheets ─────────────────────────────────────────────────
  /autocontentengine:
    post:
      operationId: createSheet
      x-phase: convert
      summary: Create a sheet
      description: Creates a new Auto Content Engine sheet for the specified agent.
      tags: [Sheets]
      parameters:
        - $ref: '#/components/parameters/AgentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [spreadsheet]
              properties:
                spreadsheet:
                  type: object
                  properties:
                    title:
                      type: string
                      example: "Q1 TikTok Campaign"
                    is_default:
                      type: boolean
                      default: false
      responses:
        '201':
          description: Sheet created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Sheet'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/CreditRequired'

  /autocontentengine/{sheet_id}:
    get:
      operationId: getSheet
      x-phase: convert
      summary: Get a sheet
      description: |
        Returns a sheet with all columns, rows, cells, layers, and global variables.
        Pass `with_execution_cost=true` to include credit cost per cell/row.
      tags: [Sheets]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
        - name: with_execution_cost
          in: query
          schema:
            type: boolean
          description: Include execution cost for each cell and row.
      responses:
        '200':
          description: Sheet with full data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Sheet'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/CreditRequired'

  /autocontentengine/{sheet_id}/clone:
    post:
      operationId: cloneSheet
      x-phase: convert
      summary: Clone a sheet
      description: Creates a deep copy of the sheet, optionally into a different agent.
      tags: [Sheets]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                target_agent_id:
                  type: string
                  description: Agent to clone into. Defaults to the same agent.
      responses:
        '201':
          description: Cloned sheet
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Sheet'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/CreditRequired'

  # ── Rows ───────────────────────────────────────────────────
  /autocontentengine/{sheet_id}/rows:
    get:
      operationId: listRows
      x-phase: edit
      summary: List rows
      tags: [Rows]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
      responses:
        '200':
          description: List of rows with cells
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Row'
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      operationId: createRow
      x-phase: edit
      summary: Create a row
      tags: [Rows]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                spreadsheet_row:
                  type: object
                  properties:
                    position:
                      type: integer
                      description: Position index for ordering.
      responses:
        '201':
          description: Row created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Row'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /autocontentengine/{sheet_id}/rows/{row_id}:
    get:
      operationId: getRow
      x-phase: edit
      summary: Get a row
      tags: [Rows]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
        - $ref: '#/components/parameters/RowId'
      responses:
        '200':
          description: Row with cells
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Row'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /autocontentengine/{sheet_id}/rows/{row_id}/duplicate:
    post:
      operationId: duplicateRow
      x-phase: edit
      summary: Duplicate a row
      description: Creates a deep copy of the row including all cells and layers.
      tags: [Rows]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
        - $ref: '#/components/parameters/RowId'
      responses:
        '201':
          description: Duplicated row
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Row'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /autocontentengine/{sheet_id}/rows/update_positions:
    put:
      operationId: updateRowPositions
      x-phase: edit
      summary: Reorder rows
      tags: [Rows]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [rows]
              properties:
                rows:
                  type: array
                  items:
                    type: object
                    required: [id, position]
                    properties:
                      id:
                        type: string
                      position:
                        type: integer
      responses:
        '200':
          description: Updated rows
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Row'

  /autocontentengine/{sheet_id}/rows/mass_update:
    put:
      operationId: massUpdateRows
      x-phase: edit
      summary: Bulk update rows
      tags: [Rows]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Bulk row update payload
      responses:
        '200':
          description: Updated rows
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Row'

  # ── Columns ────────────────────────────────────────────────
  /autocontentengine/{sheet_id}/columns:
    get:
      operationId: listColumns
      x-phase: edit
      summary: List columns
      tags: [Columns]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
      responses:
        '200':
          description: List of columns
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Column'

    post:
      operationId: createColumn
      x-phase: edit
      summary: Create a column
      tags: [Columns]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                spreadsheet_column:
                  type: object
                  properties:
                    title:
                      type: string
                      example: "Video Script"
                    type:
                      type: string
                      example: "text"
                    position:
                      type: integer
      responses:
        '201':
          description: Column created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Column'

  /autocontentengine/{sheet_id}/columns/{column_id}/duplicate:
    post:
      operationId: duplicateColumn
      x-phase: edit
      summary: Duplicate a column
      tags: [Columns]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
        - name: column_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '201':
          description: Duplicated column
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Column'

  /autocontentengine/{sheet_id}/columns/update_positions:
    patch:
      operationId: updateColumnPositions
      x-phase: edit
      summary: Reorder columns
      tags: [Columns]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [columns]
              properties:
                columns:
                  type: array
                  items:
                    type: object
                    required: [id, position]
                    properties:
                      id:
                        type: string
                      position:
                        type: integer
      responses:
        '200':
          description: Updated columns

  /autocontentengine/{sheet_id}/columns/mass_update:
    put:
      operationId: massUpdateColumns
      x-phase: edit
      summary: Bulk update columns
      tags: [Columns]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Updated columns

  # ── Cells ──────────────────────────────────────────────────
  /autocontentengine/{sheet_id}/cells/{cell_id}:
    get:
      operationId: getCell
      x-phase: edit
      summary: Get a cell
      tags: [Cells]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
        - $ref: '#/components/parameters/CellId'
      responses:
        '200':
          description: Cell with layers and job history
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Cell'

    patch:
      operationId: updateCell
      x-phase: edit
      summary: Update a cell
      description: Update the value or attributes of a cell.
      tags: [Cells]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
        - $ref: '#/components/parameters/CellId'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                spreadsheet_cell:
                  type: object
                  properties:
                    value:
                      type: string
                      description: The cell's text content.
                      example: "A day in the life of a content creator..."
      responses:
        '200':
          description: Updated cell
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Cell'

  /autocontentengine/{sheet_id}/cells/mass_update:
    put:
      operationId: massUpdateCells
      x-phase: edit
      summary: Bulk update cells
      tags: [Cells]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Updated cells

  /autocontentengine/{sheet_id}/cells/{cell_id}/set_default_user_job:
    patch:
      operationId: setDefaultUserJob
      x-phase: edit
      summary: Set default generation for a cell
      description: Pin a specific generation result as the active/visible one for this cell.
      tags: [Cells]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
        - $ref: '#/components/parameters/CellId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [user_job_id]
              properties:
                user_job_id:
                  type: string
                  description: The generation (UserJob) ID to set as default.
      responses:
        '200':
          description: Updated cell

  # ── Layers ─────────────────────────────────────────────────
  /autocontentengine/{sheet_id}/cells/{cell_id}/layers:
    post:
      operationId: createLayer
      x-phase: edit
      summary: Create a layer
      description: Add a new video layer (text, sound, clip, etc.) to a cell.
      tags: [Layers]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
        - $ref: '#/components/parameters/CellId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                video_layer:
                  type: object
                  properties:
                    name:
                      type: string
                      example: "Background Music"
                    type:
                      type: string
                      example: "sound"
                    position:
                      type: integer
      responses:
        '201':
          description: Layer created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Layer'

  /autocontentengine/{sheet_id}/cells/{cell_id}/layers/{layer_id}:
    get:
      operationId: getLayer
      x-phase: edit
      summary: Get a layer
      tags: [Layers]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
        - $ref: '#/components/parameters/CellId'
        - $ref: '#/components/parameters/LayerId'
      responses:
        '200':
          description: Layer detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Layer'

    patch:
      operationId: updateLayer
      x-phase: edit
      summary: Update a layer
      tags: [Layers]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
        - $ref: '#/components/parameters/CellId'
        - $ref: '#/components/parameters/LayerId'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                video_layer:
                  type: object
                  properties:
                    name:
                      type: string
                    additional_attributes:
                      type: object
      responses:
        '200':
          description: Updated layer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Layer'

    delete:
      operationId: deleteLayer
      x-phase: edit
      summary: Delete a layer
      tags: [Layers]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
        - $ref: '#/components/parameters/CellId'
        - $ref: '#/components/parameters/LayerId'
      responses:
        '204':
          description: Layer deleted

  /autocontentengine/{sheet_id}/cells/{cell_id}/layers/{layer_id}/duplicate:
    post:
      operationId: duplicateLayer
      x-phase: edit
      summary: Duplicate a layer
      tags: [Layers]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
        - $ref: '#/components/parameters/CellId'
        - $ref: '#/components/parameters/LayerId'
      responses:
        '201':
          description: Duplicated layer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Layer'

  /autocontentengine/{sheet_id}/cells/{cell_id}/layers/update_positions:
    put:
      operationId: updateLayerPositions
      x-phase: edit
      summary: Reorder layers
      tags: [Layers]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
        - $ref: '#/components/parameters/CellId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [layers]
              properties:
                layers:
                  type: array
                  items:
                    type: object
                    required: [id, position]
                    properties:
                      id:
                        type: string
                      position:
                        type: integer
      responses:
        '200':
          description: Updated layers

  # ── Generations ────────────────────────────────────────────
  /autocontentengine/{sheet_id}/cells/{cell_id}/generate:
    post:
      operationId: generateCellContent
      x-phase: edit
      summary: Generate content for a cell
      description: |
        Triggers AI content generation for a cell. Credits are pre-charged.
        Poll the generation status via `GET /v1/generations/{id}`.
      tags: [Generations]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
        - $ref: '#/components/parameters/CellId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [generation_type, data]
              properties:
                generation_type:
                  type: string
                  description: |
                    The type of AI generation to run. Canonical names:
                    text, image_from_text, video_from_text, video_from_image,
                    video_from_ingredients, speech_from_text, lipsync, captions, media.
                    Use canonical names for new integrations.
                  enum:
                    - text
                    - image_from_text
                    - video_from_text
                    - video_from_image
                    - video_from_ingredients
                    - speech_from_text
                    - lipsync
                    - captions
                    - media
                  example: "text"
                data:
                  type: object
                  description: Generation parameters (varies by type).
                  example:
                    prompt: "Write a TikTok script about morning routines"
                    model: "gemini_2_0_flash"
      responses:
        '201':
          description: Generation started
          content:
            application/json:
              schema:
                type: object
                properties:
                  generation_id:
                    type: string
                  status:
                    type: string
                    example: "pending"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          description: Insufficient credits or invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /autocontentengine/{sheet_id}/cells/{cell_id}/layers/{layer_id}/generate:
    post:
      operationId: generateLayerContent
      x-phase: edit
      summary: Generate content for a layer
      description: |
        Triggers AI generation for a video layer (voice, music, clip, etc.).
        Credits are pre-charged. Poll status via `GET /v1/generations/{id}`.
      tags: [Generations]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
        - $ref: '#/components/parameters/CellId'
        - $ref: '#/components/parameters/LayerId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [generation_type, data]
              properties:
                generation_type:
                  type: string
                  description: Generation type — see cell generate endpoint for full list.
                  example: "speech_from_text"
                data:
                  type: object
      responses:
        '201':
          description: Generation started
          content:
            application/json:
              schema:
                type: object
                properties:
                  generation_id:
                    type: string
                  status:
                    type: string
                    example: "pending"

  /generations/{generation_id}:
    get:
      operationId: getGeneration
      x-phase: edit
      summary: Get generation status
      description: Check the status and result of an AI generation job.
      tags: [Generations]
      parameters:
        - name: generation_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Generation details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Generation'

  /generations/{generation_id}/stop:
    post:
      operationId: stopGeneration
      x-phase: edit
      summary: Stop a generation
      description: Cancels a running generation. Credits are refunded.
      tags: [Generations]
      parameters:
        - name: generation_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Generation stopped
          content:
            application/json:
              schema:
                type: object
                properties:
                  generation_id:
                    type: string
                  status:
                    type: string
                    example: "stopped"

  /generations/{generation_id}/continue:
    post:
      operationId: continueGeneration
      x-phase: edit
      summary: Continue a stopped generation
      description: Resumes a previously stopped generation. Credits are re-charged.
      tags: [Generations]
      parameters:
        - name: generation_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Generation resumed
          content:
            application/json:
              schema:
                type: object
                properties:
                  generation_id:
                    type: string
                  status:
                    type: string
                    example: "pending"

  # ── Render ────────────────────────────────────────────────
  /autocontentengine/{sheet_id}/cells/{cell_id}/render:
    post:
      operationId: renderVideo
      x-phase: export
      summary: Render final video
      description: |
        Composites all layers (video, audio, captions, text overlays) into a final video.
        The result appears in the final_video column cell.
        Returns a generation_id — poll `GET /v1/generations/{id}` until status is "completed".
        Credits are pre-charged and refunded on failure.
      tags: [Generations]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
        - $ref: '#/components/parameters/CellId'
      responses:
        '201':
          description: Render started
          content:
            application/json:
              schema:
                type: object
                properties:
                  generation_id:
                    type: string
                  status:
                    type: string
                    example: "pending"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # ── Variables ──────────────────────────────────────────────
  /autocontentengine/{sheet_id}/global_variables:
    get:
      operationId: getGlobalVariables
      x-phase: edit
      summary: Get global variables
      description: Returns the global variables sheet associated with this sheet.
      tags: [Variables]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
      responses:
        '200':
          description: Global variables sheet
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Sheet'

  /autocontentengine/{sheet_id}/import_global_variables:
    post:
      operationId: importGlobalVariables
      x-phase: edit
      summary: Import global variables from XLSX
      description: Upload an XLSX file to populate the global variables sheet.
      tags: [Variables]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/SheetId'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: XLSX file with global variables
      responses:
        '200':
          description: Variables imported
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Sheet'

  /autocontentengine/global_variables_template:
    get:
      operationId: downloadVariablesTemplate
      x-phase: edit
      summary: Download variables template
      description: Downloads a blank XLSX template for global variables.
      tags: [Variables]
      responses:
        '200':
          description: XLSX file
          content:
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                format: binary

  # ── Automation ─────────────────────────────────────────────
  /agents/{agent_id}/automation_config:
    get:
      operationId: getAutomationConfig
      x-phase: edit
      summary: Get automation config
      description: Returns the agent's webhook and callback configuration.
      tags: [Automation]
      parameters:
        - $ref: '#/components/parameters/AgentIdPath'
      responses:
        '200':
          description: Automation configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutomationConfig'

    patch:
      operationId: updateAutomationConfig
      x-phase: edit
      summary: Update automation config
      description: Configure webhooks and callbacks for the agent.
      tags: [Automation]
      parameters:
        - $ref: '#/components/parameters/AgentIdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [automation_config]
              properties:
                automation_config:
                  type: object
                  properties:
                    webhook_url:
                      type: string
                      format: uri
                      description: URL to receive event notifications.
                      example: "https://hooks.example.com/gen"
                    callback_url:
                      type: string
                      format: uri
                      description: URL to receive generation completion callbacks.
                      example: "https://api.example.com/callbacks/gen"
                    polling_enabled:
                      type: boolean
                      description: Enable polling-based status checks.
      responses:
        '200':
          description: Updated config
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutomationConfig'

  /agents/{agent_id}/automation_config/test_webhook:
    post:
      operationId: testWebhook
      x-phase: edit
      summary: Test webhook
      description: Sends a test payload to the configured webhook URL.
      tags: [Automation]
      parameters:
        - $ref: '#/components/parameters/AgentIdPath'
      responses:
        '200':
          description: Test result
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  error:
                    type: string

  /agents/{agent_id}/automation_config/test_callback:
    post:
      operationId: testCallback
      x-phase: edit
      summary: Test callback
      description: Sends a test payload to the configured callback URL.
      tags: [Automation]
      parameters:
        - $ref: '#/components/parameters/AgentIdPath'
      responses:
        '200':
          description: Test result
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  error:
                    type: string

  # ── Content Monitoring & Publishing ─────────────────────────
  /user_jobs:
    post:
      operationId: createUserJob
      x-phase: [ideas, export]
      summary: Create user job (monitoring or publishing)
      description: |
        Create a background job. Use `user_job_type` to select the job kind:

        - **`train_social`** — scrape social media content into the agent's knowledge base. Supports TikTok, Instagram, and YouTube. Send as multipart/form-data with `user_job[user_job_type]` and `user_job[data]` (JSON-stringified).
        - **`publish_content`** — post or schedule content to a social platform. Send as JSON with top-level `user_job_type` and `data` fields.
      tags: [Content Monitoring, Publishing]
      parameters:
        - $ref: '#/components/parameters/AgentId'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: ["user_job[user_job_type]", "user_job[data]"]
              properties:
                "user_job[user_job_type]":
                  type: string
                  enum: [train_social]
                "user_job[data]":
                  type: string
                  description: JSON-stringified object with platform (tiktok|instagram|youtube), type (username|hashtag|keyword), value, days (0|1|7|30|90|180), country, max_results (1-50), monitoring (default false), comment_monitoring.
            example:
              "user_job[user_job_type]": train_social
              "user_job[data]": '{"platform":"instagram","type":"username","value":"@fashionbrand","days":30,"max_results":50,"monitoring":true}'
          application/json:
            schema:
              type: object
              required: [user_job_type, data]
              properties:
                user_job_type:
                  type: string
                  enum: [publish_content]
                data:
                  type: string
                  description: JSON-stringified object with platform, media_url, description, schedule_type, scheduled_time, media_type, title, thumbnail_url, timezone_offset.
            example:
              user_job_type: publish_content
              data: '{"platform":"tiktok","media_url":"https://cdn.example.com/video.mp4","description":"Factory price reveal #sourcing","schedule_type":"now","media_type":"VIDEO"}'
      responses:
        '201':
          description: Job created
          content:
            application/json:
              schema:
                type: object
                properties:
                  user_job_id:
                    type: integer
              example:
                user_job_id: 12345
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          description: Validation error, unsupported platform/type combination, or no active credit purchase.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /user_jobs/{id}:
    get:
      operationId: getUserJob
      x-phase: [ideas, export]
      summary: Get user job status
      description: |
        Poll an async user job. Works for both `train_social` (content monitoring) and `publish_content` (social publishing) jobs. Returns current status, progress, and the final `result` on completion.
      tags: [Content Monitoring, Publishing]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
        - $ref: '#/components/parameters/AgentId'
      responses:
        '200':
          description: User job status
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: integer
                  user_job_type:
                    type: string
                  status:
                    type: string
                    enum: [pending, processing, completed, failed]
                  result:
                    type: object
                    description: Populated on `completed`. For `publish_content`, contains `post_id`.
                  failed_reason:
                    type: string
                    description: Populated on `failed`.
              example:
                id: 138860
                user_job_type: publish_content
                status: completed
                result:
                  post_id: "7459234123456"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      operationId: updateMonitoringJob
      x-phase: ideas
      summary: Update monitoring job
      description: Update an existing `train_social` job. Only jobs with `pending` or `processing` status can be updated. Send fields as flat form params (not nested).
      tags: [Content Monitoring]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
        - $ref: '#/components/parameters/AgentId'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                platform:
                  type: string
                  enum: [tiktok, instagram, youtube]
                type:
                  type: string
                  enum: [username, hashtag, keyword]
                value:
                  type: string
                days:
                  type: integer
                  enum: [0, 1, 7, 30, 90, 180]
                country:
                  type: string
                max_results:
                  type: integer
                  minimum: 1
                  maximum: 50
                monitoring:
                  type: boolean
                comment_monitoring:
                  type: boolean
      responses:
        '200':
          description: Monitoring job updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  user_job_id:
                    type: integer
              example:
                user_job_id: 12345
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Job status invalid or validation error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # ── Templates ─────────────────────────────────────────────
  /templates/projects:
    get:
      operationId: listTemplates
      x-phase: convert
      summary: List templates
      description: Returns paginated list of pre-configured engine templates. 20 per page.
      tags: [Templates]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
          description: Page number (starts at 1).
      responses:
        '200':
          description: List of templates
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Template'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /templates/projects/{slug}:
    get:
      operationId: getTemplate
      x-phase: convert
      summary: Get a template
      description: Returns a single template by slug, UUID, or numeric ID.
      tags: [Templates]
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
          description: URL slug, UUID, or numeric ID.
      responses:
        '200':
          description: Template details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Template'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /templates/spreadsheets/{slug}/clone:
    post:
      operationId: cloneTemplate
      x-phase: convert
      summary: Clone a template
      description: Clones the template into the specified agent's workspace. Returns the new engine with columns, rows, and cells.
      tags: [Templates]
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
          description: Template slug, UUID, or numeric ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [agent_id]
              properties:
                agent_id:
                  type: string
                  description: Agent to clone the template into.
      responses:
        '201':
          description: Cloned engine
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Sheet'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/CreditRequired'

  # ── Organizations ─────────────────────────────────────────
  /organizations:
    get:
      operationId: listOrganizations
      x-phase: setup
      summary: List organizations
      description: Returns all organizations the authenticated user belongs to.
      tags: [Organizations]
      responses:
        '200':
          description: List of organizations
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Organization'
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      operationId: createOrganization
      x-phase: setup
      summary: Create an organization
      tags: [Organizations]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [organization]
              properties:
                "organization[name]":
                  type: string
                  example: "My Company"
                "organization[avatar]":
                  type: string
                  format: binary
                  description: Organization avatar image.
      responses:
        '201':
          description: Organization created
          content:
            application/json:
              schema:
                type: object
                properties:
                  organization_id:
                    type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /organizations/{organization_id}:
    get:
      operationId: getOrganization
      x-phase: setup
      summary: Get an organization
      tags: [Organizations]
      parameters:
        - name: organization_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Organization details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Organization'
        '422':
          description: Organization not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    patch:
      operationId: updateOrganization
      x-phase: setup
      summary: Update an organization
      description: Requires owner or manager role.
      tags: [Organizations]
      parameters:
        - name: organization_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                "organization[name]":
                  type: string
                "organization[avatar]":
                  type: string
                  format: binary
      responses:
        '200':
          description: Organization updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  organization_id:
                    type: string
        '403':
          description: Forbidden (not owner or manager)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Organization not found or validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    delete:
      operationId: deleteOrganization
      x-phase: setup
      summary: Delete an organization
      description: Permanently deletes the organization and all associated data. Requires owner role. Irreversible.
      tags: [Organizations]
      parameters:
        - name: organization_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Organization deleted
        '403':
          description: Forbidden (not owner)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Organization not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # ── Content Resources ─────────────────────────────────────
  /content_resources:
    get:
      operationId: listContentResources
      x-phase: edit
      summary: List content resources
      description: Returns paginated content resources. 20 items per page (page starts at 0).
      tags: [Content Resources]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - name: type
          in: query
          schema:
            type: string
            enum: [image, video, audio, zip, safe_tensors]
          description: Filter by resource type.
        - name: project_id
          in: query
          schema:
            type: string
          description: Filter by project (engine) ID.
        - name: ids[]
          in: query
          schema:
            type: array
            items:
              type: string
          description: Filter by specific resource IDs.
        - name: page
          in: query
          schema:
            type: integer
          description: Page number (starts at 0).
      responses:
        '200':
          description: List of content resources
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ContentResource'
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      operationId: createContentResource
      x-phase: edit
      summary: Upload a content resource
      description: Upload a file as a content resource. Optionally assign to an asset folder.
      tags: [Content Resources]
      parameters:
        - $ref: '#/components/parameters/AgentId'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: ["content_resource[file]"]
              properties:
                "content_resource[file]":
                  type: string
                  format: binary
                  description: The file to upload.
                "asset_folder[id]":
                  type: string
                  description: Optional asset folder ID.
      responses:
        '201':
          description: Content resource created
          content:
            application/json:
              schema:
                type: object
                properties:
                  content_resource:
                    $ref: '#/components/schemas/ContentResource'
                  generator:
                    type: object
                    nullable: true
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Creation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /content_resources/{id}:
    get:
      operationId: getContentResource
      x-phase: edit
      summary: Get a content resource
      tags: [Content Resources]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/AgentId'
      responses:
        '200':
          description: Content resource details
          content:
            application/json:
              schema:
                type: object
                properties:
                  content_resource:
                    $ref: '#/components/schemas/ContentResource'
                  generator:
                    type: object
                    nullable: true
                    description: Null for manual uploads; contains job info for AI-generated resources.
                    properties:
                      id:
                        type: string
                      status:
                        type: string
                      type:
                        type: string
        '404':
          description: Content resource not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    patch:
      operationId: updateContentResource
      x-phase: edit
      summary: Rename a content resource
      tags: [Content Resources]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/AgentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                content_resource:
                  type: object
                  properties:
                    filename:
                      type: string
      responses:
        '200':
          description: Content resource updated
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'

    delete:
      operationId: deleteContentResource
      x-phase: edit
      summary: Delete a content resource
      tags: [Content Resources]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/AgentId'
      responses:
        '200':
          description: Content resource deleted
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'

  # ── Asset Libraries ───────────────────────────────────────
  /asset_libraries:
    get:
      operationId: listAssetLibraries
      x-phase: edit
      summary: Browse asset library
      description: |
        Unified view of files and folders. Polymorphic response — items have
        `type: "AssetFolder"` or `type: "ContentResource"`.
      tags: [Asset Libraries]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - name: folder_id
          in: query
          schema:
            type: string
          description: Browse inside a specific folder.
        - name: asset_type
          in: query
          schema:
            type: string
            enum: [image, video, audio, folder]
          description: Filter by asset type.
        - name: search
          in: query
          schema:
            type: string
          description: Search by file name.
        - name: order
          in: query
          schema:
            type: string
            enum: [recent]
          description: Sort order.
        - name: page
          in: query
          schema:
            type: integer
        - name: page_size
          in: query
          schema:
            type: integer
      responses:
        '200':
          description: List of assets and folders
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                    type:
                      type: string
                      enum: [AssetFolder, ContentResource]
                    name:
                      type: string
                      description: Folder name (AssetFolder only).
                    url:
                      type: string
                      format: uri
                      description: File URL (ContentResource only).
                    thumbnail_url:
                      type: string
                      format: uri
                      nullable: true
                    file_name:
                      type: string
                    content_type:
                      type: string
        '401':
          $ref: '#/components/responses/Unauthorized'

  /asset_folders:
    post:
      operationId: createAssetFolder
      x-phase: edit
      summary: Create an asset folder
      tags: [Asset Libraries]
      parameters:
        - $ref: '#/components/parameters/AgentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [asset_folder]
              properties:
                asset_folder:
                  type: object
                  required: [name]
                  properties:
                    name:
                      type: string
                      example: "Campaign Assets"
                    parent_id:
                      type: string
                      description: Parent folder ID for nesting.
      responses:
        '201':
          description: Folder created
          content:
            application/json:
              schema:
                type: object
                properties:
                  asset_folder_id:
                    type: string
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /asset_folders/{id}:
    patch:
      operationId: updateAssetFolder
      x-phase: edit
      summary: Rename an asset folder
      tags: [Asset Libraries]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/AgentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [asset_folder]
              properties:
                asset_folder:
                  type: object
                  properties:
                    name:
                      type: string
      responses:
        '200':
          description: Folder renamed
          content:
            application/json:
              schema:
                type: object
                properties:
                  asset_folder_id:
                    type: string
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'

    delete:
      operationId: deleteAssetFolder
      x-phase: edit
      summary: Delete an asset folder
      description: Deletes the folder and all its contents (subfolders and files).
      tags: [Asset Libraries]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/AgentId'
      responses:
        '200':
          description: Folder deleted
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'

  # ── Direct Upload ─────────────────────────────────────────
  /direct_upload:
    post:
      operationId: createDirectUpload
      x-phase: edit
      summary: Get pre-signed upload URL
      description: |
        Returns a pre-signed S3 URL for uploading large files (up to 1 GB).

        **Workflow:**
        1. Call this endpoint with file metadata
        2. PUT the file to the returned URL with the specified headers
        3. Use the `signed_id` as `content_resource[file]` in a content resource creation call

        **Compute checksum:** `openssl md5 -binary file | base64`
      tags: [Direct Upload]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [blob]
              properties:
                blob:
                  type: object
                  required: [filename, byte_size, checksum, content_type]
                  properties:
                    filename:
                      type: string
                      example: "hero-video.mp4"
                    byte_size:
                      type: integer
                      description: File size in bytes (max 1 GB).
                      example: 52428800
                    checksum:
                      type: string
                      description: Base64-encoded MD5 checksum.
                      example: "rL0Y20zC+Fzt72VPzMSk2A=="
                    content_type:
                      type: string
                      example: "video/mp4"
                    metadata:
                      type: object
      responses:
        '200':
          description: Pre-signed upload details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DirectUpload'
        '422':
          description: Invalid content type, file size exceeded, or upload failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # ── Agent Profile ─────────────────────────────────────────
  /agent/profile:
    get:
      operationId: getAgentProfile
      x-phase: setup
      summary: Get agent profile
      description: Returns the agent's identity, voice, and brand profile configuration.
      tags: [Agent Profile]
      parameters:
        - $ref: '#/components/parameters/AgentId'
      responses:
        '200':
          description: Agent profile
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentProfile'
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      operationId: createAgentProfile
      x-phase: setup
      summary: Create agent profile
      description: Creates an initial profile for the agent.
      tags: [Agent Profile]
      parameters:
        - $ref: '#/components/parameters/AgentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentProfileInput'
      responses:
        '201':
          description: Profile created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentProfile'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    put:
      operationId: updateAgentProfile
      x-phase: setup
      summary: Update agent profile
      description: Replaces the agent's profile with the provided data.
      tags: [Agent Profile]
      parameters:
        - $ref: '#/components/parameters/AgentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentProfileInput'
      responses:
        '200':
          description: Profile updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentProfile'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    delete:
      operationId: deleteAgentProfile
      x-phase: setup
      summary: Delete agent profile
      description: Removes the agent's profile configuration.
      tags: [Agent Profile]
      parameters:
        - $ref: '#/components/parameters/AgentId'
      responses:
        '200':
          description: Profile deleted
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ── Agent Chat ────────────────────────────────────────────
  # NOTE: All /agent/* chat endpoints use base URL https://agent.gen.pro/v1

  /agent/run:
    post:
      operationId: startAgentRun
      x-phase: ideas
      summary: Start an agent run
      description: |
        Sends a message to the agent and starts a new run. Returns immediately with a run ID.
        Poll `GET /agent/runs/{run_id}` for status and messages, or use the returned progress path for live updates.

        **Base URL:** `https://agent.gen.pro/v1`
      tags: [Agent Chat]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [message, agent_id]
              properties:
                message:
                  type: string
                  description: The user's message to the agent.
                  example: "Generate 5 TikTok video ideas about morning routines"
                agent_id:
                  type: string
                  description: The agent to chat with.
                conversation_id:
                  type: string
                  description: Continue an existing conversation. Omit to start a new one.
                attachments:
                  type: array
                  items:
                    type: object
                  description: File attachments for the message.
                context:
                  type: object
                  description: Additional context for the agent.
                debug:
                  type: boolean
                  description: Enable debug mode for verbose logging.
      responses:
        '202':
          description: Run started
          content:
            application/json:
              schema:
                type: object
                properties:
                  run_id:
                    type: string
                  conversation_id:
                    type: string
                  status:
                    type: string
                    example: "running"
                  firebase_path:
                    type: string
                    description: Run-specific progress path for live updates.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          description: Invalid parameters or agent not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /agent/runs/{run_id}:
    get:
      operationId: getAgentRun
      x-phase: ideas
      summary: Get agent run status
      description: |
        Poll this endpoint to check run status and retrieve messages.

        **Base URL:** `https://agent.gen.pro/v1`
      tags: [Agent Chat]
      parameters:
        - name: run_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Run status and messages
          content:
            application/json:
              schema:
                type: object
                properties:
                  run_id:
                    type: string
                  conversation_id:
                    type: string
                  status:
                    type: string
                    enum: [running, completed, failed, awaiting_approval]
                  messages:
                    type: array
                    items:
                      type: object
                      properties:
                        role:
                          type: string
                          enum: [user, assistant, system]
                        content:
                          type: string
                        created_at:
                          type: string
                          format: date-time
        '404':
          $ref: '#/components/responses/NotFound'

  /agent/runs/{run_id}/approve:
    post:
      operationId: approveAgentAction
      x-phase: ideas
      summary: Approve or reject a pending agent action
      description: |
        When a run has status `awaiting_approval`, approve the proposed action to continue.
        Send `approved: false` to reject the action and fail the run.

        **Base URL:** `https://agent.gen.pro/v1`
      tags: [Agent Chat]
      parameters:
        - name: run_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [approved]
              properties:
                approved:
                  type: boolean
                  description: "`true` approves and continues the run; `false` rejects the pending action."
      responses:
        '200':
          description: Action decision accepted
          content:
            application/json:
              schema:
                type: object
                properties:
                  run_id:
                    type: string
                  status:
                    type: string
                    enum: [running, failed]
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Run not in awaiting_approval state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /agent/conversations:
    get:
      operationId: listConversations
      x-phase: ideas
      summary: List conversations
      description: |
        Returns all conversations for the authenticated user.

        **Base URL:** `https://agent.gen.pro/v1`
      tags: [Agent Chat]
      parameters:
        - $ref: '#/components/parameters/AgentId'
      responses:
        '200':
          description: List of conversations
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Conversation'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /agent/conversations/{id}:
    get:
      operationId: getConversation
      x-phase: ideas
      summary: Get a conversation
      description: |
        Returns a conversation with its messages.

        **Base URL:** `https://agent.gen.pro/v1`
      tags: [Agent Chat]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Conversation with messages
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Conversation'
        '404':
          $ref: '#/components/responses/NotFound'

    patch:
      operationId: updateConversation
      x-phase: ideas
      summary: Rename or pin a conversation
      description: |
        Update conversation metadata (title, pinned status).

        **Base URL:** `https://agent.gen.pro/v1`
      tags: [Agent Chat]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                pinned:
                  type: boolean
      responses:
        '200':
          description: Conversation updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Conversation'
        '404':
          $ref: '#/components/responses/NotFound'

    delete:
      operationId: deleteConversation
      x-phase: ideas
      summary: Delete a conversation
      description: |
        Soft-deletes a conversation and its messages.

        **Base URL:** `https://agent.gen.pro/v1`
      tags: [Agent Chat]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Conversation deleted
        '404':
          $ref: '#/components/responses/NotFound'

  /agent/conversations/{id}/messages:
    get:
      operationId: listConversationMessages
      x-phase: ideas
      summary: List conversation messages
      description: |
        Returns paginated message history for a conversation.

        **Base URL:** `https://agent.gen.pro/v1`
      tags: [Agent Chat]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: page
          in: query
          schema:
            type: integer
        - name: per_page
          in: query
          schema:
            type: integer
      responses:
        '200':
          description: Paginated messages
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    role:
                      type: string
                      enum: [user, assistant, system]
                    content:
                      type: string
                    created_at:
                      type: string
                      format: date-time
        '404':
          $ref: '#/components/responses/NotFound'

  /agent/conversations/{id}/runs:
    get:
      operationId: listConversationRuns
      x-phase: ideas
      summary: List runs in a conversation
      description: |
        Returns all agent runs within a conversation.

        **Base URL:** `https://agent.gen.pro/v1`
      tags: [Agent Chat]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: List of runs
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    run_id:
                      type: string
                    status:
                      type: string
                      enum: [running, completed, failed, awaiting_approval]
                    created_at:
                      type: string
                      format: date-time
        '404':
          $ref: '#/components/responses/NotFound'

  /agent/ideas:
    get:
      operationId: listIdeas
      x-phase: ideas
      summary: List content ideas
      description: |
        Returns content ideas for the agent. Requires `agent_id`.

        **Base URL:** `https://agent.gen.pro/v1`
      tags: [Agent Chat]
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - name: status
          in: query
          schema:
            type: string
            enum: [generated, approve_to_create, ready_for_review, change_idea, change_video, rejected, approved_to_post, posted]
          description: Filter by idea status.
      responses:
        '200':
          description: List of ideas
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Idea'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /agent/ideas/{id}/status:
    post:
      operationId: cycleIdeaStatus
      x-phase: ideas
      summary: Cycle idea status
      description: |
        Advances the idea to the next status in the workflow.

        **Base URL:** `https://agent.gen.pro/v1`
      tags: [Agent Chat]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Status updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Idea'
        '404':
          $ref: '#/components/responses/NotFound'

  /agent/ideas/{id}/status/{status}:
    put:
      operationId: setIdeaStatus
      x-phase: ideas
      summary: Set idea status explicitly
      description: |
        Sets the idea to a specific status.

        **Base URL:** `https://agent.gen.pro/v1`
      tags: [Agent Chat]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: status
          in: path
          required: true
          schema:
            type: string
            enum: [generated, approve_to_create, ready_for_review, change_idea, change_video, rejected, approved_to_post, posted]
      responses:
        '200':
          description: Status set
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Idea'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Invalid status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # ── Research (agent.gen.pro) ──────────────────────────────
  /research:
    post:
      operationId: runResearch
      x-phase: ideas
      summary: Start research for a topic
      description: |
        Starts standalone research across multiple public sources and returns an async run.
        Poll `GET /v1/agent/runs/{run_id}` for completion and messages.

        Depths: `quick`, `default`, `deep`. This is a paid operation.

        **Base URL:** `https://agent.gen.pro/v1`
      tags: [Agent Chat]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [topic, agent_id]
              properties:
                topic:
                  type: string
                  example: "tariffs on beauty imports 2026"
                depth:
                  type: string
                  enum: [quick, default, deep]
                  default: default
                agent_id:
                  type: string
      responses:
        '202':
          description: Research run accepted
          content:
            application/json:
              schema:
                type: object
                required: [run_id, conversation_id, status]
                properties:
                  run_id:
                    type: string
                  conversation_id:
                    type: string
                  status:
                    type: string
                    example: running
                  firebase_path:
                    type: string
                    description: Run-specific progress path for live updates.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          description: Invalid parameters or insufficient credits
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # ── Agent Core ────────────────────────────────────────────
  /agents/{agent_id}/core:
    get:
      operationId: getAgentCore
      x-phase: setup
      summary: Get the full agent setup
      description: |
        Returns the agent's full setup as a flat object whose field names mirror
        exactly what the GEN Setup canvas FE reads and writes. Merges brand
        setup, voice association, and personality into a single response — you
        get the whole agent in one call.
      tags: [Agent Core]
      parameters:
        - name: agent_id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Full agent setup
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentCore'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
    patch:
      operationId: patchAgentCore
      x-phase: setup
      summary: Update any combination of setup fields
      description: |
        Flat merge-patch. Send only the fields you want to change — missing
        fields are not forwarded. Field names map 1:1 to the GEN Setup canvas
        FE — no wrappers, no sections.

        **Field groups:**
        - Brand setup — `brand_name`, `description`, `identity_type`, `goal`,
          `keywords`, `monitored`, `target_platforms`, `shortform`, `longform`,
          `onboarding_status`, `linked_accounts`.
        - Personality — `personality` (full persona/backstory, max 20000 chars).
        - Voice — `default_user_voice`.

        **Guardrails:**
        - Unknown fields return 422 — typos fail fast.
        - `description` is capped at 500 chars. Full persona text belongs in
          `personality` (max 20000 chars).
        - `identity_type` is a strict enum: `"brand"` or `"character"`.
        - `monitored[].item_type` is a strict enum: `"account"`, `"hashtag"`,
          or `"keyword"`.

        **Semantics:** `linked_accounts` and `monitored` use full-list
        replacement. To add one row without touching others, read the current
        list via GET first, append, then PATCH.

        Returns 200 on full success, 207 Multi-Status with per-target results
        on partial failure. See `AgentCorePatchResult` for target keys.
      tags: [Agent Core]
      parameters:
        - name: agent_id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentCorePatch'
      responses:
        '200':
          description: All fields updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentCorePatchResult'
        '207':
          description: Partial success — inspect per-target status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentCorePatchResult'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '422':
          description: Validation error — unknown field, bad enum, or length cap exceeded
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  # ── Agent Voice ───────────────────────────────────────────
  /agents/{agent_id}/voice/library:
    get:
      operationId: listAgentVoices
      x-phase: setup
      summary: List voices available to the agent
      description: |
        Merges four sources: `public` (shared catalog), `user_designed`
        (via the 4-step design flow), `user_trained` (cloned from audio),
        and `user_elevenlabs` (from the user's connected ElevenLabs account).
      tags: [Agent Voice]
      parameters:
        - { name: agent_id, in: path, required: true, schema: { type: string } }
        - name: source
          in: query
          schema:
            type: string
            enum: [public, user_designed, user_trained, user_elevenlabs]
      responses:
        '200':
          description: Voice list
          content:
            application/json:
              schema:
                type: object
                properties:
                  voices:
                    type: array
                    items: { $ref: '#/components/schemas/VoiceLibraryItem' }
                  total: { type: integer }

  /agents/{agent_id}/voice/integrations/elevenlabs:
    get:
      operationId: getElevenLabsIntegrationStatus
      x-phase: setup
      summary: Check whether the agent has an ElevenLabs key connected
      tags: [Agent Voice]
      parameters:
        - { name: agent_id, in: path, required: true, schema: { type: string } }
      responses:
        '200':
          description: Status
          content:
            application/json:
              schema:
                type: object
                properties:
                  connected: { type: boolean }
                  masked_key: { type: string, nullable: true }
    post:
      operationId: connectElevenLabs
      x-phase: setup
      summary: Connect the user's ElevenLabs API key
      description: Validates the key against ElevenLabs /v1/user before saving it on the agent record.
      tags: [Agent Voice]
      parameters:
        - { name: agent_id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                api_key: { type: string }
              required: [api_key]
      responses:
        '200':
          description: Connected
        '400':
          description: invalid_key
    delete:
      operationId: disconnectElevenLabs
      x-phase: setup
      summary: Remove the ElevenLabs key from the agent
      tags: [Agent Voice]
      parameters:
        - { name: agent_id, in: path, required: true, schema: { type: string } }
      responses:
        '204': { description: Disconnected }

  /agents/{agent_id}/voice/integrations/elevenlabs/test:
    post:
      operationId: testElevenLabsKey
      x-phase: setup
      summary: Test an ElevenLabs key without saving it
      tags: [Agent Voice]
      parameters:
        - { name: agent_id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties: { api_key: { type: string } }
              required: [api_key]
      responses:
        '200':
          description: Tested
          content:
            application/json:
              schema:
                type: object
                properties: { valid: { type: boolean } }

  /agents/{agent_id}/voice/design/generate-script:
    post:
      operationId: generateVoiceScript
      x-phase: setup
      summary: "Voice design step 1/4 — generate a read-aloud script"
      tags: [Agent Voice]
      parameters:
        - { name: agent_id, in: path, required: true, schema: { type: string } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                language: { type: string }
      responses:
        '200':
          description: Script
          content:
            application/json:
              schema:
                type: object
                properties: { voice_sample: { type: string } }

  /agents/{agent_id}/voice/design/generate-description:
    post:
      operationId: generateVoiceDescription
      x-phase: setup
      summary: "Voice design step 2/4 — generate style descriptors"
      tags: [Agent Voice]
      parameters:
        - { name: agent_id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                gender: { type: string }
                voice_description: { type: string }
                language: { type: string }
                script: { type: string }
              required: [gender]
      responses:
        '200':
          description: Description
          content:
            application/json:
              schema:
                type: object
                properties: { voice_description: { type: string } }

  /agents/{agent_id}/voice/design/generate-samples:
    post:
      operationId: generateVoiceSamples
      x-phase: setup
      summary: "Voice design step 3/4 — generate 3 candidate audio samples"
      tags: [Agent Voice]
      parameters:
        - { name: agent_id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                text: { type: string }
                description: { type: string }
              required: [text]
      responses:
        '200':
          description: Candidates
          content:
            application/json:
              schema:
                type: object
                properties:
                  samples:
                    type: array
                    items:
                      type: object
                      properties:
                        generation_id: { type: string }
                        audio: { type: string, description: "Base64-encoded audio bytes" }

  /agents/{agent_id}/voice/design:
    post:
      operationId: finalizeDesignedVoice
      x-phase: setup
      summary: "Voice design step 4/4 — finalize"
      description: "Persists a designed voice by picking one of the generation_id values from step 3."
      tags: [Agent Voice]
      parameters:
        - { name: agent_id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                generation_id: { type: string }
                name: { type: string }
                gender: { type: string }
                language: { type: string }
                description: { type: string }
              required: [generation_id, name]
      responses:
        '201':
          description: Voice created

  /agents/{agent_id}/voice/clone:
    post:
      operationId: cloneVoice
      x-phase: setup
      summary: Clone a voice from an existing audio sample
      description: |
        Synchronous. Provide exactly one of `audio_url` (preferred — server
        downloads) or `audio_base64` (inline bytes for small clips).
      tags: [Agent Voice]
      parameters:
        - { name: agent_id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                audio_url: { type: string, format: uri }
                audio_base64: { type: string }
                gender: { type: string }
                language: { type: string }
                description: { type: string }
              required: [name]
      responses:
        '201':
          description: Voice cloned

  /agents/{agent_id}/voice/{voice_id}:
    delete:
      operationId: deleteAgentVoice
      x-phase: setup
      summary: Delete a user-owned voice
      tags: [Agent Voice]
      parameters:
        - { name: agent_id, in: path, required: true, schema: { type: string } }
        - { name: voice_id, in: path, required: true, schema: { type: string } }
      responses:
        '204': { description: Deleted }
        '404': { $ref: '#/components/responses/NotFound' }

  /agents/{agent_id}/voice/{voice_id}/preview:
    post:
      operationId: previewVoice
      x-phase: setup
      summary: "Enqueue a TTS preview job (async)"
      description: |
        Returns `{user_job_id}` immediately. Poll
        `GET /v1/agents/{agent_id}/voice/preview/{job_id}` until
        `status == "completed"`, then read the audio URL from
        `output_resources`.
      tags: [Agent Voice]
      parameters:
        - { name: agent_id, in: path, required: true, schema: { type: string } }
        - { name: voice_id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                text: { type: string }
              required: [text]
      responses:
        '202':
          description: Job enqueued
          content:
            application/json:
              schema:
                type: object
                properties: { user_job_id: { type: integer } }

  /agents/{agent_id}/voice/preview/{job_id}:
    get:
      operationId: getVoicePreviewStatus
      x-phase: setup
      summary: Poll a TTS preview job
      tags: [Agent Voice]
      parameters:
        - { name: agent_id, in: path, required: true, schema: { type: string } }
        - { name: job_id, in: path, required: true, schema: { type: string } }
      responses:
        '200':
          description: User job record

components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: Personal Access Token (PAT). Create one in Settings > API Keys.
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Clerk/Dynamic XYZ JWT token.

  parameters:
    AgentId:
      name: agent_id
      in: query
      required: true
      description: The agent ID. Find via `GET /v1/agents`.
      schema:
        type: string
    AgentIdPath:
      name: agent_id
      in: path
      required: true
      schema:
        type: string
    SheetId:
      name: sheet_id
      in: path
      required: true
      schema:
        type: string
    RowId:
      name: row_id
      in: path
      required: true
      schema:
        type: string
    CellId:
      name: cell_id
      in: path
      required: true
      schema:
        type: string
    LayerId:
      name: layer_id
      in: path
      required: true
      schema:
        type: string
    TokenId:
      name: id
      in: path
      required: true
      schema:
        type: string

  schemas:
    User:
      type: object
      properties:
        id:
          type: string
        email:
          type: string
        name:
          type: string
        username:
          type: string
        created_at:
          type: string

    Workspace:
      type: object
      properties:
        id:
          type: string
        name:
          type: string

    Agent:
      type: object
      properties:
        id:
          type: string
        name:
          type: string

    ApiKey:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        token_type:
          type: string
        created_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
          nullable: true
        last_used_at:
          type: string
          format: date-time
          nullable: true
        revoked_at:
          type: string
          format: date-time
          nullable: true
        status:
          type: string
          enum: [active, expired, revoked]

    Sheet:
      type: object
      properties:
        id:
          type: string
        slug:
          type: string
        title:
          type: string
        project_type:
          type: string
        is_default:
          type: boolean
        uuid:
          type: string
        status:
          type: string
        created_at:
          type: integer
          description: Unix timestamp (seconds)
        created_at_ts:
          type: integer
          description: Unix timestamp (milliseconds)
        updated_at_ts:
          type: integer
          description: Unix timestamp (milliseconds)
        spreadsheet_columns:
          type: array
          items:
            $ref: '#/components/schemas/Column'
        spreadsheet_rows:
          type: array
          items:
            $ref: '#/components/schemas/Row'
        global_variables_sheet:
          nullable: true
          allOf:
            - $ref: '#/components/schemas/Sheet'

    Row:
      type: object
      properties:
        id:
          type: string
        position:
          type: integer
        additional_attributes:
          type: object
        created_at_ts:
          type: integer
        updated_at_ts:
          type: integer
        spreadsheet_cells:
          type: array
          items:
            $ref: '#/components/schemas/Cell'
        execution_cost:
          type: string
          nullable: true
          description: Total credit cost (only when `with_execution_cost=true`).

    Column:
      type: object
      properties:
        id:
          type: string
        title:
          type: string
        temp_title:
          type: string
          nullable: true
        type:
          type: string
        column_role:
          type: string
          nullable: true
        description:
          type: string
          nullable: true
        position:
          type: integer
        additional_attributes:
          type: object
        created_at_ts:
          type: integer
        updated_at_ts:
          type: integer

    Cell:
      type: object
      properties:
        id:
          type: string
        value:
          type: string
          nullable: true
        additional_attributes:
          type: object
        cell_role:
          type: string
          nullable: true
        created_at_ts:
          type: integer
        updated_at_ts:
          type: integer
        default_user_job:
          nullable: true
          allOf:
            - $ref: '#/components/schemas/Generation'
        user_jobs:
          type: array
          items:
            $ref: '#/components/schemas/Generation'
        video_layers:
          type: array
          items:
            $ref: '#/components/schemas/Layer'
        execution_cost:
          type: string
          nullable: true

    Layer:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        position:
          type: integer
        type:
          type: string
        additional_attributes:
          type: object
        created_at_ts:
          type: integer
        updated_at_ts:
          type: integer
        default_user_job:
          nullable: true
          allOf:
            - $ref: '#/components/schemas/Generation'
        user_jobs:
          type: array
          items:
            $ref: '#/components/schemas/Generation'
        execution_cost:
          type: string
          nullable: true

    Generation:
      type: object
      properties:
        id:
          type: string
        status:
          type: string
          enum: [pending, processing, completed, failed, stopped]
        user_job_type:
          type: string
        result:
          type: object
          nullable: true
        failed_reason:
          type: string
          nullable: true
        output_resources:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              url:
                type: string
                format: uri
              thumbnail_url:
                type: string
                format: uri
                nullable: true
              object_type:
                type: string
                description: '"Video", "Image", etc.'
              type:
                type: string
        execution_cost:
          type: string
          nullable: true

    AutomationConfig:
      type: object
      properties:
        webhook_url:
          type: string
          format: uri
          nullable: true
        callback_url:
          type: string
          format: uri
          nullable: true
        polling_enabled:
          type: boolean

    Template:
      type: object
      properties:
        id:
          type: string
        slug:
          type: string
        title:
          type: string
        name:
          type: string
        description:
          type: string
        cover:
          type: string
          format: uri
          nullable: true
        tutorial_url:
          type: string
          format: uri
          nullable: true
        tags:
          type: array
          items:
            type: string
        recent_generated_contents:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              type:
                type: string
              thumbnail_url:
                type: string
                format: uri

    AgentDetail:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
          nullable: true
        organization_id:
          type: string
        time_zone:
          type: string
          nullable: true
        primary_avatar_id:
          type: string
          nullable: true
        primary_avatar_url:
          type: string
          format: uri
          nullable: true
        agent_setup_project_id:
          type: string
          nullable: true
        role:
          type: string
        default_user_voice:
          type: object
          nullable: true
          properties:
            id:
              type: string
            name:
              type: string
            gender:
              type: string
            language:
              type: string
            provider:
              type: string
            url:
              type: string
              format: uri

    Avatar:
      type: object
      properties:
        id:
          type: string
        is_primary:
          type: boolean
        url:
          type: string
          format: uri
        thumbnail_url:
          type: string
          format: uri
          nullable: true
        degod_avatar_id:
          type: string
          nullable: true

    Organization:
      type: object
      properties:
        id:
          type: string
        uuid:
          type: string
        organization_id:
          type: string
        name:
          type: string
        avatar:
          type: object
          nullable: true
          properties:
            url:
              type: string
              format: uri
            thumbnail_url:
              type: string
              format: uri
        user_role:
          type: string
        credit:
          type: number
        available_credit:
          type: object
          properties:
            generic:
              type: number
            aura:
              type: number
        total_members:
          type: integer
        credit_plan:
          type: object
          nullable: true
          properties:
            id:
              type: string
            name:
              type: string
            cycle:
              type: string

    ContentResource:
      type: object
      properties:
        id:
          type: string
        url:
          type: string
          format: uri
        thumbnail_url:
          type: string
          format: uri
          nullable: true
        file_name:
          type: string
        content_type:
          type: string

    DirectUpload:
      type: object
      properties:
        id:
          type: string
        key:
          type: string
        filename:
          type: string
        content_type:
          type: string
        byte_size:
          type: integer
        checksum:
          type: string
        signed_id:
          type: string
          description: Use this as `content_resource[file]` when creating a content resource.
        direct_upload:
          type: object
          properties:
            url:
              type: string
              format: uri
              description: Pre-signed S3 URL. PUT the file here.
            headers:
              type: object
              properties:
                Content-Type:
                  type: string
                Content-MD5:
                  type: string

    AgentProfile:
      type: object
      properties:
        identity:
          type: object
          description: Agent identity configuration (name, persona, backstory).
        voice:
          type: object
          description: Voice and tone settings.
        brand:
          type: object
          description: Brand guidelines, colors, and style.

    AgentProfileInput:
      type: object
      description: All fields are optional. Provide only the sections you want to set.
      properties:
        identity:
          type: object
        voice:
          type: object
        brand:
          type: object

    Conversation:
      type: object
      properties:
        id:
          type: string
        title:
          type: string
          nullable: true
        agent_id:
          type: string
        pinned:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        messages:
          type: array
          items:
            type: object
            properties:
              role:
                type: string
                enum: [user, assistant, system]
              content:
                type: string
              created_at:
                type: string
                format: date-time

    Idea:
      type: object
      properties:
        id:
          type: string
        title:
          type: string
        description:
          type: string
          nullable: true
        status:
          type: string
          enum: [generated, approve_to_create, ready_for_review, change_idea, change_video, rejected, approved_to_post, posted]
        agent_id:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string
          description: Human-readable error message.
        error_code:
          type: string
          description: Machine-readable error code.

    # ── Agent Core schemas ──────────────────────────────────
    # Flat schema — every field name mirrors the public Agent Core setup fields.
    AgentCore:
      type: object
      description: Full agent setup as returned by GET /agents/{id}/core.
      properties:
        agent_id: { type: string }
        brand_name: { type: string, nullable: true }
        description: { type: string, nullable: true }
        identity_type: { type: string, enum: [brand, character], nullable: true }
        goal: { type: string, nullable: true, description: "Comma-separated, e.g. 'growth,authority,sales'" }
        target_platforms:
          type: array
          items: { type: string }
        shortform: { type: boolean, nullable: true }
        longform: { type: boolean, nullable: true }
        onboarding_status: { type: string, nullable: true }
        keywords:
          type: array
          items: { type: string }
          description: Flat keyword list used by the monitoring cron. Auto-mirrored into `monitored` on write.
        monitored:
          type: array
          items: { $ref: '#/components/schemas/MonitoredItem' }
          description: Inspiration sources — accounts, hashtags, or keywords the agent monitors for trending content.
        linked_accounts:
          type: array
          items: { $ref: '#/components/schemas/LinkedAccount' }
          description: The agent's OWN brand social links. NOT the same as `monitored` (which holds sources to watch).
        personality:
          type: string
          nullable: true
          description: Full persona text for the agent.
        default_user_voice:
          $ref: '#/components/schemas/VoiceRef'

    AgentCorePatch:
      type: object
      additionalProperties: false
      description: |
        Flat merge-patch body. Send any subset of fields — missing fields are not forwarded.
        Unknown fields return 422. `linked_accounts` and `monitored` use full-list replacement
        semantics: rows not in the list are deleted. To add without touching others, GET first,
        append, then PATCH.
      properties:
        brand_name: { type: string, maxLength: 200 }
        description:
          type: string
          maxLength: 500
          description: Short 2-3 sentence brand summary. NOT full persona — that goes in `personality`.
        identity_type: { type: string, enum: [brand, character] }
        goal: { type: string, maxLength: 100 }
        target_platforms:
          type: array
          items: { type: string }
          maxItems: 10
        shortform: { type: boolean }
        longform: { type: boolean }
        onboarding_status: { type: string, maxLength: 50 }
        keywords:
          type: array
          items: { type: string }
          maxItems: 20
          description: Flat keyword list. If set without `monitored`, auto-mirrored into monitored with item_type='keyword'.
        monitored:
          type: array
          items: { $ref: '#/components/schemas/MonitoredItem' }
          maxItems: 50
          description: Full inspiration source list (replaces the existing list).
        linked_accounts:
          type: array
          items: { $ref: '#/components/schemas/LinkedAccount' }
          maxItems: 20
        personality:
          type: string
          maxLength: 20000
          description: Full persona text for the agent.
        default_user_voice:
          $ref: '#/components/schemas/VoiceRef'

    MonitoredItem:
      type: object
      additionalProperties: false
      required: [handle, item_type]
      properties:
        handle:
          type: string
          minLength: 1
          maxLength: 300
          description: For accounts — a @handle or URL. For hashtags — the tag (with or without #). For keywords — the search term.
        item_type:
          type: string
          enum: [account, hashtag, keyword]

    LinkedAccount:
      type: object
      additionalProperties: false
      required: [url]
      properties:
        id:
          type: integer
          description: Row id if updating an existing row. Omit when adding a new account.
        url: { type: string, minLength: 1, maxLength: 500 }
        platform:
          type: string
          maxLength: 50
          description: 'tiktok | instagram | youtube | x | linkedin | pinterest | facebook | threads | website | other'
        display_name: { type: string, maxLength: 200 }

    VoiceRef:
      type: object
      additionalProperties: false
      required: [voice_id]
      properties:
        voice_id: { type: string, minLength: 1, maxLength: 200 }
        source:
          type: string
          enum: [public, user_designed, user_trained, user_elevenlabs, eleven_labs]
        name: { type: string, maxLength: 200 }

    AgentCorePatchResult:
      type: object
      description: |
        Per-section result. A PATCH to Agent Core may update multiple sections atomically.
        Section keys currently returned:
          - `brand` — brand fields (brand_name, description, identity_type, goal,
            target_platforms, shortform, longform, onboarding_status, keywords, monitored,
            linked_accounts). Clients should treat section keys as opaque strings.
          - `personality` — personality text.
          - `voice` — voice association.
        Each section appears only if the PATCH touched at least one field it owns.
        `status=ok` means that section succeeded. `status=error` means it failed
        (see `error` field). A 207 Multi-Status means at least one section errored.
      additionalProperties:
        type: object
        properties:
          status: { type: string, enum: [ok, error] }
          data:
            description: Target-specific payload on success.
          error:
            type: string
            description: Error message on failure.

    VoiceLibraryItem:
      type: object
      properties:
        voice_id: { type: string }
        name: { type: string, nullable: true }
        source: { type: string, enum: [public, user_designed, user_trained, user_elevenlabs] }
        preview_url: { type: string, nullable: true }

  responses:
    Unauthorized:
      description: Missing or invalid authentication.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "Unauthorized"
            error_code: "unauthorized"

    NotFound:
      description: Resource not found.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "Not found"
            error_code: "not_found"

    CreditRequired:
      description: Active GEN credits required.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "GEN credits are required to use the API."
            error_code: "usable_gen_credit_required"
