openapi: 3.0.3
info:
  title: MEETOVO Campaign API
  description: |
    API for AI-based campaign generation and analytics retrieval.
    
    ## Authentication
    
    All endpoints require a Bearer token in the Authorization header:
    ```
    Authorization: Bearer mk_live_xxxxxxxx...
    ```
    
    API access requires a separate subscription. Once you have booked API access, your API key is available in the Dashboard under **Connect → API**.
  version: "1.0.0"
  contact:
    name: Support
    email: support@meetovo.de
    url: https://meetovo.de
  x-logo:
    url: 'https://cdn.prod.website-files.com/67c5b7e8f78d4d305166736d/67c5bacaee54e5481cbdecc5_Logo.svg'
    altText: 'MEETOVO'
    href: 'https://meetovo.de'

servers:
  - url: https://app.meetovo.de
    description: Production

tags:
  - name: Campaigns
    description: Campaign generation and management
  - name: Companies
    description: |
      Reusable company profiles.
      
      Profiles are automatically created when generating campaigns with inline `companyDetail`.
      Use `companyId` in subsequent campaigns to reuse the same profile.
  - name: Jobs
    description: |
      Reusable job profiles.
      
      Profiles are automatically created when generating campaigns with inline `jobInformation`.
      Use `jobId` in subsequent campaigns to reuse the same profile.
  - name: Categories
    description: Job categories and subcategories
  - name: Analytics
    description: Campaign metrics and statistics
  - name: Uploads
    description: File uploads
  - name: Rate Limits
    x-traitTag: true
    description: |
      All endpoints are rate limited to protect the API from abuse.
      
      | Endpoint | Limit |
      |----------|-------|
      | `GET /campaigns` | 60 requests/min |
      | `POST /campaigns/generate` | 10 requests/min |
      | `GET /campaigns/requests/{id}/status` | 60 requests/min |
      | `GET /campaigns/{id}/analytics` | 30 requests/min |
      | `POST /uploads/image` | 20 requests/min |
      | `GET /companies` | 60 requests/min |
      | `GET /companies/{id}` | 60 requests/min |
      | `GET /jobs` | 60 requests/min |
      | `GET /jobs/{id}` | 60 requests/min |
      
      ### Response Headers
      
      Rate limit info is included in all responses:
      
      | Header | Description |
      |--------|-------------|
      | `X-RateLimit-Limit` | Maximum requests per minute |
      | `X-RateLimit-Remaining` | Remaining requests in current window |
      | `Retry-After` | Seconds until reset (only on 429) |
  - name: Webhooks
    x-traitTag: true
    description: |
      When you provide a `callbackUrl` in your campaign generation request, MEETOVO will send webhook notifications about the campaign status.
      
      ### Events
      
      | Event | Description |
      |-------|-------------|
      | `campaign.completed` | Campaign was successfully generated |
      | `campaign.partially_created` | Campaign created but needs manual review |
      | `campaign.failed` | Campaign generation failed |
      
      ### Signature Verification
      
      If you provide a `callbackSecret`, all webhooks include a signature header:
      
      ```
      X-MEETOVO-Signature: sha256=<hmac_hex>
      ```
      
      **Verification (Node.js):**
      ```javascript
      const crypto = require('crypto');
      
      function verifyWebhook(payload, signature, secret) {
        const expected = 'sha256=' + crypto
          .createHmac('sha256', secret)
          .update(JSON.stringify(payload))
          .digest('hex');
        return crypto.timingSafeEqual(
          Buffer.from(signature),
          Buffer.from(expected)
        );
      }
      ```
      
      ### Retry Policy
      
      | Attempt | Delay |
      |---------|-------|
      | 1 | Immediate |
      | 2 | 1 minute |
      | 3 | 5 minutes |
      | 4 | 30 minutes |
      | 5 | 2 hours |
      
      A webhook is considered successful if your endpoint returns `2xx` within 30 seconds.

security:
  - BearerAuth: []

