openapi: 3.0.3
info:
  title: esc Profile API
  description: |
    REST API for HR profile assessments - Assessment tests for recruiting and talent development.

    ## Authentication
    All authenticated endpoints require a Bearer Token in the Authorization header:
    ```
    Authorization: Bearer pk_live_xxx
    ```

    ## Rate Limiting
    - 100 requests per minute per API key
    - On exceeding: HTTP 429

    ## Workflow
    1. **View products** - `GET /products` (public)
    2. **Purchase package** - `POST /orders` (creates Stripe Checkout)
    3. **Check balance** - `GET /balance`
    4. **Invite candidate** - `POST /invites`
    5. **Get results** - `GET /invites/{id}/results` (JSON) or `GET /invites/{id}/report` (PDF)
    6. **Match with profile** - `GET /invites/{id}/match?profile_id=jp_xxx`

    ## New in v1.4
    - **Profiles**: List ALL completed assessments (API + Admin Tool)
    - **Profile Details**: Get full results by session ID

    ## New in v1.3
    - **Teams**: Group candidates for team analysis and reports
    - **Team Reports**: Generate team-wide analysis reports
    - **Team Export**: Export team data as CSV/JSON

    ## New in v1.2
    - **Search**: Find candidates across all completed assessments
    - **Job Profiles**: Create and manage ideal candidate profiles
    - **Matching**: Compare candidates against job profiles
    - **Full Results**: Complete JSON export of all dimensions
    - **Reminders**: Send up to 3 reminder emails per invite
    - **Cancel Invites**: Delete pending invites
  version: 1.4.0
  contact:
    email: ralph.koebler@escprofile.com
    url: https://www.escprofile.com
  license:
    name: Proprietary
    url: https://www.escprofile.com/terms/

servers:
  - url: https://www.escprofile.com/api/v1
    description: Production server

tags:
  - name: Discovery
    description: API overview and product catalog (public)
  - name: Registration
    description: Self-service API key registration (public)
  - name: Account
    description: Account information and settings
  - name: Orders
    description: Orders and payments
  - name: Execution
    description: Invite candidates and retrieve results
  - name: Results
    description: Assessment results and reports
  - name: Search
    description: Search across completed assessments
  - name: Invoices
    description: View and download invoices
  - name: GDPR
    description: GDPR/DSGVO compliant data deletion
  - name: Job Profiles
    description: Manage job profiles for candidate matching
  - name: Matching
    description: Compare candidates with job profiles
  - name: Webhooks
    description: Event notifications
  - name: Teams
    description: Manage candidate teams for group analysis and reports
  - name: Profiles
    description: View all completed assessments (profiles)

