openapi: 3.0.3
info:
  title: each::labs Official API v1
  description: |
    Official each::labs API V1.
    This API provides access to our comprehensive AI model catalog and other resources.

    ## Public contract notes

    - Public API routes handled by request logging include `X-Request-Id` for
      support and log correlation. Clients may send `X-Request-Id`; otherwise
      api-service generates one. Root, health, and metrics endpoints are excluded.
    - REST error bodies keep the backward-compatible JSON shape used by existing
      clients. Most errors include at least `error`; some shared error paths also
      include `status` and `details`. Use the HTTP status code as the primary
      programmatic signal unless an endpoint documents a richer envelope.
    - Rate-limit and retry headers are endpoint-dependent. If a response includes
      `Retry-After`, honor it before retrying; otherwise use bounded exponential
      backoff for retryable 429/5xx responses.
    - api-service exposes single workflow trigger through
      `/v1/workflows/trigger/{workflowID}/{versionID}`. Bulk workflow trigger is
      not routed through api-service; use the workflows-engine public API directly
      where bulk trigger is required.
  version: 1.0.0
  contact:
    name: API Support
    email: support@eachlabs.ai
  x-logo:
    url: './logo-white.svg'
    altText: 'each::labs'

servers:
  - url: https://api.eachlabs.ai
    description: Production server

tags:
  - name: AI Models
    description: Endpoints for listing and retrieving AI models and other resources.
  - name: AI Models Prediction
    description: Endpoints for managing model predictions
  - name: Uploads
    description: Endpoints for uploading files that can be used as model inputs.
  - name: Executions
    description: Endpoints for listing execution history.
  - name: Workflows
    description: Endpoints for triggering workflow executions through api-service.
  - name: OpenAI Compatibility
    description: OpenAI-compatible endpoints for LLM Router requests.
  - name: Webhooks
    description: |
      Endpoints for retrieving webhook information and delivery history.
      
      **Note:** Currently, webhooks are only supported for Workflows V2. We are actively expanding webhook support to other services and will update this documentation as new integrations become available.