paths:
  /api/v1/campaigns:
    get:
      tags:
        - Campaigns
      summary: List Campaigns
      description: Returns a paginated list of all campaigns for your account.
      operationId: listCampaigns
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
          description: Number of results per page
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
            minimum: 0
          description: Pagination offset
        - name: status
          in: query
          schema:
            type: string
            enum: [DRAFT, PENDING_PUBLICATION, PUBLISHING_IN_PROGRESS, PUBLISHING_FAILED, ACTIVE, TERMINATED]
          description: Filter by campaign status
        - name: companyId
          in: query
          schema:
            type: string
          description: Filter by company profile ID
        - name: jobId
          in: query
          schema:
            type: string
          description: Filter by job profile ID
        - name: createdAfter
          in: query
          schema:
            type: string
            format: date
          description: Filter campaigns created after this date (ISO 8601)
        - name: createdBefore
          in: query
          schema:
            type: string
            format: date
          description: Filter campaigns created before this date (ISO 8601)
      responses:
        '200':
          description: Campaigns retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignListResponse'
              example:
                success: true
                campaigns:
                  - id: "01HQJK7YPVS1M2N3P4Q5R6S7T8"
                    externalId: "job-2026-001"
                    companyId: "01HQLNP2QRS7TUV890WXYZ123"
                    jobId: "01HQK8M9XYZABC123DEF456GH"
                    status: "ACTIVE"
                    campaignUrl: "https://jobs.meetovo.de/f/senior-developer-berlin"
                    previewUrl: "https://jobs.meetovo.de/f/senior-developer-berlin?tracking-deaktiviert=true"
                    dashboardUrl: "https://app.meetovo.de/campaign/01HQJK7YPVS1M2N3P4Q5R6S7T8/edit"
                    createdAt: "2026-01-30T10:00:00Z"
                  - id: "01HQJK6XNTR0L1M2N3P4Q5R6S7"
                    externalId: "job-2026-002"
                    companyId: "01HQLNP2QRS7TUV890WXYZ123"
                    jobId: "01HQK8L8WXYABC123DEF456GI"
                    status: "DRAFT"
                    campaignUrl: "https://jobs.meetovo.de/f/product-manager-munich"
                    previewUrl: "https://jobs.meetovo.de/f/product-manager-munich?tracking-deaktiviert=true"
                    dashboardUrl: "https://app.meetovo.de/campaign/01HQJK6XNTR0L1M2N3P4Q5R6S7/edit"
                    createdAt: "2026-01-29T15:30:00Z"
                pagination:
                  total: 47
                  limit: 20
                  offset: 0
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'

  /api/v1/campaigns/generate:
    post:
      tags:
        - Campaigns
      summary: Generate Campaign
      description: |
        Creates a new campaign based on the provided data. 
        Fields that are not provided will be generated by AI.
        
        The response is returned immediately with a `requestId`. 
        Once generation is complete, a webhook is sent to your `callbackUrl`.
      operationId: generateCampaign
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GenerateCampaignRequest'
            example:
              callbackUrl: "https://customer.example.com/webhook/campaign-ready"
              callbackSecret: "whsec_abc123secret456"
              externalId: "job-2026-001"
              campaign:
                title: "2026-001 - Senior Developer - TechCorp GmbH"
              jobInformation:
                title: "Senior Frontend Developer (m/f/d)"
                category: "IT"
                subCategory: "Frontend"
                responsibilities:
                  - "Lead frontend architecture decisions"
                  - "Mentor junior developers"
                  - "Implement new features in React"
                jobCondition:
                  name: "Standard"
                  employmentType: "Full-time"
                  workPlaceModel: "HYBRID"
                  salary: [65000, 85000]
                  salaryPlan: "YEARLY"
                  salaryEnabled: true
                  occupancyFrequency: 40
              companyDetail:
                name: "TechCorp GmbH"
                ownerName: "Dr. Maria Schmidt"
                description: "We are a leading software company specializing in cloud solutions for enterprise clients."
                benefits: ["30 days vacation", "home office", "company car", "gym membership", "annual bonus"]
                uniqueSellingPoint: "Work on cutting-edge AI projects with a world-class team"
                values: ["Innovation", "Transparency", "Work-Life-Balance", "Continuous Learning"]
                link: "https://techcorp.de"
                logo: "https://techcorp.de/assets/logo.png"
                imprint: "https://techcorp.de/impressum"
                privacyPolicy: "https://techcorp.de/datenschutz"
                employees:
                  - fullName: "Anna Müller"
                    email: "jobs@techcorp.de"
                    phone: "+49 30 123456789"
                    position: "Head of Recruiting"
                    isContactPerson: true
                    imageUrl: "https://techcorp.de/team/anna-mueller.jpg"
                  - fullName: "Max Weber"
                    position: "Lead Developer"
                    quote: "The best team I've ever worked with. We ship great products and have fun doing it!"
                    imageUrl: "https://techcorp.de/team/max-weber.jpg"
                location:
                  name: "Headquarters Berlin"
                  isPrimary: true
                  address:
                    street: "Friedrichstraße"
                    houseNumber: "123"
                    postalCode: "10117"
                    city: "Berlin"
                    country: "Germany"
                    locality: "Mitte"
                    lat: 52.520008
                    lng: 13.404954
                images:
                  - url: "https://techcorp.de/images/team-event.jpg"
                    name: "Annual team retreat 2023"
                    type: "TEAM_CULTURE"
                  - url: "https://techcorp.de/images/office.jpg"
                    name: "Our modern office in Berlin"
                    type: "EVERYDAY_WORK"
                colorTheme:
                  headlineColor: "#1a1a2e"
                  textColor: "#4a4a4a"
                  accentColor: "#6366f1"
                  accentColorContrast: "#ffffff"
                  backgroundColor: "#f8fafc"
                  cardBackgroundColor: "#ffffff"
                  cardIconColor: "#6366f1"
                  cardTextColor: "#374151"
              targetAudience:
                age: "26to34"
                experience: 5
                experienceDuration: "YEARS"
                education:
                  - "bachelor degree"
                  - "professional certificate"
                language: "GERMAN"
                goals:
                  - "Technical leadership"
                  - "Working with modern technologies"
                  - "Good work-life balance"
                painPoints:
                  - "Stuck with legacy code and outdated frameworks"
                  - "No career growth opportunities"
                  - "Poor management and unclear direction"
                skills:
                  - "React/Vue.js expertise"
                  - "TypeScript"
                  - "Team leadership"
              communication:
                speech: "Duzen"
                writingStyle: "Casual"
              campaignSettings:
                maxQuestions: 6
                slug: "senior-developer-berlin"
                channels: [META, GOOGLE_JOBS]
                disableMessages: true
      responses:
        '202':
          description: Campaign generation accepted and processing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenerateCampaignResponse'
              example:
                success: true
                requestId: "req_abc123xyz789"
                status: "PROCESSING"
                message: "Campaign generation started. You will be notified via webhook when ready."
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientAiCredits'
        '409':
          description: Duplicate externalId
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error:
                  code: "DUPLICATE_EXTERNAL_ID"
                  message: "A campaign with externalId 'job-2026-001' already exists"
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
      callbacks:
        campaignCompleted:
          '{$request.body#/callbackUrl}':
            post:
              summary: Campaign Completed Webhook
              description: Sent when campaign generation completes successfully
              requestBody:
                required: true
                content:
                  application/json:
                    schema:
                      $ref: '#/components/schemas/WebhookCampaignCompleted'
              responses:
                '200':
                  description: Webhook received successfully
        campaignPartiallyCreated:
          '{$request.body#/callbackUrl}':
            post:
              summary: Campaign Partially Created Webhook
              description: Sent when campaign is created but needs manual review
              requestBody:
                required: true
                content:
                  application/json:
                    schema:
                      $ref: '#/components/schemas/WebhookCampaignPartiallyCreated'
              responses:
                '200':
                  description: Webhook received successfully
        campaignFailed:
          '{$request.body#/callbackUrl}':
            post:
              summary: Campaign Failed Webhook
              description: Sent when campaign generation fails
              requestBody:
                required: true
                content:
                  application/json:
                    schema:
                      $ref: '#/components/schemas/WebhookCampaignFailed'
              responses:
                '200':
                  description: Webhook received successfully

  /api/v1/campaigns/requests/{requestId}/status:
    get:
      tags:
        - Campaigns
      summary: Get Generation Status
      description: |
        Retrieves the current status of a generation request.
        Useful if the webhook fails or for polling.
      operationId: getCampaignStatus
      parameters:
        - name: requestId
          in: path
          required: true
          schema:
            type: string
          description: The request ID returned from the generate endpoint
          example: "req_abc123xyz789"
      responses:
        '200':
          description: Status retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignStatusResponse'
              example:
                requestId: "req_abc123xyz789"
                externalId: "job-2026-001"
                status: "COMPLETED"
                data:
                  id: "01HQJK7YPVS1M2N3P4Q5R6S7T8"
                  campaignUrl: "https://jobs.meetovo.de/f/senior-developer-berlin"
                  previewUrl: "https://jobs.meetovo.de/f/senior-developer-berlin?tracking-deaktiviert=true"
                  createdAt: "2026-01-30T10:30:00Z"
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'

  /api/v1/campaigns/{campaignId}/analytics:
    get:
      tags:
        - Analytics
      summary: Get Campaign Analytics
      description: Retrieves metrics for a campaign within a specified date range.
      operationId: getCampaignAnalytics
      parameters:
        - name: campaignId
          in: path
          required: true
          schema:
            type: string
          description: The campaign ID
          example: "01HQJK7YPVS1M2N3P4Q5R6S7T8"
        - name: startDate
          in: query
          required: true
          schema:
            type: string
            format: date
          description: Start date (ISO 8601)
          example: "2026-01-01"
        - name: endDate
          in: query
          required: true
          schema:
            type: string
            format: date
          description: End date (ISO 8601)
          example: "2026-01-31"
      responses:
        '200':
          description: Analytics retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnalyticsResponse'
              example:
                id: "01HQJK7YPVS1M2N3P4Q5R6S7T8"
                period:
                  start: "2026-01-01"
                  end: "2026-01-31"
                summary:
                  totalViews: 1250
                  totalApplications: 87
                  conversionRate: 6.96
                  averageTimeSeconds: 145
                pageStats:
                  - stepIndex: 0
                    stepName: "Start Page"
                    views: 1250
                    dropOffCount: 420
                    dropOffRate: 33.6
                    averageTimeSeconds: 25
                  - stepIndex: 1
                    stepName: "Experience Question"
                    views: 830
                    dropOffCount: 180
                    dropOffRate: 21.7
                    averageTimeSeconds: 35
                trafficSources:
                  - source: "facebook"
                    count: 620
                    percentage: 49.6
                  - source: "instagram"
                    count: 380
                    percentage: 30.4
                  - source: "direct"
                    count: 250
                    percentage: 20.0
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'

  /api/v1/uploads/image:
    post:
      tags:
        - Uploads
      summary: Upload Image
      description: |
        Uploads an image and returns a URL that can be used in campaign requests.
        
        **Limits:**
        - Max file size: 5 MB
        - Allowed formats: JPG, PNG, WebP
      operationId: uploadImage
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: Image file (JPG, PNG, WebP). Max 5 MB.
                tag:
                  type: string
                  description: Optional tag to associate the image (free text)
                  example: "company-logo"
      responses:
        '200':
          description: Image uploaded successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadResponse'
              example:
                success: true
                url: "https://cdn.meetovo.de/uploads/abc123def456.jpg"
                filename: "abc123def456.jpg"
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'

  # ==================
  # Company Profiles
  # ==================

  /api/v1/companies:
    get:
      tags:
        - Companies
      summary: List Company Profiles
      description: |
        Returns all company profiles for your account.
        
        Profiles are automatically created when you generate a campaign with inline `companyDetail`.
        You can reuse these profiles in future campaigns by providing `companyId`.
      operationId: listCompanies
      responses:
        '200':
          description: Company profiles retrieved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompanyListResponse'
              example:
                success: true
                companies:
                  - id: "01HQLNP2QRS7TUV890WXYZ123"
                    name: "TechCorp GmbH"
                    link: "https://techcorp.de"
                    createdAt: "2026-01-30T10:00:00Z"
                  - id: "01HQLNP3RST8UVW901XYZA234"
                    name: "StartupXYZ"
                    link: "https://startupxyz.de"
                    createdAt: "2026-01-29T15:30:00Z"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'

  /api/v1/companies/{companyId}:
    get:
      tags:
        - Companies
      summary: Get Company Profile
      description: Retrieves a specific company profile by ID.
      operationId: getCompany
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: string
          description: Company profile ID
          example: "01HQLNP2QRS7TUV890WXYZ123"
      responses:
        '200':
          description: Company profile retrieved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompanyDetailResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'

  # ==================
  # Job Profiles
  # ==================

  /api/v1/jobs:
    get:
      tags:
        - Jobs
      summary: List Job Profiles
      description: |
        Returns all job profiles for your account.
        
        Profiles are automatically created when you generate a campaign with inline `jobInformation`.
        You can reuse these profiles in future campaigns by providing `jobId`.
      operationId: listJobs
      responses:
        '200':
          description: Job profiles retrieved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobListResponse'
              example:
                success: true
                jobs:
                  - id: "01HQK8M9XYZABC123DEF456GH"
                    title: "Senior Frontend Developer (m/f/d)"
                    category: "IT & Software Development"
                    createdAt: "2026-01-30T10:00:00Z"
                  - id: "01HQK8L8WXYABC123DEF456GI"
                    title: "Product Manager"
                    category: "Management"
                    createdAt: "2026-01-29T15:30:00Z"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'

  /api/v1/jobs/{jobId}:
    get:
      tags:
        - Jobs
      summary: Get Job Profile
      description: Retrieves a specific job profile by ID.
      operationId: getJob
      parameters:
        - name: jobId
          in: path
          required: true
          schema:
            type: string
          description: Job profile ID
          example: "01HQK8M9XYZABC123DEF456GH"
      responses:
        '200':
          description: Job profile retrieved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobDetailResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'

  # ==================
  # Categories
  # ==================

  /api/v1/categories:
    get:
      tags:
        - Categories
      summary: List Job Categories
      description: |
        Returns all available job categories and their subcategories.

        Use these values for the `category` and `subCategory` fields in `jobInformation` when generating campaigns.
      operationId: listCategories
      responses:
        '200':
          description: Categories retrieved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CategoryListResponse'
              example:
                success: true
                categories:
                  - category: "IT"
                    subCategories:
                      - "Frontend"
                      - "Backend"
                      - "DevOps"
                  - category: "Marketing"
                    subCategories:
                      - "Online Marketing"
                      - "Content"
                      - "SEO"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: API key in format `mk_live_xxxxxxxx...`

  responses:
    Unauthorized:
      description: Invalid or missing API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: "UNAUTHORIZED"
              message: "Invalid or missing API key"

    ValidationError:
      description: Invalid request data
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: "VALIDATION_ERROR"
              message: "externalId is required"

    RateLimited:
      description: Too many requests
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: "RATE_LIMITED"
              message: "Rate limit exceeded. Please try again later."
      headers:
        X-RateLimit-Limit:
          description: Request limit per minute (varies by endpoint)
          schema:
            type: integer
            example: 10
        X-RateLimit-Remaining:
          description: Remaining requests in current window
          schema:
            type: integer
            example: 0
        Retry-After:
          description: Seconds until rate limit resets
          schema:
            type: integer
            example: 45

    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: "NOT_FOUND"
              message: "Resource not found"

    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: "INTERNAL_ERROR"
              message: "An unexpected error occurred. Please try again later."


    InsufficientAiCredits:
      description: AI token quota exhausted
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: "INSUFFICIENT_AI_CREDITS"
              message: "Your AI token quota has been exhausted. Please recharge your AI tokens to continue generating campaigns."

  schemas:
    ErrorResponse:
      type: object
      required:
        - success
        - error
      properties:
        success:
          type: boolean
          description: Always `false` for error responses
          example: false
        error:
          type: object
          description: Error details
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Machine-readable error code
              enum:
                - UNAUTHORIZED
                - VALIDATION_ERROR
                - NOT_FOUND
                - RATE_LIMITED
                - INSUFFICIENT_AI_CREDITS
                - INTERNAL_ERROR
              example: "VALIDATION_ERROR"
            message:
              type: string
              example: "Invalid request data"

    GenerateCampaignRequest:
      type: object
      description: |
        Request to generate a new campaign.
        
        **Company data:** Provide either:
        - `companyId` - Reuse an existing company profile
        - `companyDetail` - Create a new profile (ID returned in response)
        
        **Job data:** Provide either:
        - `jobId` - Reuse an existing job profile
        - `jobInformation` - Create a new profile (ID returned in response)
      required:
        - externalId
      properties:
        callbackUrl:
          type: string
          format: uri
          description: Webhook URL for notification after completion. If not provided, no webhook will be triggered.
          example: "https://your-app.com/webhooks/meetovo"
        callbackSecret:
          type: string
          description: Secret for webhook signature (HMAC-SHA256). We'll use this to sign the webhook payload.
          example: "whsec_your_secret_key_here"
        externalId:
          type: string
          description: Your internal ID for association. Use this to match campaigns to your system.
          example: "job-2026-00123"
        campaign:
          $ref: '#/components/schemas/Campaign'
        companyId:
          type: string
          description: ID of an existing company profile. Use this to reuse a profile from a previous campaign.
          example: "01HQLNP2QRS7TUV890WXYZ123"
        companyDetail:
          $ref: '#/components/schemas/CompanyDetail'
        jobId:
          type: string
          description: ID of an existing job profile. Use this to reuse a profile from a previous campaign.
          example: "01HQK8M9XYZABC123DEF456GH"
        jobInformation:
          $ref: '#/components/schemas/JobInformation'
        targetAudience:
          $ref: '#/components/schemas/TargetAudience'
        communication:
          $ref: '#/components/schemas/Communication'
        campaignSettings:
          $ref: '#/components/schemas/CampaignSettings'

    Campaign:
      type: object
      description: Basic campaign settings
      properties:
        title:
          type: string
          description: Campaign title for internal identification (free text)
          example: "2026-001 - Senior Developer - TechCorp GmbH"

    JobInformation:
      type: object
      description: Information about the job position
      required:
        - title
      properties:
        title:
          type: string
          description: Job title as shown to candidates. Used by AI to generate job details and target audience.
          example: "Senior Frontend Developer (m/f/d)"
        category:
          type: string
          description: Main job category
          example: "IT"
        subCategory:
          type: string
          description: Job sub-category
          example: "Frontend"
        responsibilities:
          type: array
          items:
            type: string
          description: List of job responsibilities
          example:
            - "Lead frontend architecture decisions"
            - "Mentor junior developers"
            - "Implement new features in React"
            - "Conduct code reviews"
        jobCondition:
          $ref: '#/components/schemas/JobCondition'

    JobCondition:
      type: object
      description: Employment conditions and contract details
      required:
        - employmentType
      properties:
        name:
          type: string
          description: Name/label for this condition set
          example: "Standard"
        startDate:
          type: string
          format: date
          description: Earliest start date (ISO 8601)
          example: "2026-03-01"
        endDate:
          type: string
          format: date
          description: Contract end date for temporary positions (ISO 8601). If not set, position is permanent.
          example: "2027-02-28"
        employmentType:
          type: string
          description: Type of employment
          example: "Full-time"
          enum: [Full-time, Part-time, Mini-job, Working student, Internship, Apprenticeship]
        workPlaceModel:
          type: string
          description: Where the work takes place
          enum: [ONSITE, REMOTE, HYBRID]
          example: "HYBRID"
        salary:
          type: array
          items:
            type: number
          description: "Salary range as [minimum, maximum]"
          example: [65000, 85000]
        salaryPlan:
          type: string
          description: Salary payment frequency
          enum: [MONTHLY, YEARLY]
          example: "YEARLY"
        salaryEnabled:
          type: boolean
          description: Whether to display salary in the campaign
          example: true
        occupancyFrequency:
          type: number
          description: Working hours per week
          example: 40

    CompanyDetail:
      type: object
      description: Company information and branding
      required:
        - link
      properties:
        name:
          type: string
          description: Company name
          example: "TechCorp GmbH"
        ownerName:
          type: string
          description: Owner or CEO name
          example: "Dr. Maria Schmidt"
        description:
          type: string
          description: Company description (who you are, what you do)
          example: "We are a leading software company specializing in cloud solutions for enterprise clients. Founded in 2015, we now have 150+ employees across 3 locations."
        benefits:
          type: array
          items:
            type: string
          description: Employee benefits
          example: ["30 days vacation", "home office", "company car", "gym membership", "annual bonus", "free lunch"]
        uniqueSellingPoint:
          type: string
          description: What makes your company special for candidates
          example: "Work on cutting-edge AI projects with a world-class team in a modern office"
        values:
          type: array
          items:
            type: string
          description: Company values
          example: ["Innovation", "Transparency", "Work-Life-Balance", "Continuous Learning"]
        link:
          type: string
          format: uri
          description: Company website URL. Used by AI to scrape company information and fill missing fields.
          example: "https://techcorp.de"
        logo:
          type: string
          format: uri
          description: "Company logo URL (recommended: PNG/SVG, min 200x200px)"
          example: "https://techcorp.de/assets/logo.png"
        imprint:
          type: string
          format: uri
          description: URL to legal imprint page (required in Germany)
          example: "https://techcorp.de/impressum"
        privacyPolicy:
          type: string
          format: uri
          description: URL to privacy policy page
          example: "https://techcorp.de/datenschutz"
        employees:
          type: array
          items:
            $ref: '#/components/schemas/Employee'
          description: |
            Employees for testimonials and contact person.
            
            At least one employee should have `isContactPerson: true` with email and phone.
        location:
          $ref: '#/components/schemas/Location'
        images:
          type: array
          items:
            $ref: '#/components/schemas/CompanyImage'
          description: |
            Company images for the campaign.
            
            **Required types (at least one image of each):**
            - `EVERYDAY_WORK` - Daily work environment
            - `TEAM_CULTURE` - Team culture/events
          minItems: 2
        colorTheme:
          $ref: '#/components/schemas/ColorTheme'

    Employee:
      type: object
      description: |
        Employee for testimonials or as contact person.
        
        If `isContactPerson` is true, `email` and `phone` are required.
      properties:
        fullName:
          type: string
          description: Employee's full name
          example: "Max Weber"
        email:
          type: string
          format: email
          description: Email address (required if isContactPerson is true)
          example: "max.weber@techcorp.de"
        phone:
          type: string
          description: Phone number (required if isContactPerson is true)
          example: "+49 30 123456789"
        position:
          type: string
          description: Employee's position
          example: "Lead Developer"
        quote:
          type: string
          description: Testimonial quote from the employee
          example: "The best team I've ever worked with. We ship great products and have fun doing it!"
        isContactPerson:
          type: boolean
          description: Whether this employee is the primary contact for candidates
          default: false
          example: false
        imageUrl:
          type: string
          format: uri
          description: Employee's photo URL
          example: "https://techcorp.de/team/max-weber.jpg"

    Location:
      type: object
      description: Company location/office
      properties:
        name:
          type: string
          description: Location name
          example: "Headquarters Berlin"
        address:
          $ref: '#/components/schemas/Address'
        isPrimary:
          type: boolean
          description: Whether this is the primary/main location
          default: true
          example: true

    Address:
      type: object
      description: Physical address
      properties:
        street:
          type: string
          description: Street name
          example: "Friedrichstraße"
        houseNumber:
          type: string
          description: House/building number
          example: "123"
        postalCode:
          type: string
          description: Postal/ZIP code
          example: "10117"
        city:
          type: string
          description: City name
          example: "Berlin"
        country:
          type: string
          description: Country name
          example: "Germany"
        locality:
          type: string
          description: District/neighborhood within the city
          example: "Mitte"
        lat:
          type: number
          format: double
          description: Latitude for map display
          example: 52.520008
        lng:
          type: number
          format: double
          description: Longitude for map display
          example: 13.404954

    CompanyImage:
      type: object
      description: Company image for the campaign.
      required:
        - url
        - type
      properties:
        url:
          type: string
          format: uri
          description: Image URL
          example: "https://techcorp.de/images/office-space.jpg"
        name:
          type: string
          description: Image caption/alt text
          example: "Our modern open-plan office in Berlin"
        type:
          type: string
          description: |
            Image category:
            - `EVERYDAY_WORK` - Daily work, office environment
            - `TEAM_CULTURE` - Team events, company culture
          enum: [EVERYDAY_WORK, TEAM_CULTURE]
          example: "EVERYDAY_WORK"

    ColorTheme:
      type: object
      description: Custom color theme for the campaign (all values in hex format)
      properties:
        headlineColor:
          type: string
          description: Color for headlines
          example: "#1a1a2e"
        textColor:
          type: string
          description: Color for body text
          example: "#4a4a4a"
        accentColor:
          type: string
          description: Primary accent color (buttons, links)
          example: "#6366f1"
        accentColorContrast:
          type: string
          description: Text color on accent backgrounds
          example: "#ffffff"
        backgroundColor:
          type: string
          description: Page background color
          example: "#f8fafc"
        cardBackgroundColor:
          type: string
          description: Card/section background color
          example: "#ffffff"
        cardIconColor:
          type: string
          description: Icon color in cards
          example: "#6366f1"
        cardTextColor:
          type: string
          description: Text color in cards
          example: "#374151"
        secondaryColor:
          type: string
          description: Secondary accent color
          example: "#818cf8"
        defaultGradient:
          type: string
          description: Default gradient for backgrounds
          example: "linear-gradient(135deg, #6366f1 0%, #818cf8 100%)"

    TargetAudience:
      type: object
      description: Definition of the ideal candidate
      properties:
        age:
          type: string
          description: |
            Target age range. Only the following values are allowed:

            | Value | Description |
            |-------|-------------|
            | `lessThan25` | Under 25 years |
            | `26to34` | 26 to 34 years |
            | `35to44` | 35 to 44 years |
            | `45to54` | 45 to 54 years |
            | `55plus` | 55 years and older |
          enum: [lessThan25, 26to34, 35to44, 45to54, 55plus]
          example: "26to34"
        experience:
          type: integer
          description: Required work experience (0-24)
          minimum: 0
          maximum: 24
          example: 5
        experienceDuration:
          type: string
          description: Unit for experience requirements
          enum: [MONTHS, YEARS]
          example: "YEARS"
        education:
          type: array
          items:
            type: string
            enum: [high school, associate degree, bachelor degree, professional certificate, postgraduate degree]
          uniqueItems: true
          description: |
            Required education levels (multi-select).

            | Value | Description |
            |-------|-------------|
            | `high school` | Schule |
            | `associate degree` | Fachschule |
            | `bachelor degree` | Studium |
            | `professional certificate` | Ausbildung |
            | `postgraduate degree` | Promoviert |
          example: ["bachelor degree", "professional certificate"]
        language:
          type: string
          description: Required language
          enum: [GERMAN, ENGLISH, FRENCH, SPANISH, ITALIAN, DUTCH, PORTUGUESE, POLISH, TURKISH, RUSSIAN]
          example: "GERMAN"
        goals:
          type: array
          items:
            type: string
          description: Career goals of your ideal candidate
          example: ["Technical leadership", "Working with modern technologies", "Good work-life balance"]
        painPoints:
          type: array
          items:
            type: string
          description: Problems your ideal candidate faces in their current job
          example:
            - "Stuck with legacy code and outdated frameworks"
            - "No career growth opportunities"
            - "Poor management and unclear direction"
        skills:
          type: array
          items:
            type: string
          description: Desired skills and competencies
          example:
            - "React/Vue.js expertise"
            - "TypeScript"
            - "Team leadership"
            - "Agile methodologies"

    Communication:
      type: object
      description: Communication style settings
      properties:
        speech:
          type: string
          description: "Form of address: Duzen (informal 'du') or Siezen (formal 'Sie')"
          enum: [Duzen, Siezen]
          default: Duzen
          example: "Duzen"
        writingStyle:
          type: string
          description: Writing tone and style
          enum: [Neutral, Casual, Formal, Youthful, Funny, Empathetic, Visionary]
          default: Neutral
          example: "Casual"

    CampaignSettings:
      type: object
      description: Campaign configuration
      properties:
        maxQuestions:
          type: integer
          minimum: 1
          maximum: 10
          default: 5
          description: Maximum number of questions in the funnel (1-10)
          example: 6
        domain:
          type: string
          description: Your custom domain (if configured in dashboard). The campaign funnel will be published under this domain.
          example: "jobs.techcorp.de"
        slug:
          type: string
          description: URL slug for the campaign (auto-generated if not provided)
          example: "senior-developer-berlin"
        channels:
          type: array
          items:
            type: string
            enum: [META, GOOGLE_JOBS]
          default: []
          description: |
            Channels to generate campaign assets for. If empty or not provided, the campaign is created without ads.
            
            | Channel | Description |
            |---------|-------------|
            | `META` | Facebook & Instagram ads |
            | `GOOGLE_JOBS` | Google Jobs listing |
          example: [META, GOOGLE_JOBS]
        disableMessages:
          type: boolean
          default: false
          description: |
            When `true`, all automated messages (email templates and SMS templates) are disabled for the campaign.
            Defaults to `false` — set to `true` to disable automated messages.
          example: true

    GenerateCampaignResponse:
      type: object
      properties:
        success:
          type: boolean
          description: Whether the request was accepted
          example: true
        requestId:
          type: string
          description: Unique request ID for tracking
          example: "req_abc123xyz789"
        status:
          type: string
          description: Current generation status
          enum: [PENDING, PROCESSING, COMPLETED, PARTIALLY_CREATED, FAILED]
          example: "PROCESSING"
        message:
          type: string
          description: Human-readable status message
          example: "Campaign generation started. You will be notified via webhook when ready."

    CampaignListResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        campaigns:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                description: Campaign ID
                example: "01HQJK7YPVS1M2N3P4Q5R6S7T8"
              externalId:
                type: string
                description: Your external ID
                example: "job-2026-001"
              companyId:
                type: string
                description: Company profile ID
                example: "01HQLNP2QRS7TUV890WXYZ123"
              jobId:
                type: string
                description: Job profile ID
                example: "01HQK8M9XYZABC123DEF456GH"
              status:
                type: string
                enum: [DRAFT, PENDING_PUBLICATION, PUBLISHING_IN_PROGRESS, PUBLISHING_FAILED, ACTIVE, TERMINATED]
                description: Campaign status
                example: "ACTIVE"
              campaignUrl:
                type: string
                format: uri
                description: Public campaign URL
                example: "https://jobs.meetovo.de/f/senior-developer-berlin"
              previewUrl:
                type: string
                format: uri
                description: Preview URL with tracking disabled
                example: "https://jobs.meetovo.de/f/senior-developer-berlin?tracking-deaktiviert=true"
              dashboardUrl:
                type: string
                format: uri
                description: Dashboard URL for editing
                example: "https://app.meetovo.de/campaign/01HQJK7YPVS1M2N3P4Q5R6S7T8/edit"
              createdAt:
                type: string
                format: date-time
                description: Creation timestamp
                example: "2026-01-30T10:00:00Z"
        pagination:
          type: object
          properties:
            total:
              type: integer
              description: Total number of campaigns
              example: 47
            limit:
              type: integer
              description: Results per page
              example: 20
            offset:
              type: integer
              description: Current offset
              example: 0

    CampaignStatusResponse:
      type: object
      properties:
        requestId:
          type: string
          description: The request ID
          example: "req_abc123xyz789"
        externalId:
          type: string
          description: Your external ID
          example: "job-2026-001"
        status:
          type: string
          description: Current status
          enum: [PENDING, PROCESSING, COMPLETED, PARTIALLY_CREATED, FAILED]
          example: "COMPLETED"
        data:
          type: object
          nullable: true
          description: Campaign data (only present when COMPLETED or PARTIALLY_CREATED)
          properties:
            id:
              type: string
              description: Campaign ID
              example: "01HQJK7YPVS1M2N3P4Q5R6S7T8"
            companyId:
              type: string
              description: Company profile ID (reuse in future campaigns)
              example: "01HQLNP2QRS7TUV890WXYZ123"
            jobId:
              type: string
              description: Job profile ID (reuse in future campaigns)
              example: "01HQK8M9XYZABC123DEF456GH"
            campaignUrl:
              type: string
              format: uri
              description: Public campaign URL
              example: "https://jobs.meetovo.de/f/senior-developer-berlin"
            previewUrl:
              type: string
              format: uri
              description: Preview URL with tracking disabled (view as user without activating analytics)
              example: "https://jobs.meetovo.de/f/senior-developer-berlin?tracking-deaktiviert=true"
            dashboardUrl:
              type: string
              format: uri
              description: Dashboard URL for editing (only on PARTIALLY_CREATED)
              example: "https://app.meetovo.de/campaign/01HQJK7YPVS1M2N3P4Q5R6S7T8/edit"
            createdAt:
              type: string
              format: date-time
              description: Creation timestamp
              example: "2026-01-30T10:30:00Z"

    UploadResponse:
      type: object
      properties:
        success:
          type: boolean
          description: Whether the upload succeeded
          example: true
        url:
          type: string
          format: uri
          description: CDN URL of the uploaded image
          example: "https://cdn.meetovo.de/uploads/abc123def456.jpg"
        filename:
          type: string
          description: Generated filename
          example: "abc123def456.jpg"

    # ==================
    # Company Responses
    # ==================

    CompanyListResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        companies:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                example: "01HQLNP2QRS7TUV890WXYZ123"
              name:
                type: string
                example: "TechCorp GmbH"
              link:
                type: string
                format: uri
                example: "https://techcorp.de"
              createdAt:
                type: string
                format: date-time
                example: "2026-01-30T10:00:00Z"

    CompanyDetailResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        company:
          allOf:
            - type: object
              properties:
                id:
                  type: string
                  example: "01HQLNP2QRS7TUV890WXYZ123"
                createdAt:
                  type: string
                  format: date-time
                  example: "2026-01-30T10:00:00Z"
            - $ref: '#/components/schemas/CompanyDetail'

    # ==================
    # Job Responses
    # ==================

    JobListResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        jobs:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                example: "01HQK8M9XYZABC123DEF456GH"
              title:
                type: string
                example: "Senior Frontend Developer (m/f/d)"
              category:
                type: string
                example: "IT & Software Development"
              createdAt:
                type: string
                format: date-time
                example: "2026-01-30T10:00:00Z"

    JobDetailResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        job:
          allOf:
            - type: object
              properties:
                id:
                  type: string
                  example: "01HQK8M9XYZABC123DEF456GH"
                createdAt:
                  type: string
                  format: date-time
                  example: "2026-01-30T10:00:00Z"
            - $ref: '#/components/schemas/JobInformation'

    CategoryListResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        categories:
          type: array
          items:
            $ref: '#/components/schemas/CategoryItem'

    CategoryItem:
      type: object
      properties:
        category:
          type: string
          description: Main job category
          example: "IT"
        subCategories:
          type: array
          items:
            type: string
          description: Available subcategories for this category
          example: ["Frontend", "Backend", "DevOps"]

    AnalyticsResponse:
      type: object
      properties:
        id:
          type: string
          description: Campaign ID
          example: "01HQJK7YPVS1M2N3P4Q5R6S7T8"
        period:
          type: object
          description: The requested date range for analytics
          properties:
            start:
              type: string
              format: date
              description: Period start date
              example: "2026-01-01"
            end:
              type: string
              format: date
              description: Period end date
              example: "2026-01-31"
        summary:
          type: object
          description: Aggregated metrics
          properties:
            totalViews:
              type: integer
              description: Total page views
              example: 1250
            totalApplications:
              type: integer
              description: Total completed applications
              example: 87
            conversionRate:
              type: number
              format: float
              description: Conversion rate in percent
              example: 6.96
            averageTimeSeconds:
              type: integer
              description: Average time spent (seconds)
              example: 145
        pageStats:
          type: array
          description: Per-page statistics
          items:
            type: object
            properties:
              stepIndex:
                type: integer
                description: Page index (0 = start page)
                example: 0
              stepName:
                type: string
                description: Page name
                example: "Start Page"
              views:
                type: integer
                description: Page views
                example: 1250
              dropOffCount:
                type: integer
                description: Number of drop-offs
                example: 420
              dropOffRate:
                type: number
                format: float
                description: Drop-off rate in percent
                example: 33.6
              averageTimeSeconds:
                type: integer
                description: Average time on page (seconds)
                example: 25
        trafficSources:
          type: array
          description: Traffic breakdown by source
          items:
            type: object
            properties:
              source:
                type: string
                description: Traffic source name
                example: "facebook"
              count:
                type: integer
                description: Number of visitors
                example: 620
              percentage:
                type: number
                format: float
                description: Percentage of total traffic
                example: 49.6

    # ==================
    # Webhook Schemas
    # ==================

    WebhookCampaignCompleted:
      type: object
      description: Sent when campaign generation completes successfully
      properties:
        event:
          type: string
          description: The webhook event type
          enum: [campaign.completed]
          example: "campaign.completed"
        requestId:
          type: string
          description: The request ID from the generate endpoint
          example: "req_abc123xyz789"
        externalId:
          type: string
          description: Your external ID for matching
          example: "job-2026-001"
        status:
          type: string
          description: The campaign generation status
          enum: [COMPLETED]
          example: "COMPLETED"
        timestamp:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the event occurred
          example: "2026-01-30T10:30:00Z"
        data:
          type: object
          description: Campaign data
          properties:
            id:
              type: string
              description: Campaign ID
              example: "01HQJK7YPVS1M2N3P4Q5R6S7T8"
            companyId:
              type: string
              description: Company profile ID (reuse in future campaigns)
              example: "01HQLNP2QRS7TUV890WXYZ123"
            jobId:
              type: string
              description: Job profile ID (reuse in future campaigns)
              example: "01HQK8M9XYZABC123DEF456GH"
            campaignUrl:
              type: string
              format: uri
              description: Public campaign URL
              example: "https://jobs.meetovo.de/f/senior-developer-berlin"
            previewUrl:
              type: string
              format: uri
              description: Preview URL with tracking disabled (view as user without activating analytics)
              example: "https://jobs.meetovo.de/f/senior-developer-berlin?tracking-deaktiviert=true"
            dashboardUrl:
              type: string
              format: uri
              description: URL to edit the campaign in MEETOVO dashboard
              example: "https://app.meetovo.de/campaign/01HQJK7YPVS1M2N3P4Q5R6S7T8/edit"
            createdAt:
              type: string
              format: date-time
              example: "2026-01-30T10:30:00Z"
      example:
        event: "campaign.completed"
        requestId: "req_abc123xyz789"
        externalId: "job-2026-001"
        status: "COMPLETED"
        timestamp: "2026-01-30T10:30:00Z"
        data:
          id: "01HQJK7YPVS1M2N3P4Q5R6S7T8"
          companyId: "01HQLNP2QRS7TUV890WXYZ123"
          jobId: "01HQK8M9XYZABC123DEF456GH"
          campaignUrl: "https://jobs.meetovo.de/f/senior-developer-berlin"
          previewUrl: "https://jobs.meetovo.de/f/senior-developer-berlin?tracking-deaktiviert=true"
          dashboardUrl: "https://app.meetovo.de/campaign/01HQJK7YPVS1M2N3P4Q5R6S7T8/edit"
          createdAt: "2026-01-30T10:30:00Z"

    WebhookCampaignPartiallyCreated:
      type: object
      description: Sent when campaign is created but requires manual review in dashboard
      properties:
        event:
          type: string
          description: The webhook event type
          enum: [campaign.partially_created]
          example: "campaign.partially_created"
        requestId:
          type: string
          description: The request ID from the generate endpoint
          example: "req_abc123xyz789"
        externalId:
          type: string
          description: Your external ID for matching
          example: "job-2026-001"
        status:
          type: string
          description: The campaign generation status
          enum: [PARTIALLY_CREATED]
          example: "PARTIALLY_CREATED"
        timestamp:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the event occurred
          example: "2026-01-30T10:30:00Z"
        data:
          type: object
          description: Campaign data with dashboard link for manual review
          properties:
            id:
              type: string
              description: Campaign ID
              example: "01HQJK7YPVS1M2N3P4Q5R6S7T8"
            companyId:
              type: string
              description: Company profile ID (reuse in future campaigns)
              example: "01HQLNP2QRS7TUV890WXYZ123"
            jobId:
              type: string
              description: Job profile ID (reuse in future campaigns)
              example: "01HQK8M9XYZABC123DEF456GH"
            campaignUrl:
              type: string
              format: uri
              description: Public campaign URL
              example: "https://jobs.meetovo.de/f/senior-developer-berlin"
            dashboardUrl:
              type: string
              format: uri
              description: URL to edit the campaign in MEETOVO dashboard
              example: "https://app.meetovo.de/campaign/01HQJK7YPVS1M2N3P4Q5R6S7T8/edit"
            createdAt:
              type: string
              format: date-time
              example: "2026-01-30T10:30:00Z"
      example:
        event: "campaign.partially_created"
        requestId: "req_abc123xyz789"
        externalId: "job-2026-001"
        status: "PARTIALLY_CREATED"
        timestamp: "2026-01-30T10:30:00Z"
        data:
          id: "01HQJK7YPVS1M2N3P4Q5R6S7T8"
          companyId: "01HQLNP2QRS7TUV890WXYZ123"
          jobId: "01HQK8M9XYZABC123DEF456GH"
          campaignUrl: "https://jobs.meetovo.de/f/senior-developer-berlin"
          dashboardUrl: "https://app.meetovo.de/campaign/01HQJK7YPVS1M2N3P4Q5R6S7T8/edit"
          createdAt: "2026-01-30T10:30:00Z"

    WebhookCampaignFailed:
      type: object
      description: Sent when campaign generation fails
      properties:
        event:
          type: string
          description: The webhook event type
          enum: [campaign.failed]
          example: "campaign.failed"
        requestId:
          type: string
          description: The request ID from the generate endpoint
          example: "req_abc123xyz789"
        externalId:
          type: string
          description: Your external ID for matching
          example: "job-2026-001"
        status:
          type: string
          description: The campaign generation status
          enum: [FAILED]
          example: "FAILED"
        timestamp:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the event occurred
          example: "2026-01-30T10:30:00Z"
        error:
          type: object
          description: Error details explaining why the campaign generation failed
          properties:
            code:
              type: string
              description: Machine-readable error code
              enum:
                - GENERATION_FAILED
                - TIMEOUT
                - INVALID_DATA
                - SCRAPING_FAILED
                - INTERNAL_ERROR
              example: "GENERATION_FAILED"
            message:
              type: string
              description: Human-readable error message
              example: "AI generation timed out after 5 minutes"
      example:
        event: "campaign.failed"
        requestId: "req_abc123xyz789"
        externalId: "job-2026-001"
        status: "FAILED"
        timestamp: "2026-01-30T10:30:00Z"
        error:
          code: "GENERATION_FAILED"
          message: "AI generation timed out after 5 minutes"
