openapi: "3.1.0"
info:
  title: kakak.ai Platform API
  version: "1.0.0"
  description: |
    Public REST API for kakak.ai's document evaluation platform (DOC-01).
    Authenticate with `Authorization: Bearer kk_live_...` or `kk_test_...`.
    Test-mode keys return schema-valid stubbed output and do not deduct credits.

    Base URL: `https://api.platform.kakak.ai/v1`

servers:
  - url: https://api.platform.kakak.ai/v1
    description: Production
  - url: http://localhost:3100/v1
    description: Local development

security:
  - ApiKey: []

tags:
  - name: Workflows
  - name: Runs
  - name: Reference Documents
  - name: Documents
  - name: Prompt Templates
  - name: Products
  - name: Usage
  - name: Webhooks
  - name: API Keys
  - name: Health

paths:
  /health:
    get:
      tags: [Health]
      summary: Service health check
      security: []
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, enum: [ok, degraded] }
                  service: { type: string }
                  checks: { type: object }

  /keys:
    get:
      tags: [API Keys]
      summary: List API keys
      description: Uses Supabase session JWT (not Platform API key).
      security: [{ SupabaseSession: [] }]
      responses:
        "200":
          description: List of API keys
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ApiKeySummary"
    post:
      tags: [API Keys]
      summary: Create API key
      security: [{ SupabaseSession: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, maxLength: 100 }
                mode: { type: string, enum: [live, test], default: live }
                scopes: { type: array, items: { type: string } }
      responses:
        "201":
          description: Key created (token shown once)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SecretApiKey"

  /keys/{keyId}:
    delete:
      tags: [API Keys]
      summary: Revoke API key
      security: [{ SupabaseSession: [] }]
      parameters:
        - $ref: "#/components/parameters/KeyId"
      responses:
        "200": { description: Key revoked }

  /products:
    get:
      tags: [Products]
      summary: List products
      responses:
        "200":
          description: Product catalog
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Product" }

  /products/{code}:
    get:
      tags: [Products]
      summary: Get product by code
      parameters:
        - name: code
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Product detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Product"

  /prompt-templates:
    get:
      tags: [Prompt Templates]
      summary: List prompt templates
      responses:
        "200":
          description: Catalog of prompt templates
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/PromptTemplate" }

  /prompt-templates/generate:
    post:
      tags: [Prompt Templates]
      summary: AI-generate evaluation prompt
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [goal]
              properties:
                goal: { type: string, maxLength: 2000 }
                category: { type: string, enum: [education, legal, grading, hr, general] }
                output_format: { type: string, enum: [json, markdown, docx, xlsx, csv] }
      responses:
        "200":
          description: Generated prompt
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GeneratedPrompt"

  /workflows:
    get:
      tags: [Workflows]
      summary: List workflows
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
      responses:
        "200":
          description: Paginated workflow list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/Workflow" } }
                  next_cursor: { type: string, nullable: true }
    post:
      tags: [Workflows]
      summary: Create workflow
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [product_code, name]
              properties:
                product_code: { type: string }
                name: { type: string, maxLength: 200 }
                prompt_template_id: { type: string }
                user_prompt: { type: string }
                reference_document_ids: { type: array, items: { type: string } }
                output_format: { type: string, enum: [json, markdown, docx, xlsx, csv] }
                output_schema_sample_id: { type: string }
                output_schema: { type: object }
                default_mode: { type: string, enum: [sync, async] }
                default_model_policy: { type: string, enum: [auto, gemini-file-search, deepseek-1m-fulltext, hybrid] }
                webhook_endpoint_id: { type: string }
      responses:
        "201":
          description: Workflow created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Workflow"

  /workflows/{workflowId}:
    get:
      tags: [Workflows]
      summary: Get workflow
      parameters:
        - $ref: "#/components/parameters/WorkflowId"
      responses:
        "200":
          description: Workflow detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Workflow"
    patch:
      tags: [Workflows]
      summary: Update workflow
      parameters:
        - $ref: "#/components/parameters/WorkflowId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                status: { type: string, enum: [active, paused, archived] }
                user_prompt: { type: string }
      responses:
        "200":
          description: Workflow updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Workflow"
    delete:
      tags: [Workflows]
      summary: Delete workflow
      parameters:
        - $ref: "#/components/parameters/WorkflowId"
      responses:
        "200": { description: Workflow deleted }

  /workflows/{workflowId}/runs:
    post:
      tags: [Runs]
      summary: Create a run
      description: Execute a workflow. Async only in v1; sync returns 409 use_async.
      parameters:
        - $ref: "#/components/parameters/WorkflowId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                mode: { type: string, enum: [async] }
                metadata: { type: string, description: "JSON object string" }
                input_documents: { type: string, description: "JSON array of document IDs" }
                "files[]": { type: array, items: { type: string, format: binary } }
      responses:
        "202":
          description: Run queued
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"

  /runs:
    get:
      tags: [Runs]
      summary: List runs
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - name: workflow_id
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string, enum: [queued, running, succeeded, failed, canceled, refunded] }
      responses:
        "200":
          description: Paginated run list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/Run" } }
                  next_cursor: { type: string, nullable: true }

  /runs/{runId}:
    get:
      tags: [Runs]
      summary: Get run
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Run detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"

  /runs/{runId}/cancel:
    post:
      tags: [Runs]
      summary: Cancel queued run
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Run canceled (full refund)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Run"

  /runs/{runId}/output:
    get:
      tags: [Runs]
      summary: Download run output
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Signed download URL
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RunOutputFile"

  /reference-documents:
    get:
      tags: [Reference Documents]
      summary: List reference documents
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
      responses:
        "200":
          description: Paginated list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/ReferenceDocument" } }
                  next_cursor: { type: string, nullable: true }
    post:
      tags: [Reference Documents]
      summary: Upload reference document
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [name, file]
              properties:
                name: { type: string, maxLength: 200 }
                file: { type: string, format: binary }
      responses:
        "201":
          description: Reference document created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReferenceDocument"

  /reference-documents/{refId}:
    get:
      tags: [Reference Documents]
      summary: Get reference document
      parameters:
        - $ref: "#/components/parameters/RefId"
      responses:
        "200":
          description: Reference document detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReferenceDocument"
    delete:
      tags: [Reference Documents]
      summary: Delete reference document pointer
      parameters:
        - $ref: "#/components/parameters/RefId"
      responses:
        "200": { description: Deleted }

  /documents:
    post:
      tags: [Documents]
      summary: Request presigned upload URL
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [filename]
              properties:
                filename: { type: string }
                mime_type: { type: string }
                size_bytes: { type: integer, maximum: 52428800 }
      responses:
        "200":
          description: Presigned upload URL
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentUpload"

  /documents/{docId}/complete:
    post:
      tags: [Documents]
      summary: Complete upload + extract text
      parameters:
        - $ref: "#/components/parameters/DocId"
      responses:
        "200":
          description: Document ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Document"

  /usage:
    get:
      tags: [Usage]
      summary: Query API usage
      parameters:
        - name: from
          in: query
          schema: { type: string, format: date }
        - name: to
          in: query
          schema: { type: string, format: date }
        - name: group_by
          in: query
          schema: { type: string, enum: [workflow] }
      responses:
        "200":
          description: Usage report
          content:
            application/json:
              schema:
                type: object
                properties:
                  period:
                    type: object
                    properties:
                      from: { type: string }
                      to: { type: string }
                  total_runs: { type: integer }
                  total_credits: { type: number }
                  by_workflow:
                    type: array
                    items:
                      type: object
                      properties:
                        workflow_id: { type: string }
                        name: { type: string }
                        runs: { type: integer }
                        credits: { type: number }

  /usage/balance:
    get:
      tags: [Usage]
      summary: Get pro-credit balance
      responses:
        "200":
          description: Current balance
          content:
            application/json:
              schema:
                type: object
                properties:
                  pro_credits: { type: number }
                  bonus_pro: { type: number }
                  regular_pro: { type: number }
                  basic_credits_excluded: { type: boolean }

  /webhook-endpoints:
    get:
      tags: [Webhooks]
      summary: List webhook endpoints
      responses:
        "200":
          description: Endpoint list (secrets excluded)
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/WebhookEndpoint" }
    post:
      tags: [Webhooks]
      summary: Create webhook endpoint
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url: { type: string, format: uri }
                events:
                  type: array
                  items: { type: string, enum: [run.succeeded, run.failed, run.canceled, reference_document.ready] }
      responses:
        "201":
          description: Endpoint created (includes secret)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/WebhookEndpoint"
                  - type: object
                    properties:
                      secret: { type: string }
                      warning: { type: string }

  /webhook-endpoints/{whkId}:
    get:
      tags: [Webhooks]
      summary: Get webhook endpoint
      parameters:
        - $ref: "#/components/parameters/WhkId"
      responses:
        "200":
          description: Endpoint detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookEndpoint"
    delete:
      tags: [Webhooks]
      summary: Delete webhook endpoint
      parameters:
        - $ref: "#/components/parameters/WhkId"
      responses:
        "200": { description: Deleted }