paths:
  /v1/models:
    get:
      tags:
        - AI Models
      summary: List AI models
      description: Retrieve a paginated list of AI models with optional filters
      operationId: listModels
      parameters:
        - name: name
          in: query
          schema:
            type: string
          description: Filter by model name (case-insensitive search)
          example: "nano-banana-pro-edit"
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            minimum: 1
            maximum: 500
          description: Maximum number of results to return
          example: 20
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
            minimum: 0
          description: Number of results to skip for pagination
          example: 0
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Model'
        '400':
          $ref: '#/components/responses/BadRequest'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/models/{slug}:
    get:
      tags:
        - AI Models
      summary: Get model details
      description: Retrieve detailed information about a specific model by slug
      operationId: getModelByPath
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
          description: Model slug identifier
          example: "nano-banana-pro-edit"
      responses:
        '200':
          description: Model details retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelDetail'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/models/{slug}/schemas/openapi:
    get:
      tags:
        - AI Models
      summary: Get model OpenAPI schema
      description: |
        Returns an OpenAPI 3.0 document describing the model's input
        contract (derived from its request_schema) and the prediction
        lifecycle endpoints. Intended for the model API tab and for
        clients that consume a per-model machine-readable spec.
      operationId: getModelOpenAPI
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
          description: Model slug identifier
          example: "nano-banana-pro-edit"
      responses:
        '200':
          description: OpenAPI 3.0 document for the model
          content:
            application/json:
              schema:
                type: object
                description: An OpenAPI 3.0 document.
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/model:
    get:
      tags:
        - AI Models
      summary: Get model details (deprecated)
      description: |
        Deprecated: use `GET /v1/models/{slug}` instead. Retained for
        backwards compatibility; returns the same payload.
      deprecated: true
      operationId: getModel
      parameters:
        - name: slug
          in: query
          required: true
          schema:
            type: string
          description: Model slug identifier
          example: "nano-banana-pro-edit"
      responses:
        '200':
          description: Model details retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelDetail'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/llm-router/model-catalog:
    get:
      tags:
        - AI Models
      summary: Get eachlabs-llm-router model catalog
      description: Retrieve the curated visible model list and generated model enum schema for eachlabs-llm-router.
      operationId: getLLMRouterModelCatalog
      responses:
        '200':
          description: LLM router model catalog retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LLMRouterModelCatalog'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/prediction:
    post:
      tags:
        - AI Models Prediction
      summary: Create Model Prediction
      description: |
        Create a new prediction for a specific model.

      operationId: createPrediction
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePredictionRequest'
            example:
              model: "flux-1-1-pro"
              input:
                prompt: "A beautiful sunset over the ocean with vibrant colors"
                aspect_ratio: "16:9"
              webhook_url: "https://your-app.com/webhook"
              webhook_secret: "your-secret-key"
      responses:
        '200':
          description: Prediction created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatePredictionResponse'
              example:
                status: "success"
                message: "Prediction created successfully"
                predictionID: "abc123-def456-ghi789"
        '400':
          $ref: '#/components/responses/BadRequest'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/prediction/run:
    post:
      tags:
        - AI Models Prediction
      summary: Run Model Prediction Synchronously
      description: |
        Create a prediction and wait for the result. This compatibility endpoint is only supported
        for models with synchronous execution enabled; unsupported models return a bad request error.

      operationId: runPrediction
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePredictionRequest'
            example:
              model: "eachlabs-llm-router"
              input:
                model: "openai/gpt-4o-mini"
                messages:
                  - role: "user"
                    content: "Write a one sentence caption."
      responses:
        '200':
          description: Prediction completed or failed synchronously
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunPredictionResponse'
              example:
                status: "success"
                message: "Prediction completed successfully"
                predictionID: "abc123-def456-ghi789"
                output:
                  text: "A cinematic sunset over the ocean."
                error: ""
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/prediction/{id}:
    get:
      tags:
        - AI Models Prediction
      summary: Get Model Prediction
      description: |
        Retrieve the status and results of a model prediction by its ID.

        Prediction statuses are `starting` or `processing` while work is in flight.
        Terminal statuses are `success`, `failed`, and `cancelled`.
      operationId: getPrediction
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Prediction ID
          example: "abc123-def456-ghi789"
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: Prediction retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Prediction'
              example:
                id: "abc123-def456-ghi789"
                input:
                  prompt: "A beautiful sunset over the ocean with vibrant colors"
                  aspect_ratio: "16:9"
                status: "success"
                output: "https://storage.example.com/predictions/abc123/image.jpg"
                logs: null
                metrics:
                  predict_time: 12.5
                  cost: 0.05
                urls:
                  cancel: "https://api.eachlabs.ai/v1/prediction/abc123-def456-ghi789/cancel"
                  get: "https://api.eachlabs.ai/v1/prediction/abc123-def456-ghi789"
        '404':
          description: Prediction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: "Prediction not found"
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/prediction/{id}/cancel:
    post:
      tags:
        - AI Models Prediction
      summary: Cancel Model Prediction
      description: |
        Cancel a running prediction by ID. Cancellation is best-effort for upstream providers;
        not every provider can interrupt work that is already running. When provider
        cancellation is supported and succeeds, the Eachlabs execution record is updated
        to `cancelled`; otherwise the original prediction may still complete or fail.
      operationId: cancelPrediction
      security:
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Prediction ID
          example: "abc123-def456-ghi789"
      responses:
        '200':
          description: Prediction cancellation accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CancelPredictionResponse'
              example:
                status: "cancelled"
                message: "Prediction canceled successfully"
                predictionID: "abc123-def456-ghi789"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/upload/presign:
    post:
      tags:
        - Uploads
      summary: Create Presigned Upload URL
      description: |
        Create a short-lived presigned URL for uploading media files. Upload the raw file bytes
        to `presigned_url` using every header returned in `required_headers`, then pass
        `public_url` as a model input.

      operationId: presignUpload
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PresignUploadRequest'
            example:
              content_type: "image/png"
              file_type: "image"
      responses:
        '200':
          description: Presigned upload URL created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PresignUploadResponse'
              example:
                id: "0dce0f44-b8a5-4f30-91d4-5f6b0c221bf0"
                presigned_url: "https://eachlabs-storage.s3.amazonaws.com/uploads/...?X-Amz-Signature=..."
                public_url: "https://cdn-us.eachlabs.ai/uploads/0dce0f44-b8a5-4f30-91d4-5f6b0c221bf0.png"
                expires_at: "2026-04-27T18:15:00Z"
                required_headers:
                  x-amz-meta-file-id: "0dce0f44-b8a5-4f30-91d4-5f6b0c221bf0"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/executions:
    get:
      tags:
        - Executions
      summary: List executions
      description: |
        Retrieve execution history for the authenticated API key with optional filters.
        Model prediction terminal statuses include `success`, `failed`, and `cancelled`;
        workflow executions can surface `running`, `completed`, `failed`, or `cancelled`
        depending on the originating backend.
      operationId: listExecutions
      security:
        - ApiKeyAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          description: Maximum number of executions to return
          example: 20
        - name: offset
          in: query
          schema:
            type: integer
            minimum: 0
            default: 0
          description: Number of executions to skip for pagination
          example: 0
        - name: model
          in: query
          schema:
            type: string
          description: Filter executions by model slug
          example: "flux-1-1-pro"
        - name: status
          in: query
          schema:
            type: string
          description: Comma-separated status filter
          example: "success,failed"
        - name: workflow_id
          in: query
          schema:
            type: string
          description: Filter by workflow ID
          example: "wf_abc123"
        - name: workflow_execution_id
          in: query
          schema:
            type: string
          description: Filter by workflow execution ID
          example: "wf_exec_abc123"
        - name: from
          in: query
          schema:
            type: string
            format: date-time
          description: Start of creation time window, as RFC 3339
          example: "2026-01-01T00:00:00Z"
        - name: to
          in: query
          schema:
            type: string
            format: date-time
          description: End of creation time window, as RFC 3339
          example: "2026-01-31T23:59:59Z"
      responses:
        '200':
          description: List of executions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListExecutionsResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/chat/completions:
    post:
      tags:
        - OpenAI Compatibility
      summary: Create Chat Completion
      description: |
        OpenAI-compatible chat completions endpoint backed by Eachlabs LLM Router. Use
        `Authorization: Bearer <API_KEY>` and point an OpenAI-compatible client at
        `https://api.eachlabs.ai/v1`.

        This endpoint uses the OpenAI-compatible error envelope rather than the REST
        error body described in the global contract notes.
      operationId: createChatCompletion
      security:
        - BearerAuth: []
      parameters:
        - name: X-Eachlabs-Webhook-Url
          in: header
          required: false
          schema:
            type: string
            format: uri
          description: Optional webhook URL for asynchronous result delivery.
          example: "https://your-app.com/webhook"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatCompletionRequest'
            example:
              model: "openai/gpt-4o-mini"
              messages:
                - role: "user"
                  content: "Write a one sentence caption."
      responses:
        '200':
          description: OpenAI-compatible chat completion. Requests with `stream: true` return Server-Sent Events.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatCompletionResponse'
            text/event-stream:
              schema:
                type: string
                description: |
                  Server-Sent Events stream for requests with `stream: true`.
                  Events use OpenAI-compatible `chat.completion.chunk` payloads
                  and the stream terminates with `data: [DONE]`.
              example: |
                data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"A cinematic"},"finish_reason":null}]}

                data: [DONE]
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OpenAIErrorResponse'
        '401':
          description: Authentication failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OpenAIErrorResponse'
        '402':
          description: Insufficient balance
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OpenAIErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OpenAIErrorResponse'

  /v1/workflows:
    post:
      tags:
        - Workflows
      summary: Create workflow
      description: |
        Create a new workflow together with its initial version. This route forwards to
        workflows-engine, which generates the slug, persists the workflow + version, and
        provisions the execution state machine.

        Only `name` is required. `definition` is the workflow DSL as an arbitrary JSON
        object (steps, input_schema, metadata). Categories, tags, and visibility are not
        accepted on this route — workflows-engine applies its defaults (categories
        `["general"]`, visibility `private`).
      operationId: createWorkflow
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WorkflowCreateRequest'
            example:
              name: "My Workflow"
              definition:
                version: "1.0"
                steps: []
      responses:
        '201':
          description: Workflow created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowDetailResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
        '502':
          $ref: '#/components/responses/BadGateway'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/workflows/trigger/{workflowID}/{versionID}:
    post:
      tags:
        - Workflows
      summary: Trigger workflow execution
      description: |
        Start an asynchronous workflow execution through api-service. This route forwards to
        workflows-engine and requires callers to pin the workflow version in the path.

        The immediate response only confirms the workflow was queued. Downstream workflow
        lifecycle statuses are `running`, `completed`, `failed`, and `cancelled`.
        api-service does not expose workflows-engine's bulk trigger route.
      operationId: triggerWorkflowExecution
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workflowID
          in: path
          required: true
          schema:
            type: string
          description: Workflow ID or slug to execute.
          example: "wf_abc123"
        - name: versionID
          in: path
          required: true
          schema:
            type: string
          description: Workflow version ID to execute.
          example: "v1"
        - name: webhook_url
          in: query
          required: false
          schema:
            type: string
            format: uri
          description: Optional compatibility query parameter for execution completion webhooks. The request body field takes precedence when both are present.
          example: "https://your-app.com/webhook"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WorkflowTriggerRequest'
            example:
              inputs:
                prompt: "A cinematic establishing shot of a mountain lake"
              webhook_url: "https://your-app.com/webhook"
              webhook_secret: "your-secret-key"
      responses:
        '202':
          description: Workflow execution queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowTriggerResponse'
              example:
                execution_id: "exec_xyz789"
                status: "queued"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
        '502':
          $ref: '#/components/responses/BadGateway'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/files/{id}:
    delete:
      tags:
        - Uploads
      summary: Delete uploaded file
      description: |
        Delete a previously uploaded file by its ID. Returns 204 on success.
        Proxies the delete request to storage-service and passes through its
        status codes: 404 when the file does not exist, 409 when deletion is
        not permitted for the file's current state.
      operationId: deleteFile
      security:
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: File ID returned by the presign upload endpoint
          example: "0dce0f44-b8a5-4f30-91d4-5f6b0c221bf0"
      responses:
        '204':
          description: File deleted successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/webhooks:
    get:
      tags:
        - Webhooks
      summary: List webhooks
      description: Retrieve a paginated list of webhooks for the authenticated organization
      operationId: listWebhooks
      security:
        - ApiKeyAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
          description: Maximum number of webhooks to return
          example: 50
        - name: offset
          in: query
          schema:
            type: integer
            minimum: 0
            default: 0
          description: Number of webhooks to skip for pagination
          example: 0
      responses:
        '200':
          description: List of webhooks
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListWebhooksResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/webhooks/{execution_id}:
    get:
      tags:
        - Webhooks
      summary: Get webhook by execution ID
      description: Retrieve details of a specific webhook including delivery attempts by execution ID
      operationId: getWebhook
      security:
        - ApiKeyAuth: []
      parameters:
        - name: execution_id
          in: path
          required: true
          schema:
            type: string
          description: Execution ID for tracking the webhook
          example: "abc123-def456-ghi789"
      responses:
        '200':
          description: Webhook details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookWithAttempts'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'