paths:
  /:
    get:
      tags: [Discovery]
      summary: API Information
      description: Returns API metadata and available endpoints
      operationId: getApiInfo
      responses:
        '200':
          description: API information
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiInfo'

  /request-trial:
    post:
      tags: [Registration]
      summary: Request trial access
      description: |
        Request a free trial access with 3 credits.
        The request is manually reviewed - you will receive an email with your credentials within 24 hours.

        Note: Consumer email addresses (Gmail, GMX, etc.) are not accepted.
      operationId: requestTrial
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RequestTrialRequest'
            example:
              company: "AI Startup Ltd"
              email: "dev@ai-startup.com"
              contact_name: "John Smith"
              use_case: "Integration into our ATS for automated candidate assessments"
              website: "https://ai-startup.com"
      responses:
        '200':
          description: Request received
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RequestTrialResponse'
        '400':
          description: Invalid input or consumer email
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /register:
    post:
      tags: [Registration]
      summary: Register API access
      description: |
        Self-service registration for new API customers. Creates a new customer account
        and generates an API key. The key is only shown once in the response - save it immediately!

        Optionally specify a product_id to directly create a Stripe Checkout session for purchasing credits.
      operationId: registerApiAccess
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterRequest'
            example:
              email: "developer@example.com"
              company: "Example Ltd"
              name: "John Smith"
              address: "123 Main Street"
              zip: "10001"
              city: "New York"
              country: "US"
              vat_id: "US12345678"
              billing_contact: "Accounting"
              phone: "+1 212 555 1234"
              website: "https://www.example.com"
              product_id: "pa-pack-10"
              mode: "live"
      responses:
        '201':
          description: Registration successful
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RegisterResponse'
        '400':
          description: Invalid input (email or company missing)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Email already registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /products:
    get:
      tags: [Discovery]
      summary: List products
      description: Returns available assessment products with prices (public, no authentication required)
      operationId: listProducts
      parameters:
        - name: category
          in: query
          schema:
            type: string
          description: Filter by category
        - name: max_price
          in: query
          schema:
            type: number
          description: Maximum price
        - name: min_credits
          in: query
          schema:
            type: integer
          description: Minimum credits
      responses:
        '200':
          description: Product list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductList'

  /orders:
    post:
      tags: [Orders]
      summary: Create order
      description: Creates a new order and returns the Stripe Checkout URL
      operationId: createOrder
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOrderRequest'
      responses:
        '201':
          description: Order created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /orders/{orderId}:
    get:
      tags: [Orders]
      summary: Get order status
      description: Check the status of an order
      operationId: getOrder
      security:
        - bearerAuth: []
      parameters:
        - name: orderId
          in: path
          required: true
          schema:
            type: string
          example: ord_abc123xyz
      responses:
        '200':
          description: Order details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderStatus'
        '404':
          $ref: '#/components/responses/NotFound'

  /balance:
    get:
      tags: [Execution]
      summary: Check balance
      description: Get current credit balance and package details
      operationId: getBalance
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Balance information
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BalanceResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /account:
    get:
      tags: [Account]
      summary: Get account information
      description: |
        Retrieve account details for the authenticated API customer.

        **Returns:**
        - Company and contact information
        - Billing address
        - Active API keys (prefix only, not full key)
        - Account status and type
        - Personal assessment link
      operationId: getAccount
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Account information
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccountResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /invoices:
    get:
      tags: [Invoices]
      summary: List invoices
      description: |
        List all invoices for your account.

        **Filtering:**
        - `status`: Filter by payment status (paid, unpaid, all)

        **Returns:**
        - List of invoices with amounts and payment status
        - Summary with total amounts
      operationId: listInvoices
      security:
        - bearerAuth: []
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [paid, unpaid, all]
            default: all
          description: Filter by payment status
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 100
          description: Maximum invoices to return
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
          description: Pagination offset
      responses:
        '200':
          description: Invoice list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /invoices/{invoiceId}:
    get:
      tags: [Invoices]
      summary: Get invoice details
      description: |
        Get detailed information about a specific invoice including line items.
      operationId: getInvoice
      security:
        - bearerAuth: []
      parameters:
        - name: invoiceId
          in: path
          required: true
          schema:
            type: string
          example: inv_12345
      responses:
        '200':
          description: Invoice details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceDetailResponse'
        '404':
          $ref: '#/components/responses/NotFound'

  /invoices/{invoiceId}/pdf:
    get:
      tags: [Invoices]
      summary: Download invoice PDF
      description: |
        Download the invoice as a PDF file.

        The PDF includes:
        - Company header and billing address
        - Invoice number and date
        - Line items with amounts
        - VAT calculation (if applicable)
        - Payment details
      operationId: downloadInvoicePdf
      security:
        - bearerAuth: []
      parameters:
        - name: invoiceId
          in: path
          required: true
          schema:
            type: string
          example: inv_12345
      responses:
        '200':
          description: PDF file
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        '404':
          $ref: '#/components/responses/NotFound'

  /invites:
    post:
      tags: [Execution]
      summary: Create invite
      description: |
        Invite a candidate to a profile assessment.

        **Requirements:**
        - At least 1 available credit
        - Maximum 10 pending invites at a time

        Credits are only deducted when the candidate completes the assessment.
      operationId: createInvite
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateInviteRequest'
      responses:
        '201':
          description: Invite created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InviteResponse'
        '402':
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Too many pending invites (max. 10)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    get:
      tags: [Execution]
      summary: List invites
      description: List all invites with optional filtering
      operationId: listInvites
      security:
        - bearerAuth: []
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, started, completed, expired]
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Invite list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InviteList'

  /invites/{inviteId}:
    get:
      tags: [Execution]
      summary: Get invite status
      description: Check the status of a specific invite
      operationId: getInvite
      security:
        - bearerAuth: []
      parameters:
        - name: inviteId
          in: path
          required: true
          schema:
            type: string
          example: inv_abc123xyz
      responses:
        '200':
          description: Invite details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InviteResponse'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Execution]
      summary: Cancel invite
      description: |
        Cancel a pending invite. Only invites with status "pending" can be cancelled.
        Started or completed invites cannot be cancelled.
      operationId: deleteInvite
      security:
        - bearerAuth: []
      parameters:
        - name: inviteId
          in: path
          required: true
          schema:
            type: string
          example: inv_abc123xyz
      responses:
        '200':
          description: Invite cancelled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteInviteResponse'
        '400':
          description: Cannot cancel (not pending)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'

  /invites/{inviteId}/remind:
    post:
      tags: [Execution]
      summary: Send reminder
      description: |
        Send a reminder email to the candidate.

        **Limits:**
        - Maximum 3 reminders per invite
        - Minimum 24 hours between reminders
        - Only for pending or started invites
      operationId: sendReminder
      security:
        - bearerAuth: []
      parameters:
        - name: inviteId
          in: path
          required: true
          schema:
            type: string
          example: inv_abc123xyz
      responses:
        '200':
          description: Reminder sent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReminderResponse'
        '400':
          description: Invalid status or expired
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Too many reminders or too soon
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /invites/{inviteId}/report:
    get:
      tags: [Execution]
      summary: Download PDF report
      description: |
        Download the assessment PDF report for a completed invite.

        Available report types:
        - **standard**: General profile analysis (default)
        - **leadership**: Leadership-focused analysis
        - **sales**: Sales-focused analysis
      operationId: getReport
      security:
        - bearerAuth: []
      parameters:
        - name: inviteId
          in: path
          required: true
          schema:
            type: string
          example: inv_abc123xyz
        - name: type
          in: query
          schema:
            type: string
            enum: [standard, leadership, sales]
            default: standard
          description: Report type/focus
        - name: lang
          in: query
          schema:
            type: string
            enum: [en, de]
            default: en
          description: Report language
      responses:
        '200':
          description: PDF report file
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        '400':
          description: Assessment not completed or invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'

  /invites/{inviteId}/results:
    get:
      tags: [Results]
      summary: Get full results (JSON)
      description: |
        Retrieve complete assessment results as structured JSON.
        Contains all dimension scores, Graves values, and candidate info.

        **Includes:**
        - 16 operative dimensions with raw %, c-value (0-10), and labels
        - 6 Graves values (G2-G7)
        - Wir-Ich ratio
        - Norm group information
        - Optional descriptions for each dimension
      operationId: getResultsJson
      security:
        - bearerAuth: []
      parameters:
        - name: inviteId
          in: path
          required: true
          schema:
            type: string
          example: inv_abc123xyz
        - name: include_descriptions
          in: query
          schema:
            type: boolean
            default: false
          description: Include dimension descriptions
      responses:
        '200':
          description: Full assessment results
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FullResultsResponse'
        '400':
          description: Assessment not completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'

  /invites/{inviteId}/match:
    get:
      tags: [Matching]
      summary: Match with job profile
      description: |
        Compare a candidate's assessment results with a job profile.
        Returns overall match score and per-dimension breakdown.

        **Match scores:**
        - 90-100: Excellent (green)
        - 70-89: Good (lightgreen)
        - 50-69: Acceptable/Marginal (yellow/orange)
        - 0-49: Critical (red)
      operationId: matchWithProfile
      security:
        - bearerAuth: []
      parameters:
        - name: inviteId
          in: path
          required: true
          schema:
            type: string
          example: inv_abc123xyz
        - name: profile_id
          in: query
          required: true
          schema:
            type: string
          description: Job profile ID (jp_xxx format)
          example: jp_634
        - name: include_details
          in: query
          schema:
            type: boolean
            default: false
          description: Include profile tolerance ranges
      responses:
        '200':
          description: Match results
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MatchResponse'
        '400':
          description: Assessment not completed or profile has no dimensions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'

  /results/{inviteId}:
    get:
      tags: [Results]
      summary: Get results (legacy)
      description: Retrieve assessment results for a completed invite
      operationId: getResults
      security:
        - bearerAuth: []
      parameters:
        - name: inviteId
          in: path
          required: true
          schema:
            type: string
          example: inv_abc123xyz
        - name: include_raw
          in: query
          schema:
            type: boolean
            default: false
          description: Include raw session data
      responses:
        '200':
          description: Assessment results
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FullResultsResponse'
        '202':
          description: Assessment not yet completed
        '404':
          $ref: '#/components/responses/NotFound'

  /search:
    get:
      tags: [Search]
      summary: Search assessments
      description: |
        Search across all completed assessments for your account.

        **Search options:**
        - `q`: General search across name and email
        - `name`: Search by name only
        - `email`: Search by email only
        - `date_from`/`date_to`: Filter by completion date

        At least one search parameter is required.
      operationId: searchAssessments
      security:
        - bearerAuth: []
      parameters:
        - name: q
          in: query
          schema:
            type: string
          description: General search query (searches name and email)
          example: "john"
        - name: name
          in: query
          schema:
            type: string
          description: Search by name only
        - name: email
          in: query
          schema:
            type: string
          description: Search by email only
        - name: date_from
          in: query
          schema:
            type: string
            format: date
          description: Filter by completion date (from)
          example: "2026-01-01"
        - name: date_to
          in: query
          schema:
            type: string
            format: date
          description: Filter by completion date (to)
          example: "2026-12-31"
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
          description: Maximum results to return
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
          description: Pagination offset
        - name: include_results
          in: query
          schema:
            type: boolean
            default: false
          description: Include basic dimension scores in results
      responses:
        '200':
          description: Search results
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchResponse'
        '400':
          description: Missing search query
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /job-profiles:
    get:
      tags: [Job Profiles]
      summary: List job profiles
      description: Get all job profiles for your account
      operationId: listJobProfiles
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Profile list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobProfileList'
    post:
      tags: [Job Profiles]
      summary: Create job profile
      description: |
        Create a new job profile with ideal dimension values.

        **Dimensions available:**
        - Motivation: goal_orientation, problem_solving
        - Work Style: proactive, reflective
        - Decision Making: internal, external
        - Perception: sameness, difference
        - Information: global, detail
        - Resilience: failure_tolerance, stability
        - Approach: procedural, options
        - Orientation: practical, people, process
        - Values: graves2_security through graves7_growth
      operationId: createJobProfile
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateJobProfileRequest'
      responses:
        '201':
          description: Profile created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobProfileResponse'
        '400':
          $ref: '#/components/responses/BadRequest'

  /job-profiles/{profileId}:
    get:
      tags: [Job Profiles]
      summary: Get job profile
      description: Get details of a specific job profile including all dimensions
      operationId: getJobProfile
      security:
        - bearerAuth: []
      parameters:
        - name: profileId
          in: path
          required: true
          schema:
            type: string
          example: jp_634
      responses:
        '200':
          description: Profile details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobProfileResponse'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Job Profiles]
      summary: Update job profile
      description: Update an existing job profile
      operationId: updateJobProfile
      security:
        - bearerAuth: []
      parameters:
        - name: profileId
          in: path
          required: true
          schema:
            type: string
          example: jp_634
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateJobProfileRequest'
      responses:
        '200':
          description: Profile updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobProfileResponse'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Job Profiles]
      summary: Delete job profile
      description: Delete a job profile (soft delete)
      operationId: deleteJobProfile
      security:
        - bearerAuth: []
      parameters:
        - name: profileId
          in: path
          required: true
          schema:
            type: string
          example: jp_634
      responses:
        '200':
          description: Profile deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteJobProfileResponse'
        '404':
          $ref: '#/components/responses/NotFound'

  /gdpr/candidates:
    get:
      tags: [GDPR]
      summary: List candidates for deletion
      description: |
        List all candidates eligible for GDPR-compliant data deletion.

        **Filter options:**
        - `status=active`: Candidates with personal data (default)
        - `status=deleted`: Already anonymized candidates
        - `status=all`: All candidates

        Candidates marked as "protected" cannot be deleted.
      operationId: listGdprCandidates
      security:
        - bearerAuth: []
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [active, deleted, all]
            default: active
          description: Filter by deletion status
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 100
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Candidate list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GdprCandidateListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /gdpr/candidates/{sessionId}:
    get:
      tags: [GDPR]
      summary: Get candidate deletion status
      description: Check if a candidate can be deleted and their current status
      operationId: getGdprCandidateStatus
      security:
        - bearerAuth: []
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: integer
          description: Session ID of the candidate
      responses:
        '200':
          description: Candidate status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GdprCandidateStatusResponse'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [GDPR]
      summary: Delete candidate data (GDPR)
      description: |
        Anonymize all personal data for a candidate according to GDPR requirements.

        **This action:**
        - Replaces personal data (name, email, IP) with "dsgvo" / "Anonym"
        - Removes team and job profile assignments
        - Deletes activity logs
        - Is **irreversible**

        **Prerequisites:**
        - Candidate must not be protected (`protected: false`)
        - Candidate must not already be anonymized

        Assessment results (scores, dimensions) are preserved for statistical purposes.
      operationId: deleteGdprCandidate
      security:
        - bearerAuth: []
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: integer
          description: Session ID of the candidate
      responses:
        '200':
          description: Data anonymized successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GdprDeleteResponse'
        '400':
          description: Already anonymized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Candidate is protected from deletion
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'

  /gdpr/deletions:
    get:
      tags: [GDPR]
      summary: List completed deletions
      description: |
        Audit log of all GDPR deletions performed for your account.
        Useful for compliance documentation.
      operationId: listGdprDeletions
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 100
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Deletion history
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GdprDeletionListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /webhooks:
    post:
      tags: [Webhooks]
      summary: Register webhook
      description: Register a URL to receive event notifications
      operationId: createWebhook
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWebhookRequest'
      responses:
        '201':
          description: Webhook created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookResponse'
    get:
      tags: [Webhooks]
      summary: List webhooks
      description: List all registered webhooks
      operationId: listWebhooks
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Webhook list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookList'

  /teams:
    get:
      tags: [Teams]
      summary: List teams
      description: |
        Get all teams for your account.

        **Filter options:**
        - `status=active`: Active teams (default)
        - `status=inactive`: Inactive/archived teams
        - `status=all`: All teams
      operationId: listTeams
      security:
        - bearerAuth: []
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [active, inactive, all]
            default: active
          description: Filter by team status
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 100
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Team list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TeamListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags: [Teams]
      summary: Create team
      description: |
        Create a new team for grouping candidates.

        Teams can be used to:
        - Group candidates for team analysis
        - Generate team reports (leadership, sales, employee)
        - Export team data as CSV
      operationId: createTeam
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTeamRequest'
      responses:
        '201':
          description: Team created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TeamResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /teams/{teamId}:
    get:
      tags: [Teams]
      summary: Get team details
      description: |
        Get team details including member list.

        Returns all team members with their session info and assessment completion status.
        Anonymized candidates (GDPR deleted) are excluded from the member list.
      operationId: getTeam
      security:
        - bearerAuth: []
      parameters:
        - name: teamId
          in: path
          required: true
          schema:
            type: integer
          description: Team ID
      responses:
        '200':
          description: Team details with members
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TeamDetailResponse'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags: [Teams]
      summary: Update team
      description: Update team name, subtitle, or description
      operationId: updateTeam
      security:
        - bearerAuth: []
      parameters:
        - name: teamId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTeamRequest'
      responses:
        '200':
          description: Team updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TeamResponse'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Teams]
      summary: Delete team
      description: |
        Delete a team and all member assignments.

        **Warning:** This action cannot be undone. All member assignments will be removed.
        The candidates themselves are not affected - only the team grouping is removed.
      operationId: deleteTeam
      security:
        - bearerAuth: []
      parameters:
        - name: teamId
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Team deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteTeamResponse'
        '404':
          $ref: '#/components/responses/NotFound'

  /teams/{teamId}/members:
    post:
      tags: [Teams]
      summary: Add members to team
      description: |
        Add one or more candidates to the team.

        You can specify candidates by:
        - `session_ids`: Array of session IDs
        - `invite_ids`: Array of invite IDs (inv_xxx format)

        Candidates must belong to your account and have completed their assessment.
        Already existing members are skipped (not duplicated).
      operationId: addTeamMembers
      security:
        - bearerAuth: []
      parameters:
        - name: teamId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddTeamMembersRequest'
      responses:
        '200':
          description: Members added
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AddTeamMembersResponse'
        '400':
          description: No valid candidates provided
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Teams]
      summary: Remove all members
      description: |
        Remove all members from the team.

        The team itself remains - only the member assignments are deleted.
        Use DELETE /teams/{teamId} to delete the entire team.
      operationId: removeAllTeamMembers
      security:
        - bearerAuth: []
      parameters:
        - name: teamId
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: All members removed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RemoveAllMembersResponse'
        '404':
          $ref: '#/components/responses/NotFound'

  /teams/{teamId}/members/{sessionId}:
    delete:
      tags: [Teams]
      summary: Remove single member
      description: Remove a specific candidate from the team
      operationId: removeTeamMember
      security:
        - bearerAuth: []
      parameters:
        - name: teamId
          in: path
          required: true
          schema:
            type: integer
        - name: sessionId
          in: path
          required: true
          schema:
            type: integer
          description: Session ID of the candidate to remove
      responses:
        '200':
          description: Member removed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RemoveMemberResponse'
        '404':
          $ref: '#/components/responses/NotFound'

  /teams/{teamId}/export:
    get:
      tags: [Teams]
      summary: Export team data
      description: |
        Export team member data with all assessment dimensions.

        **Export includes:**
        - Candidate info (name, email, session ID)
        - All operative dimensions (normalized 0-100)
        - Graves values (G2-G7)
        - Assessment metadata

        **Formats:**
        - `csv`: Comma-separated values (default)
        - `json`: JSON array

        Anonymized candidates are excluded from export.
      operationId: exportTeam
      security:
        - bearerAuth: []
      parameters:
        - name: teamId
          in: path
          required: true
          schema:
            type: integer
        - name: format
          in: query
          schema:
            type: string
            enum: [csv, json]
            default: csv
          description: Export format
      responses:
        '200':
          description: Export data
          content:
            text/csv:
              schema:
                type: string
            application/json:
              schema:
                $ref: '#/components/schemas/TeamExportResponse'
        '404':
          $ref: '#/components/responses/NotFound'

  /profiles:
    get:
      tags: [Profiles]
      summary: List all profiles
      description: |
        List all completed assessments (profiles) for your account.

        This endpoint returns ALL assessments, regardless of whether they were created via API or manually in the Admin Tool.

        **Parameters:**
        - `status`: Filter by completion status (completed, all)
        - `sort`: Sort order (newest, oldest, name)
        - `include_results`: Include basic dimension scores

        GDPR-anonymized candidates are automatically excluded.
      operationId: listProfiles
      security:
        - bearerAuth: []
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [completed, all]
            default: completed
          description: Filter by status
        - name: sort
          in: query
          schema:
            type: string
            enum: [newest, oldest, name]
            default: newest
          description: Sort order
        - name: include_results
          in: query
          schema:
            type: boolean
            default: false
          description: Include basic dimension scores
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 100
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Profile list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /profiles/{sessionId}:
    get:
      tags: [Profiles]
      summary: Get profile details
      description: |
        Get detailed information about a specific profile/assessment.

        Returns candidate info, assessment metadata, full dimension results, and team memberships.
      operationId: getProfile
      security:
        - bearerAuth: []
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: integer
          description: Session ID of the profile
      responses:
        '200':
          description: Profile details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileDetailResponse'
        '404':
          $ref: '#/components/responses/NotFound'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: 'Format: pk_live_[32-char-hex]'

  schemas:
    ApiInfo:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            name:
              type: string
            version:
              type: string
            description:
              type: string

    ProductList:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            products:
              type: array
              items:
                $ref: '#/components/schemas/Product'
            total:
              type: integer

    Product:
      type: object
      properties:
        productID:
          type: string
        name:
          type: string
        description:
          type: string
        offers:
          type: object
          properties:
            price:
              type: string
            priceCurrency:
              type: string
        credits:
          type: integer
        features:
          type: array
          items:
            type: string

    CreateOrderRequest:
      type: object
      required:
        - product_id
      properties:
        product_id:
          type: string
          example: pa-pack-10
        email:
          type: string
          format: email

    OrderResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            order_id:
              type: string
            status:
              type: string
            product:
              type: object
            payment:
              type: object
              properties:
                amount:
                  type: number
                currency:
                  type: string
                checkout_url:
                  type: string
                  format: uri

    OrderStatus:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            order_id:
              type: string
            status:
              type: string
              enum: [pending_payment, completed, cancelled]
            payment_status:
              type: string

    BalanceResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            customer:
              type: object
            balance:
              type: object
              properties:
                credits_available:
                  type: integer
                credits_used:
                  type: integer
                pending_invites:
                  type: integer
                  description: Number of pending invites (informational only, no reservation)
            packages:
              type: array
              items:
                type: object

    AccountResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            account:
              type: object
              properties:
                id:
                  type: string
                  example: cus_9000001
                company:
                  type: string
                slug:
                  type: string
                  description: URL slug for assessment link
                type:
                  type: string
                  enum: [standard, API, enterprise]
                status:
                  type: string
                  enum: [active, inactive, api_only]
                created_at:
                  type: string
                  format: date-time
                assessment_link:
                  type: string
                  format: uri
                  description: Direct link for candidate assessments
            contact:
              type: object
              properties:
                email:
                  type: string
                first_name:
                  type: string
                last_name:
                  type: string
                phone:
                  type: string
                website:
                  type: string
                address:
                  type: string
            billing:
              type: object
              properties:
                email:
                  type: string
                company:
                  type: string
                street:
                  type: string
                city:
                  type: string
                vat_id:
                  type: string
                attention:
                  type: string
                  description: Billing attention (z.Hd.)
            api_keys:
              type: array
              items:
                type: object
                properties:
                  key_prefix:
                    type: string
                    example: pk_live_abc1
                  name:
                    type: string
                  mode:
                    type: string
                    enum: [live, test]
                  created_at:
                    type: string
                    format: date-time
                  last_used_at:
                    type: string
                    format: date-time
                  rate_limit:
                    type: integer
                  permissions:
                    type: array
                    items:
                      type: string
            links:
              type: object
              properties:
                balance:
                  type: string
                invites:
                  type: string
                job_profiles:
                  type: string
                documentation:
                  type: string

    InvoiceListResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            invoices:
              type: array
              items:
                $ref: '#/components/schemas/InvoiceSummary'
            summary:
              type: object
              properties:
                total_net:
                  type: number
                total_gross:
                  type: number
                currency:
                  type: string
            pagination:
              type: object
              properties:
                total:
                  type: integer
                limit:
                  type: integer
                offset:
                  type: integer
                has_more:
                  type: boolean

    InvoiceSummary:
      type: object
      properties:
        id:
          type: string
          example: inv_12345
        invoice_number:
          type: string
          example: "2026/1001/1"
        date:
          type: string
          format: date
        amount_net:
          type: number
        amount_gross:
          type: number
        currency:
          type: string
        status:
          type: string
          enum: [paid, unpaid]
        pdf_url:
          type: string

    InvoiceDetailResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            id:
              type: string
            invoice_number:
              type: string
            date:
              type: string
              format: date
            billing_address:
              type: object
              properties:
                company:
                  type: string
                attention:
                  type: string
                street:
                  type: string
                city:
                  type: string
                vat_id:
                  type: string
            line_items:
              type: array
              items:
                $ref: '#/components/schemas/InvoiceLineItem'
            product:
              type: object
              nullable: true
              properties:
                id:
                  type: integer
                name:
                  type: string
                price_net:
                  type: number
            amounts:
              type: object
              properties:
                net:
                  type: number
                vat_rate:
                  type: number
                vat_amount:
                  type: number
                gross:
                  type: number
                currency:
                  type: string
            reverse_charge:
              type: boolean
              description: True if VAT reverse charge applies
            status:
              type: string
              enum: [paid, unpaid]
            notes:
              type: string
              nullable: true
            pdf_url:
              type: string

    InvoiceLineItem:
      type: object
      properties:
        description:
          type: string
        additional_text:
          type: string
          nullable: true
        amount_net:
          type: number
          nullable: true
          description: null for text-only lines

    CreateInviteRequest:
      type: object
      required:
        - email
      properties:
        email:
          type: string
          format: email
          description: Candidate email address
        name:
          type: string
          description: Candidate name (optional)
        product_type:
          type: string
          default: escprofile
        send_email:
          type: boolean
          default: true
          description: Send invitation email to candidate

    InviteResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            invite_id:
              type: string
            status:
              type: string
              enum: [pending, started, completed, expired]
            candidate:
              type: object
              properties:
                email:
                  type: string
                name:
                  type: string
            analysis_link:
              type: string
              format: uri
            created_at:
              type: string
              format: date-time

    InviteList:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            invites:
              type: array
              items:
                $ref: '#/components/schemas/InviteResponse'
            total:
              type: integer

    ResultsResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            invite_id:
              type: string
            status:
              type: string
            candidate:
              type: object
            completed_at:
              type: string
              format: date-time
            scores:
              type: object
              additionalProperties:
                type: number
            overall_score:
              type: number
            report:
              type: object
              properties:
                pdf_url:
                  type: string
                  format: uri

    CreateWebhookRequest:
      type: object
      required:
        - url
      properties:
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
            enum: [analysis.completed, analysis.started, order.completed]

    WebhookResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            webhook_id:
              type: string
            url:
              type: string
            secret:
              type: string
              description: HMAC signing key (shown only once)
            events:
              type: array
              items:
                type: string

    WebhookList:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            webhooks:
              type: array
              items:
                type: object

    RequestTrialRequest:
      type: object
      required:
        - company
        - email
        - use_case
      properties:
        company:
          type: string
          description: Company name
        email:
          type: string
          format: email
          description: Company email (no consumer addresses like Gmail, GMX)
        contact_name:
          type: string
          description: Contact person name (optional)
        use_case:
          type: string
          description: Brief description of the intended use case
        website:
          type: string
          format: uri
          description: Company website (optional)

    RequestTrialResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            message:
              type: string
              example: "Trial request received. You will receive an email with your credentials within 24 hours."
            reference:
              type: string
              example: "trial_abc123"
            next_steps:
              type: array
              items:
                type: string

    RegisterRequest:
      type: object
      required:
        - email
        - company
      properties:
        email:
          type: string
          format: email
          description: Contact email for the API account
        company:
          type: string
          description: Company or organization name
        name:
          type: string
          description: Contact person (optional)
        address:
          type: string
          description: Street address (optional)
          example: "123 Main Street"
        zip:
          type: string
          description: Postal code (optional)
          example: "10001"
        city:
          type: string
          description: City (optional)
          example: "New York"
        country:
          type: string
          description: ISO country code (optional, default US)
          default: "US"
          example: "US"
        vat_id:
          type: string
          description: VAT ID for invoicing (optional)
          example: "US12345678"
        billing_contact:
          type: string
          description: Invoice recipient (attn.)
          example: "Accounting"
        phone:
          type: string
          description: Phone number (optional)
          example: "+1 212 555 1234"
        website:
          type: string
          format: uri
          description: Company website (optional)
          example: "https://www.example.com"
        product_id:
          type: string
          description: Product ID for immediate purchase (optional)
          enum: [pa-single, pa-pack-10, pa-pack-50]
        mode:
          type: string
          description: API key mode (optional, default live)
          enum: [live, test]
          default: "live"

    RegisterResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            message:
              type: string
            customer:
              type: object
              properties:
                id:
                  type: string
                company:
                  type: string
                email:
                  type: string
                is_new:
                  type: boolean
            api_key:
              type: object
              properties:
                key:
                  type: string
                  description: The full API key - SAVE IT! Shown only once.
                prefix:
                  type: string
                mode:
                  type: string
                permissions:
                  type: array
                  items:
                    type: string
                rate_limit:
                  type: integer
            order:
              type: object
              description: Only present if product_id was specified
              properties:
                order_id:
                  type: string
                checkout_url:
                  type: string
                  format: uri
            next_steps:
              type: object
              additionalProperties:
                type: string

    Error:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: object
          properties:
            message:
              type: string
            code:
              type: string

    DeleteInviteResponse:
      type: object
      properties:
        success:
          type: boolean
        message:
          type: string
        data:
          type: object
          properties:
            invite_id:
              type: string
            candidate_email:
              type: string
            was_expired:
              type: boolean
            cancelled_at:
              type: string
              format: date-time

    ReminderResponse:
      type: object
      properties:
        success:
          type: boolean
        message:
          type: string
        data:
          type: object
          properties:
            invite_id:
              type: string
            candidate_email:
              type: string
            status:
              type: string
            reminder_number:
              type: integer
            reminders_remaining:
              type: integer
            sent_at:
              type: string
              format: date-time
            expires_at:
              type: string
              format: date-time
            days_until_expiry:
              type: integer

    FullResultsResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            invite_id:
              type: string
            candidate:
              type: object
              properties:
                name:
                  type: string
                email:
                  type: string
                gender:
                  type: string
            assessment:
              type: object
              properties:
                completed_at:
                  type: string
                  format: date-time
                product_type:
                  type: string
            dimensions:
              type: object
              description: All 16 operative dimensions
              additionalProperties:
                $ref: '#/components/schemas/DimensionScore'
            graves_values:
              type: object
              description: Graves spiral dynamics values (G2-G7)
              additionalProperties:
                $ref: '#/components/schemas/GravesScore'
            wir_ich_ratio:
              type: object
              properties:
                wir_percent:
                  type: number
                ich_percent:
                  type: number
                tendency:
                  type: string
            norm_group:
              type: object
              properties:
                id:
                  type: integer
                name:
                  type: string
            reports:
              type: object
              properties:
                standard:
                  type: string
                  format: uri
                leadership:
                  type: string
                  format: uri
                sales:
                  type: string
                  format: uri

    SearchResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            query:
              type: object
              properties:
                q:
                  type: string
                name:
                  type: string
                email:
                  type: string
                date_from:
                  type: string
                date_to:
                  type: string
            candidates:
              type: array
              items:
                $ref: '#/components/schemas/SearchCandidate'
            pagination:
              type: object
              properties:
                total:
                  type: integer
                limit:
                  type: integer
                offset:
                  type: integer
                has_more:
                  type: boolean

    SearchCandidate:
      type: object
      properties:
        session_id:
          type: integer
        invite_id:
          type: string
          nullable: true
        name:
          type: string
        first_name:
          type: string
        last_name:
          type: string
        email:
          type: string
        gender:
          type: string
          enum: [male, female, other]
        birth_date:
          type: string
          format: date
        started_at:
          type: string
          format: date-time
        completed_at:
          type: string
          format: date-time
        scores:
          type: object
          description: Only present when include_results=true
          properties:
            goal_orientation:
              type: number
            problem_solving:
              type: number
            proactive:
              type: number
            reflective:
              type: number
            internal:
              type: number
            external:
              type: number
            global:
              type: number
            detail:
              type: number
        links:
          type: object
          properties:
            results:
              type: string
            report:
              type: string

    DimensionScore:
      type: object
      properties:
        raw_percent:
          type: number
          description: Raw score as percentage (0-100)
        c_value:
          type: number
          description: Normalized C-value (0-10, mean=5)
        label:
          type: string
          enum: [Very Low, Low, Below Average, Average, Above Average, High, Very High]
        name:
          type: object
          properties:
            en:
              type: string
            de:
              type: string

    GravesScore:
      type: object
      properties:
        raw_percent:
          type: number
        c_value:
          type: number
        name:
          type: object
          properties:
            en:
              type: string
            de:
              type: string

    MatchResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            invite_id:
              type: string
            candidate:
              type: object
              properties:
                name:
                  type: string
                email:
                  type: string
            profile:
              type: object
              properties:
                id:
                  type: string
                name:
                  type: string
                type:
                  type: string
            overall_match:
              type: number
              description: Overall match percentage (0-100)
            overall_rating:
              type: object
              properties:
                en:
                  type: string
                de:
                  type: string
                color:
                  type: string
                  enum: [green, lightgreen, yellow, orange, red]
            dimensions_matched:
              type: integer
            strengths:
              type: array
              items:
                type: string
            development_areas:
              type: array
              items:
                type: string
            recommendation:
              type: string
            match_details:
              type: object
              additionalProperties:
                $ref: '#/components/schemas/MatchDetail'

    MatchDetail:
      type: object
      properties:
        dimension:
          type: string
        label:
          type: object
          properties:
            en:
              type: string
            de:
              type: string
        candidate_value:
          type: number
        profile_ideal:
          type: number
        delta:
          type: number
        match_score:
          type: integer
        rating:
          type: object
          properties:
            en:
              type: string
            de:
              type: string
            color:
              type: string
        zone:
          type: string
          enum: [green, yellow, red]

    JobProfileList:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            profiles:
              type: array
              items:
                $ref: '#/components/schemas/JobProfileSummary'
            total:
              type: integer

    JobProfileSummary:
      type: object
      properties:
        id:
          type: string
          example: jp_634
        name:
          type: string
        name_en:
          type: string
        type:
          type: string
        dimensions_configured:
          type: integer
        active:
          type: boolean

    JobProfileResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            id:
              type: string
            name:
              type: string
            name_en:
              type: string
            type:
              type: string
            description:
              type: string
            active:
              type: boolean
            created_at:
              type: string
              format: date-time
            dimensions:
              type: object
              additionalProperties:
                $ref: '#/components/schemas/ProfileDimension'
            dimensions_count:
              type: integer

    ProfileDimension:
      type: object
      properties:
        db_name:
          type: string
        ideal:
          type: number
          description: Ideal C-value (0-10)
        ok_min:
          type: number
          description: Minimum acceptable value
        ok_max:
          type: number
          description: Maximum acceptable value
        critical_low:
          type: number
          description: Critical low threshold
        critical_high:
          type: number
          description: Critical high threshold

    CreateJobProfileRequest:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          description: Profile name (German)
        name_en:
          type: string
          description: Profile name (English)
        type:
          type: string
          default: standard
        description:
          type: string
        dimensions:
          type: object
          description: Dimension configurations
          additionalProperties:
            type: object
            properties:
              ideal:
                type: number
              ok_min:
                type: number
              ok_max:
                type: number
              critical_low:
                type: number
              critical_high:
                type: number

    UpdateJobProfileRequest:
      type: object
      properties:
        name:
          type: string
        name_en:
          type: string
        type:
          type: string
        description:
          type: string
        active:
          type: boolean
        dimensions:
          type: object
          additionalProperties:
            type: object
            properties:
              ideal:
                type: number
              ok_min:
                type: number
              ok_max:
                type: number
              critical_low:
                type: number
              critical_high:
                type: number

    DeleteJobProfileResponse:
      type: object
      properties:
        success:
          type: boolean
        message:
          type: string
        data:
          type: object
          properties:
            id:
              type: string
            deleted:
              type: boolean

    GdprCandidateListResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            candidates:
              type: array
              items:
                $ref: '#/components/schemas/GdprCandidate'
            pagination:
              type: object
              properties:
                total:
                  type: integer
                limit:
                  type: integer
                offset:
                  type: integer
                has_more:
                  type: boolean

    GdprCandidate:
      type: object
      properties:
        session_id:
          type: integer
        invite_id:
          type: string
          nullable: true
        status:
          type: string
          enum: [active, anonymized]
        protected:
          type: boolean
          description: If true, candidate cannot be deleted
        personal_data:
          type: object
          nullable: true
          description: null if already anonymized
          properties:
            first_name:
              type: string
            last_name:
              type: string
            email:
              type: string
            birth_date:
              type: string
            gender:
              type: string
        assessment:
          type: object
          properties:
            type:
              type: string
            started_at:
              type: string
            completed_at:
              type: string

    GdprCandidateStatusResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            session_id:
              type: integer
            invite_id:
              type: string
              nullable: true
            status:
              type: string
              enum: [active, anonymized]
            protected:
              type: boolean
            can_delete:
              type: boolean
              description: True if candidate can be deleted (not protected, not already anonymized)
            personal_data:
              type: object
              nullable: true
            assessment:
              type: object

    GdprDeleteResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            status:
              type: string
              enum: [success, partial]
            message:
              type: string
            session_id:
              type: integer
            anonymized:
              type: object
              properties:
                original_name:
                  type: string
                original_email:
                  type: string
                deleted_at:
                  type: string
                  format: date-time
            affected_tables:
              type: array
              items:
                type: string

    GdprDeletionListResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            deletions:
              type: array
              items:
                type: object
                properties:
                  session_id:
                    type: integer
                  invite_id:
                    type: string
                    nullable: true
                  assessment_type:
                    type: string
                  original_date:
                    type: string
                  status:
                    type: string
            pagination:
              type: object
              properties:
                total:
                  type: integer
                limit:
                  type: integer
                offset:
                  type: integer
                has_more:
                  type: boolean

    TeamListResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            teams:
              type: array
              items:
                $ref: '#/components/schemas/TeamSummary'
            pagination:
              type: object
              properties:
                total:
                  type: integer
                limit:
                  type: integer
                offset:
                  type: integer
                has_more:
                  type: boolean

    TeamSummary:
      type: object
      properties:
        id:
          type: integer
          example: 634
        title:
          type: string
          example: "Sales Team Q1 2026"
        subtitle:
          type: string
          example: "Regional Sales"
        description:
          type: string
        status:
          type: string
          enum: [active, inactive]
        member_count:
          type: integer
        created_at:
          type: string
          format: date-time
        created_by:
          type: string

    TeamResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          $ref: '#/components/schemas/TeamSummary'

    TeamDetailResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            id:
              type: integer
            title:
              type: string
            subtitle:
              type: string
            description:
              type: string
            status:
              type: string
              enum: [active, inactive]
            created_at:
              type: string
              format: date-time
            created_by:
              type: string
            members:
              type: array
              items:
                $ref: '#/components/schemas/TeamMember'
            member_count:
              type: integer

    TeamMember:
      type: object
      properties:
        session_id:
          type: integer
        invite_id:
          type: string
          nullable: true
        first_name:
          type: string
        last_name:
          type: string
        email:
          type: string
        assessment_type:
          type: string
        completed_at:
          type: string
          format: date-time

    CreateTeamRequest:
      type: object
      required:
        - title
      properties:
        title:
          type: string
          description: Team name/title
          example: "Sales Team Q1 2026"
        subtitle:
          type: string
          description: Additional subtitle (optional)
          example: "Regional Sales"
        description:
          type: string
          description: Team description (optional)

    UpdateTeamRequest:
      type: object
      properties:
        title:
          type: string
        subtitle:
          type: string
        description:
          type: string

    DeleteTeamResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            id:
              type: integer
            deleted:
              type: boolean
            members_removed:
              type: integer
            message:
              type: string

    AddTeamMembersRequest:
      type: object
      properties:
        session_ids:
          type: array
          items:
            type: integer
          description: Array of session IDs to add
        invite_ids:
          type: array
          items:
            type: string
          description: Array of invite IDs (inv_xxx format) to add

    AddTeamMembersResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            team_id:
              type: integer
            added:
              type: array
              items:
                type: object
                properties:
                  session_id:
                    type: integer
                  status:
                    type: string
                    enum: [added, already_member, not_found, not_completed]
            added_count:
              type: integer
            skipped_count:
              type: integer

    RemoveAllMembersResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            team_id:
              type: integer
            removed_count:
              type: integer
            message:
              type: string

    RemoveMemberResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            team_id:
              type: integer
            session_id:
              type: integer
            removed:
              type: boolean

    TeamExportResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            team_id:
              type: integer
            team_name:
              type: string
            exported_at:
              type: string
              format: date-time
            member_count:
              type: integer
            members:
              type: array
              items:
                $ref: '#/components/schemas/TeamExportMember'

    TeamExportMember:
      type: object
      properties:
        session_id:
          type: integer
        first_name:
          type: string
        last_name:
          type: string
        email:
          type: string
        completed_at:
          type: string
          format: date-time
        dimensions:
          type: object
          description: All dimension scores (0-100)
          additionalProperties:
            type: number
        graves_values:
          type: object
          description: Graves values (G2-G7)
          additionalProperties:
            type: number

    ProfileListResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            profiles:
              type: array
              items:
                $ref: '#/components/schemas/ProfileSummary'
            pagination:
              type: object
              properties:
                total:
                  type: integer
                limit:
                  type: integer
                offset:
                  type: integer
                has_more:
                  type: boolean
            filters:
              type: object
              properties:
                status:
                  type: string
                sort:
                  type: string

    ProfileSummary:
      type: object
      properties:
        session_id:
          type: integer
        invite_id:
          type: string
          nullable: true
        candidate:
          type: object
          properties:
            first_name:
              type: string
            last_name:
              type: string
            name:
              type: string
            email:
              type: string
            gender:
              type: string
              enum: [male, female, diverse]
            birth_date:
              type: string
              format: date
              nullable: true
        assessment:
          type: object
          properties:
            type:
              type: string
            status:
              type: string
              enum: [completed, in_progress]
            started_at:
              type: string
              format: date-time
            completed_at:
              type: string
              format: date-time
              nullable: true
        source:
          type: string
          enum: [api, manual]
          description: Whether created via API or Admin Tool
        results:
          type: object
          description: Only present when include_results=true
          additionalProperties:
            type: number
        links:
          type: object
          properties:
            details:
              type: string
            results:
              type: string
              nullable: true
            report:
              type: string
              nullable: true

    ProfileDetailResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            session_id:
              type: integer
            invite_id:
              type: string
              nullable: true
            candidate:
              type: object
              properties:
                first_name:
                  type: string
                last_name:
                  type: string
                name:
                  type: string
                email:
                  type: string
                gender:
                  type: string
                birth_date:
                  type: string
                  nullable: true
            assessment:
              type: object
              properties:
                type:
                  type: string
                status:
                  type: string
                started_at:
                  type: string
                completed_at:
                  type: string
                  nullable: true
                calculation_type:
                  type: string
            source:
              type: string
              enum: [api, manual]
            results:
              type: object
              description: Full dimension results (only for completed)
              properties:
                dimensions:
                  type: object
                graves_values:
                  type: object
                norm_group:
                  type: string
                  nullable: true
            teams:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: integer
                  title:
                    type: string
                  subtitle:
                    type: string
            links:
              type: object
              properties:
                results_json:
                  type: string
                  nullable: true
                report_pdf:
                  type: string
                  nullable: true

  responses:
    BadRequest:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
