openapi: 3.1.0
info:
  title: ValuDomain Public API
  version: "1.0.0"
  description: |
    Public REST API for programmatic access to ValuDomain's domain valuation
    and Pro Deep Analysis Report engines.

    ## Authentication
    All endpoints require `Authorization: Bearer <api_key>`. Keys are issued
    per consumer and look like `vd_live_xxxxxxxxxxxxxxxx`. Keep your key
    secret — anyone with it can spend your monthly quota.

    ## Quotas & rate limiting
    Each key has a monthly quota (default 1,000 calls). Counters reset at the
    start of each calendar month (UTC). Every successful call returns
    `X-RateLimit-Limit` and `X-RateLimit-Remaining` headers. A soft per-key
    rate limit of ~2 req/sec is applied — burst higher and you'll see 429s
    with `Retry-After: 1`.

    ## Errors
    All errors use the shape:
    ```json
    { "error": { "code": "string", "message": "string", "retry_after": 60 } }
    ```
    `retry_after` (seconds) is present for 429 responses.

servers:
  - url: https://valudomain.com
    description: Production
  - url: https://valudomain.lovable.app
    description: Production (Lovable alias)

security:
  - BearerAuth: []

paths:
  /api/public/v1/valuations:
    post:
      summary: Create a quick valuation (synchronous)
      description: |
        Returns a full quick valuation in ~3–8 seconds. The response contains
        all 11 scored dimensions, comparable sales, pricing recommendations,
        risks, and verified WHOIS data.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ValuationRequest"
            examples:
              example1:
                value:
                  domain: "openai.com"
      responses:
        "200":
          description: Valuation completed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValuationResponse"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "500": { $ref: "#/components/responses/InternalError" }
        "502": { $ref: "#/components/responses/UpstreamError" }

  /api/public/v1/pro-reports:
    post:
      summary: Generate a Pro Deep Analysis Report (asynchronous)
      description: |
        Creates a Pro report job. Pro reports take 30–90 seconds. The
        response includes a `job_id` and, in most cases, the completed
        result inline (the server processes the job before responding).
        If the response shows `status: "processing"` or `"queued"`, poll
        `GET /api/public/v1/pro-reports/{job_id}` until status is
        `completed` or `failed`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProReportRequest"
            examples:
              example1:
                value:
                  domain: "openai.com"
      responses:
        "202":
          description: Job accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProReportJob"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "500": { $ref: "#/components/responses/InternalError" }

  /api/public/v1/pro-reports/{job_id}:
    get:
      summary: Poll a Pro Report job
      parameters:
        - in: path
          name: job_id
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Current job state
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProReportJob" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404":
          description: Job not found, or not owned by this API key
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: "vd_live_*"

  schemas:
    ValuationRequest:
      type: object
      required: [domain]
      properties:
        domain:
          type: string
          minLength: 3
          maxLength: 253
          pattern: "^[a-zA-Z0-9.-]+$"
          example: "openai.com"

    ValuationResponse:
      type: object
      properties:
        domain: { type: string }
        generated_at: { type: string, format: date-time }
        result: { $ref: "#/components/schemas/ValuationResult" }

    ValuationResult:
      type: object
      properties:
        estimated_value: { type: integer, description: "Midpoint USD" }
        estimated_value_low: { type: integer }
        estimated_value_high: { type: integer }
        confidence: { type: string, enum: [high, medium, low] }
        confidence_score: { type: integer, minimum: 0, maximum: 100 }
        market_demand: { type: string, enum: [high, medium, low] }
        search_volume: { type: integer }
        search_volume_trend_pct: { type: integer }
        competitor_count: { type: integer }
        data_sources_count: { type: integer }
        domain_age_years: { type: integer }
        rationale: { type: string }
        dimensions:
          type: array
          description: "Exactly 11 scored dimensions."
          items: { $ref: "#/components/schemas/Dimension" }
        comparable_sales:
          type: array
          items: { $ref: "#/components/schemas/ComparableSale" }
        pricing_recommendations:
          type: object
          properties:
            asking_price: { type: integer }
            auction_start: { type: integer }
            reserve_price: { type: integer }
        risks:
          type: array
          items: { $ref: "#/components/schemas/Risk" }
        whois:
          type: object
          properties:
            created: { type: string, nullable: true }
            expiry: { type: string, nullable: true }
            registrar: { type: string, nullable: true }
            age_years: { type: integer, nullable: true }
        comps_source: { type: string, enum: [namebio, ai_fallback] }
        comps_real_count: { type: integer }

    Dimension:
      type: object
      properties:
        name:
          type: string
          enum:
            - "Length & Memorability"
            - "Keyword Value"
            - "TLD Quality"
            - "Brandability"
            - "Pronounceability"
            - "Commercial Intent"
            - "Search Demand"
            - "Comparable Sales"
            - "Industry Trend"
            - "Trademark Risk"
            - "Linguistic Quality"
        score: { type: integer, minimum: 0, maximum: 100 }
        contribution_pct: { type: integer }
        detail: { type: string }

    ComparableSale:
      type: object
      properties:
        domain: { type: string }
        price: { type: integer }
        date: { type: string }
        source: { type: string }

    Risk:
      type: object
      properties:
        level: { type: string, enum: [high, medium, low] }
        title: { type: string }
        detail: { type: string }

    ProReportRequest:
      type: object
      required: [domain]
      properties:
        domain: { type: string, pattern: "^[a-zA-Z0-9.-]+$" }

    ProReportJob:
      type: object
      properties:
        job_id: { type: string, format: uuid }
        domain: { type: string }
        status: { type: string, enum: [queued, processing, completed, failed] }
        result:
          type: object
          nullable: true
          properties:
            markdown: { type: string }
            structured:
              type: object
              properties:
                rating: { type: string, enum: [BUY, CONSIDER, PASS] }
                weighted_score: { type: number }
                value_range_low_usd: { type: integer }
                value_range_high_usd: { type: integer }
                buyer_offer_usd: { type: integer }
                optimistic_ceiling_usd: { type: integer }
                one_line_verdict: { type: string }
            sources: { type: object }
            model: { type: string }
            quick_valuation: { $ref: "#/components/schemas/ValuationResult" }
        error: { type: string, nullable: true }
        created_at: { type: string, format: date-time }
        completed_at: { type: string, format: date-time, nullable: true }
        poll_url: { type: string }

    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              enum:
                - missing_authorization
                - invalid_authorization_scheme
                - invalid_api_key
                - key_revoked
                - quota_exceeded
                - rate_limited
                - invalid_input
                - not_found
                - internal_error
                - upstream_error
            message: { type: string }
            retry_after: { type: integer, description: "Seconds (429 only)" }

  responses:
    BadRequest:
      description: Invalid input
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example:
            error: { code: "invalid_input", message: "domain: Invalid" }
    Unauthorized:
      description: Missing/invalid/revoked API key
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example:
            error: { code: "invalid_api_key", message: "Unknown API key" }
    RateLimited:
      description: Per-key rate limit or monthly quota exceeded
      headers:
        Retry-After:
          schema: { type: integer }
        X-RateLimit-Limit:
          schema: { type: integer }
        X-RateLimit-Remaining:
          schema: { type: integer }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example:
            error: { code: "quota_exceeded", message: "Monthly quota of 1000 reached.", retry_after: 1234567 }
    InternalError:
      description: Server error
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    UpstreamError:
      description: AI provider error
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