components:
  headers:
    RequestId:
      description: Request correlation ID. Echoes the incoming `X-Request-Id` header or a generated ID.
      schema:
        type: string
      example: "123e4567-e89b-12d3-a456-426614174000"
    RetryAfter:
      description: Optional retry delay returned by rate-limit or upstream retry responses when available.
      schema:
        oneOf:
          - type: integer
            minimum: 0
          - type: string
      example: 30

  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key for authentication
    BearerAuth:
      type: http
      scheme: bearer
      description: API key passed as an Authorization Bearer token for OpenAI-compatible endpoints

  schemas:
    Model:
      type: object
      properties:
        title:
          type: string
          example: "Nano Banana Pro Edit"
        slug:
          type: string
          example: "nano-banana-pro-edit"
        version:
          type: string
          deprecated: true
          description: "Deprecated. Always returns \"0.0.1\". Will be removed in a future version."
          example: "0.0.1"
        output_type:
          type: string
          example: "array"
        request_schema:
          type: object
          description: |
            JSON Schema for API requests to this model.
            Defines the structure, types, and validation rules for making requests.
          example:
            type: object
            required: ["prompt"]
            properties:
              prompt:
                type: string
                minLength: 10
                maxLength: 500
              num_images:
                type: integer
                minimum: 1
                maximum: 4
                default: 1

    ModelDetail:
      type: object
      properties:
        title:
          type: string
          example: "Nano Banana Pro Edit"
        slug:
          type: string
          example: "nano-banana-pro-edit"
        version:
          type: string
          deprecated: true
          description: "Deprecated. Always returns \"0.0.1\". Will be removed in a future version."
          example: "0.0.1"
        output_type:
          type: string
          example: "array"
        request_schema:
          type: object
          description: |
            JSON Schema for API requests to this model.
            Defines the structure, types, and validation rules for making requests.
          example:
            type: object
            required: ["prompt"]
            properties:
              prompt:
                type: string
                minLength: 10
                maxLength: 500

    LLMRouterModelCatalog:
      type: object
      properties:
        slug:
          type: string
          example: "eachlabs-llm-router"
        source:
          type: string
          enum: ["catalog", "odoo_fallback"]
        version:
          type: integer
          example: 1
        updated_at:
          type: string
          example: "2026-05-24"
        model_enum:
          type: array
          items:
            type: string
          example: ["openai/gpt-5.2", "anthropic/claude-sonnet-4-6"]
        request_schema:
          type: object
          description: JSON Schema fragment containing the generated model enum.
        models:
          type: array
          items:
            $ref: '#/components/schemas/LLMRouterCatalogModel'

    LLMRouterCatalogModel:
      type: object
      properties:
        id:
          type: string
          example: "openai/gpt-5.2"
        status:
          type: string
          example: "visible"
        family:
          type: string
          example: "openai"
        canonical_provider:
          type: string
          example: "openrouter"
        canonical_target_model:
          type: string
          example: "openai/gpt-5.2"
        routing_policy:
          type: string
          example: "explicit_route"
        legacy_requesty:
          type: boolean
        title:
          type: string
          example: "GPT-5.2"
        description:
          type: string
        faqs:
          type: array
          items:
            $ref: '#/components/schemas/LLMRouterCatalogFAQ'
        readme_md:
          type: string

    LLMRouterCatalogFAQ:
      type: object
      properties:
        question:
          type: string
        answer:
          type: string

    WorkflowTriggerRequest:
      type: object
      properties:
        inputs:
          type: object
          additionalProperties: true
          description: Input values defined by the workflow version. May be omitted for workflows with no inputs.
          example:
            prompt: "A cinematic establishing shot of a mountain lake"
        webhook_url:
          type: string
          format: uri
          description: Optional webhook URL for workflow execution completion.
          example: "https://your-app.com/webhook"
        webhook_secret:
          type: string
          description: Optional secret used to sign workflow webhook requests.
          example: "your-secret-key"

    WorkflowTriggerResponse:
      type: object
      properties:
        execution_id:
          type: string
          description: Queued workflow execution identifier.
          example: "exec_xyz789"
        status:
          type: string
          enum:
            - queued
          description: Execution queue status returned immediately after triggering.
          example: "queued"

    WorkflowCreateRequest:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          description: Human-readable workflow name. Required.
          example: "My Workflow"
        definition:
          type: object
          additionalProperties: true
          description: Workflow DSL (steps, input_schema, metadata) as a JSON object.
          example:
            version: "1.0"
            steps: []

    WorkflowDetailResponse:
      type: object
      description: The created workflow (summary fields + its versions).
      properties:
        workflow_id:
          type: string
          example: "wf_abc123"
        slug:
          type: string
          example: "my-workflow"
        name:
          type: string
          example: "My Workflow"
        categories:
          type: array
          items:
            type: string
          example: ["video"]
        tags:
          type: array
          items:
            type: string
          example: ["beta"]
        status:
          type: string
          example: "active"
        trigger_count:
          type: integer
          example: 0
        clone_count:
          type: integer
          example: 0
        is_public:
          type: boolean
          example: false
        production:
          type: boolean
          example: false
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        versions:
          type: array
          items:
            $ref: '#/components/schemas/WorkflowVersionDetail'

    WorkflowVersionDetail:
      type: object
      properties:
        version_id:
          type: string
          example: "1.0"
        slug:
          type: string
          example: "my-workflow"
        published:
          type: boolean
          example: true
        locked:
          type: boolean
          example: false
        production:
          type: boolean
          example: false
        allowed_to_share:
          type: boolean
          example: false
        trigger_count:
          type: integer
          example: 0
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        definition:
          type: object
          additionalProperties: true
          description: The version's workflow DSL.
        status:
          type: string
          example: "active"
        public_description:
          type: string
          nullable: true
        public_thumbnail:
          type: string
          nullable: true
        public_example_output:
          type: string
          nullable: true

    Error:
      type: object
      description: |
        Backward-compatible REST error body. Endpoint-specific handlers may return only
        `error`; shared error paths may also include `status` and `details`.
      properties:
        status:
          type: integer
          description: HTTP status code, when returned by the shared error handler.
          example: 400
        error:
          type: string
          description: Safe user-facing error message.
          example: "error message"
        details:
          nullable: true
          description: Optional validation or request details for client errors.
      required:
        - error

    CreatePredictionRequest:
      type: object
      required:
        - model
        - input
      properties:
        model:
          type: string
          description: Model slug or identifier
          example: "flux-1-1-pro"
        version:
          type: string
          deprecated: true
          description: "Deprecated. This field is ignored. Kept for backwards compatibility."
          example: "0.0.1"
        input:
          type: object
          description: Input parameters for the prediction
          additionalProperties: true
          example:
            prompt: "A beautiful sunset over the ocean with vibrant colors"
            aspect_ratio: "16:9"
        webhook_url:
          type: string
          format: uri
          description: Optional webhook URL to receive prediction result asynchronously
          example: "https://your-app.com/webhook"
        webhook_secret:
          type: string
          description: Optional secret used to sign webhook requests
          example: "your-secret-key"

    CreatePredictionResponse:
      type: object
      properties:
        status:
          type: string
          description: Status of the prediction creation request
          example: "success"
        message:
          type: string
          description: Human-readable message about the prediction creation
          example: "Prediction created successfully"
        predictionID:
          type: string
          description: Unique prediction identifier
          example: "abc123-def456-ghi789"

    RunPredictionResponse:
      type: object
      properties:
        status:
          type: string
          enum:
            - success
            - failed
            - cancelled
          description: Final terminal status of the synchronous prediction
          example: "success"
        message:
          type: string
          description: Human-readable message
          example: "Prediction completed successfully"
        predictionID:
          type: string
          description: Unique prediction identifier
          example: "abc123-def456-ghi789"
        output:
          description: Prediction output, when available
          nullable: true
          oneOf:
            - type: string
            - type: array
            - type: object
        error:
          type: string
          description: User-facing error message when the prediction failed
          example: ""

    CancelPredictionResponse:
      type: object
      properties:
        status:
          type: string
          enum:
            - cancelled
          description: Cancellation status returned when the cancellation request succeeds
          example: "cancelled"
        message:
          type: string
          description: Human-readable cancellation message
          example: "Prediction canceled successfully"
        predictionID:
          type: string
          description: Unique prediction identifier
          example: "abc123-def456-ghi789"

    Prediction:
      type: object
      properties:
        id:
          type: string
          description: Unique prediction identifier
          example: "abc123-def456-ghi789"
        input:
          type: object
          description: Input parameters used for the prediction
          additionalProperties: true
          example:
            prompt: "A beautiful sunset over the ocean with vibrant colors"
            aspect_ratio: "16:9"
        status:
          type: string
          description: |
            Current status of the prediction. `starting` and `processing` are
            non-terminal. `success`, `failed`, and `cancelled` are terminal.
          enum:
            - starting
            - processing
            - success
            - failed
            - cancelled
          example: "success"
        output:
          description: Prediction output (type varies based on model)
          oneOf:
            - type: string
            - type: array
            - type: object
          example: "https://storage.example.com/predictions/abc123/image.jpg"
        logs:
          type: string
          nullable: true
          description: Execution logs from the prediction
          example: null
        metrics:
          type: object
          description: Performance and cost metrics
          properties:
            predict_time:
              type: number
              description: Time taken for prediction in seconds
              example: 12.5
            cost:
              type: number
              description: Cost of the prediction in USD
              example: 0.05
        urls:
          type: object
          description: Related API endpoints
          properties:
            cancel:
              type: string
              format: uri
              description: URL to cancel the prediction
              example: "https://api.eachlabs.ai/v1/prediction/abc123-def456-ghi789/cancel"
            get:
              type: string
              format: uri
              description: URL to get the prediction
              example: "https://api.eachlabs.ai/v1/prediction/abc123-def456-ghi789"

    PresignUploadRequest:
      type: object
      required:
        - content_type
      properties:
        content_type:
          type: string
          description: MIME type of the file to upload
          example: "image/png"
        file_type:
          type: string
          description: High-level file category
          enum:
            - image
            - video
            - audio
            - other
          example: "image"
        ttl_days:
          type: integer
          nullable: true
          description: Optional file TTL tier in days, when enabled for the caller
          example: 30

    PresignUploadResponse:
      type: object
      properties:
        id:
          type: string
          description: Upload identifier
          example: "0dce0f44-b8a5-4f30-91d4-5f6b0c221bf0"
        presigned_url:
          type: string
          format: uri
          description: Short-lived URL for uploading raw file bytes
          example: "https://eachlabs-storage.s3.amazonaws.com/uploads/...?X-Amz-Signature=..."
        public_url:
          type: string
          format: uri
          description: Public URL to pass as a model input after upload succeeds
          example: "https://cdn-us.eachlabs.ai/uploads/0dce0f44-b8a5-4f30-91d4-5f6b0c221bf0.png"
        expires_at:
          type: string
          format: date-time
          description: Presigned URL expiration time
          example: "2026-04-27T18:15:00Z"
        required_headers:
          type: object
          description: Headers that must be sent verbatim with the upload PUT request
          additionalProperties:
            type: string
          example:
            x-amz-meta-file-id: "0dce0f44-b8a5-4f30-91d4-5f6b0c221bf0"

    ExecutionSummary:
      type: object
      properties:
        id:
          type: string
          description: Execution identifier
          example: "exec_abc123"
        model:
          type: string
          description: Model slug
          example: "flux-1-1-pro"
        status:
          type: string
          description: |
            Execution status. Model prediction terminal statuses include `success`,
            `failed`, and `cancelled`; workflow executions can surface `running`,
            `completed`, `failed`, or `cancelled` depending on the originating backend.
          example: "success"
        workflow_id:
          type: string
          nullable: true
          description: Workflow ID, when execution was triggered by a workflow
          example: "wf_abc123"
        workflow_execution_id:
          type: string
          nullable: true
          description: Workflow execution ID, when execution was triggered by a workflow
          example: "wf_exec_abc123"
        execution_cost:
          type: number
          description: Execution cost in USD
          example: 0.05
        run_time:
          type: number
          description: Runtime in seconds
          example: 12.5
        created_at:
          type: string
          format: date-time
          description: Creation timestamp
          example: "2026-01-01T00:00:00Z"
        started_at:
          type: string
          format: date-time
          nullable: true
          description: Start timestamp
          example: "2026-01-01T00:00:02Z"
        completed_at:
          type: string
          format: date-time
          nullable: true
          description: Completion timestamp
          example: "2026-01-01T00:00:15Z"
        output:
          type: string
          nullable: true
          description: Primary execution output, when available
          example: "https://storage.example.com/predictions/abc123/image.jpg"

    ListExecutionsResponse:
      type: object
      properties:
        executions:
          type: array
          items:
            $ref: '#/components/schemas/ExecutionSummary'
        total_count:
          type: integer
          description: Total number of matching executions
          example: 123
        offset:
          type: integer
          description: Applied offset
          example: 0
        limit:
          type: integer
          description: Applied limit
          example: 20
      required:
        - executions
        - total_count
        - offset
        - limit

    ChatCompletionRequest:
      type: object
      description: OpenAI-compatible chat completions request body
      required:
        - model
        - messages
      additionalProperties: true
      properties:
        model:
          type: string
          description: LLM model identifier
          example: "openai/gpt-4o-mini"
        messages:
          type: array
          items:
            type: object
            additionalProperties: true
          description: OpenAI-compatible chat messages
          example:
            - role: "user"
              content: "Write a one sentence caption."
        stream:
          type: boolean
          description: Set to `true` to receive OpenAI-compatible SSE chunks (`text/event-stream`) instead of a buffered JSON response.
          example: false
        stream_options:
          type: object
          nullable: true
          additionalProperties: true
          description: Optional OpenAI-compatible streaming options. When `include_usage` is true, the stream may include a final usage chunk before `[DONE]`.
          properties:
            include_usage:
              type: boolean
              description: Include token usage in the streamed response when provider usage data is available.
              example: true

    ChatCompletionResponse:
      type: object
      description: OpenAI-compatible chat completions response
      properties:
        id:
          type: string
          example: "chatcmpl-abc123"
        object:
          type: string
          example: "chat.completion"
        created:
          type: integer
          format: int64
          example: 1773651600
        model:
          type: string
          example: "eachlabs-llm-router"
        choices:
          type: array
          items:
            type: object
            properties:
              index:
                type: integer
                example: 0
              message:
                type: object
                properties:
                  role:
                    type: string
                    example: "assistant"
                  content:
                    type: string
                    example: "A cinematic sunset over the ocean."
              finish_reason:
                type: string
                example: "stop"
        usage:
          type: object
          nullable: true
          properties:
            prompt_tokens:
              type: integer
              example: 12
            completion_tokens:
              type: integer
              example: 8
            total_tokens:
              type: integer
              example: 20

    OpenAIErrorResponse:
      type: object
      description: OpenAI-compatible error envelope used only by `/v1/chat/completions`.
      properties:
        error:
          type: object
          properties:
            message:
              type: string
              example: "Invalid API key"
            type:
              type: string
              example: "authentication_error"
            param:
              type: string
              example: "model"
            code:
              type: string
              example: "invalid_api_key"

    Webhook:
      type: object
      description: Webhook information without attempts
      properties:
        execution_id:
          type: string
          nullable: true
          description: Execution identifier for tracking
          example: "abc123-def456-ghi789"
        url:
          type: string
          format: uri
          description: Target URL for webhook delivery
          example: "https://api.example.com/webhook"
        request:
          type: string
          description: Original request payload
          example: "{\"event\":\"prediction.completed\"}"
        headers:
          type: object
          description: Headers included in the webhook request
          additionalProperties:
            type: string
          example:
            "Content-Type": "application/json"
        source:
          type: string
          description: Service that triggered the webhook
          example: "api-gateway"
        created_at:
          type: string
          format: date-time
          description: When the webhook was created
          example: "2025-12-14T10:30:00Z"
      required:
        - url
        - request
        - source
        - created_at

    WebhookWithAttempts:
      allOf:
        - $ref: '#/components/schemas/Webhook'
        - type: object
          properties:
            attempts:
              type: array
              description: Delivery attempts for this webhook
              items:
                $ref: '#/components/schemas/WebhookAttempt'

    WebhookAttempt:
      type: object
      description: Individual webhook delivery attempt
      properties:
        status:
          type: string
          enum:
            - PENDING
            - SUCCESS
            - FAILED
          description: Status of the delivery attempt
          example: "SUCCESS"
        status_code:
          type: integer
          nullable: true
          description: HTTP status code from target URL
          example: 200
        response:
          type: string
          nullable: true
          description: Response body from target URL
          example: "{\"received\":true}"
        error_message:
          type: string
          nullable: true
          description: Error message if delivery failed
          example: "connection timeout"
        created_at:
          type: string
          format: date-time
          description: When the attempt was made
          example: "2025-12-14T10:30:05Z"
      required:
        - status
        - created_at

    ListWebhooksResponse:
      type: object
      properties:
        webhooks:
          type: array
          items:
            $ref: '#/components/schemas/Webhook'
        limit:
          type: integer
          description: Applied limit
          example: 50
        offset:
          type: integer
          description: Applied offset
          example: 0
        total_count:
          type: integer
          description: Total number of matching webhooks
          example: 123
      required:
        - webhooks
        - limit
        - offset
        - total_count

  responses:
    BadRequest:
      description: Bad request
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "slug parameter is required"

    NotFound:
      description: Resource not found
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "Failed to fetch model: model not found"

    Unauthorized:
      description: Authentication required or invalid credentials
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "Invalid or missing API key"

    PaymentRequired:
      description: Insufficient balance
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "insufficient balance"

    Forbidden:
      description: Forbidden
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "forbidden"

    Conflict:
      description: Conflict with current resource state
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "conflict"

    RequestTimeout:
      description: Upstream request timed out
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "Request timeout"

    UnprocessableEntity:
      description: Request body could not be processed
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "invalid request body"

    BadGateway:
      description: Upstream service unavailable
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "workflows-engine unavailable"

    InternalServerError:
      description: Internal server error
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: "Failed to fetch models: internal error"