components:
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer
      bearerFormat: "kk_live_... or kk_test_..."
    SupabaseSession:
      type: http
      scheme: bearer
      description: Supabase auth session (for /keys only)

  parameters:
    KeyId:
      name: keyId
      in: path
      required: true
      schema: { type: string }
    WorkflowId:
      name: workflowId
      in: path
      required: true
      schema: { type: string }
    RunId:
      name: runId
      in: path
      required: true
      schema: { type: string }
    RefId:
      name: refId
      in: path
      required: true
      schema: { type: string }
    DocId:
      name: docId
      in: path
      required: true
      schema: { type: string }
    WhkId:
      name: whkId
      in: path
      required: true
      schema: { type: string }
    Limit:
      name: limit
      in: query
      schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
    Cursor:
      name: cursor
      in: query
      schema: { type: string }
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      schema: { type: string }

  schemas:
    ApiKeySummary:
      type: object
      properties:
        public_id: { type: string }
        name: { type: string }
        mode: { type: string, enum: [live, test] }
        key_prefix: { type: string }
        last4: { type: string }
        scopes: { type: array, items: { type: string } }
        status: { type: string, enum: [active, revoked] }
        rate_limit_rpm: { type: integer, nullable: true }
        expires_at: { type: string, format: date-time, nullable: true }
        last_used_at: { type: string, format: date-time, nullable: true }
        created_at: { type: string, format: date-time }
        revoked_at: { type: string, format: date-time, nullable: true }

    SecretApiKey:
      type: object
      properties:
        public_id: { type: string }
        token: { type: string }
        name: { type: string }
        mode: { type: string }
        scopes: { type: array, items: { type: string } }
        last4: { type: string }
        created_at: { type: string, format: date-time }
        warning: { type: string }

    Product:
      type: object
      properties:
        code: { type: string }
        name: { type: string }
        description: { type: string, nullable: true }
        status: { type: string, enum: [active, beta, deprecated] }
        default_model_policy: { type: string }
        created_at: { type: string, format: date-time }

    PromptTemplate:
      type: object
      properties:
        id: { type: string }
        slug: { type: string }
        title: { type: string }
        description: { type: string, nullable: true }
        category: { type: string }
        user_prompt_seed: { type: string }
        recommended_output_format: { type: string, nullable: true }
        locale: { type: string }

    GeneratedPrompt:
      type: object
      properties:
        user_prompt: { type: string }
        recommended_output_format: { type: string, nullable: true }

    Workflow:
      type: object
      properties:
        id: { type: string }
        object: { type: string }
        product_code: { type: string }
        name: { type: string }
        status: { type: string, enum: [active, paused, archived] }
        prompt_template_id: { type: string, nullable: true }
        user_prompt: { type: string, nullable: true }
        reference_document_ids: { type: array, items: { type: string } }
        output_format: { type: string, enum: [json, markdown, docx, xlsx, csv] }
        output_schema: { type: object, nullable: true }
        output_schema_sample_id: { type: string, nullable: true }
        default_mode: { type: string }
        default_model_policy: { type: string }
        webhook_endpoint_id: { type: string, nullable: true }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    Run:
      type: object
      properties:
        id: { type: string }
        object: { type: string }
        workflow_id: { type: string }
        status: { type: string }
        mode: { type: string }
        quoted_credits: { type: number }
        charged_credits: { type: number, nullable: true }
        input_documents: { type: array, items: { type: string } }
        metadata: { type: object }
        result_url: { type: string }
        output_format: { type: string, nullable: true }
        output: { type: object }
        output_file: { $ref: "#/components/schemas/RunOutputFile" }
        usage: { $ref: "#/components/schemas/RunUsage" }
        error: { type: object, nullable: true }
        created_at: { type: string, format: date-time }
        started_at: { type: string, format: date-time, nullable: true }
        finished_at: { type: string, format: date-time, nullable: true }

    RunUsage:
      type: object
      properties:
        input_tokens: { type: integer, nullable: true }
        output_tokens: { type: integer, nullable: true }
        model: { type: string, nullable: true }
        rag_strategy: { type: string, nullable: true }

    RunOutputFile:
      type: object
      properties:
        url: { type: string }
        expires_at: { type: string, format: date-time }
        mime_type: { type: string }

    ReferenceDocument:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        status: { type: string, enum: [processing, ready, failed] }
        created_at: { type: string, format: date-time }

    DocumentUpload:
      type: object
      properties:
        id: { type: string }
        upload_url: { type: string }
        expires_at: { type: string, format: date-time }

    Document:
      type: object
      properties:
        id: { type: string }
        object: { type: string }
        mime_type: { type: string, nullable: true }
        size_bytes: { type: integer, nullable: true }
        page_count: { type: integer, nullable: true }
        extracted: { type: boolean }
        created_at: { type: string, format: date-time }

    WebhookEndpoint:
      type: object
      properties:
        id: { type: string }
        url: { type: string }
        events: { type: array, items: { type: string } }
        status: { type: string, enum: [active, disabled] }
        created_at: { type: string, format: date-time }

    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
