openapi: 3.1.0
info:
  title: kLog Ingest API
  version: 0.1.0
  description: Contrato REST exclusivo para ingesta de eventos. Un evento por POST.
paths:
  /v1/ingest/{tenantId}/events:
    post:
      operationId: ingestEvent
      summary: Ingiere un evento en el tenant
      security:
        - userJWT: []
        - userCookie: []
        - apiToken: []
      parameters:
        - name: tenantId
          in: path
          required: true
          schema:
            type: string
            format: uuid
            pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
            description: UUIDv7 público; nunca corresponde al ID incremental interno.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/IngestEventInput"
      responses:
        "201":
          description: Evento persistido
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Event"
        "202":
          description: Evento descartado por un transform de tipo filter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IngestDropped"
        "400":
          description: Entrada inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/APIError"
        "401":
          description: No autenticado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/APIError"
        "403":
          description: Sin permiso events.write o fuera del scope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/APIError"
        "402":
          description: Cuota del plan superada (BILLING_MODE=cloud)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/APIError"
        "429":
          description: Cuota de ingesta superada
          headers:
            Retry-After:
              description: Segundos hasta poder reintentar
              schema:
                type: integer
            X-RateLimit-Limit:
              schema:
                type: integer
            X-RateLimit-Remaining:
              schema:
                type: integer
            X-RateLimit-Reset:
              schema:
                type: integer
                description: Epoch Unix del fin de la ventana
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/APIError"
components:
  securitySchemes:
    userJWT:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Access JWT de usuario; sub contiene su UUIDv7 y su sesión debe seguir activa.
    userCookie:
      type: apiKey
      in: cookie
      name: klog_access
    apiToken:
      type: http
      scheme: bearer
      bearerFormat: klog_api_token
      description: Token opaco ligado a un proyecto y al rol device.
  schemas:
    IngestEventInput:
      type: object
      required: [message]
      additionalProperties: false
      properties:
        applicationId:
          type: string
          format: uuid
          pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
          description: Obligatorio con API token; la aplicación debe pertenecer a su proyecto.
        level:
          type: string
          description: Severidad. Vacío → info. Alias warning→warn, err/fatal/critical→error, trace→debug.
          enum: [debug, info, warn, error, warning, err, fatal, critical, trace]
        traceId:
          type: string
          maxLength: 128
          description: Identificador de traza opcional.
        message:
          type: string
          minLength: 1
          maxLength: 32768
        payload:
          description: Objeto JSON. Si falta, el servidor usa {}.
          default: {}
        tags:
          type: array
          maxItems: 50
          items:
            type: string
            minLength: 1
            maxLength: 100
    Event:
      type: object
      required: [id, message, payload, tags, createdAt]
      properties:
        id:
          type: string
          format: uuid
          pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
        applicationId:
          type: string
          format: uuid
          pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
        level:
          type: string
          enum: [debug, info, warn, error]
        message:
          type: string
        payload: {}
        tags:
          type: array
          items:
            type: string
        createdAt:
          type: string
          format: date-time
    IngestDropped:
      type: object
      required: [dropped, id]
      additionalProperties: false
      properties:
        dropped:
          type: boolean
          const: true
        id:
          type: string
          format: uuid
    APIError:
      type: object
      required: [error]
      additionalProperties: false
      properties:
        error:
          type: string
