openapi: 3.1.0

info:
  title: EU GPT — Public API
  description: |
    External-facing API for programmatic access to EU GPT.

    Authenticate every request with an API key issued from the EU GPT web app:

    ```
    Authorization: Bearer eugpt_<token>
    ```

    Keys are managed in the chat UI (Settings → API Keys). They have no
    rotating refresh — revoke and reissue when needed.
  version: "1.0.0"
  contact:
    name: EU GPT
    url: https://eugpt.nl

servers:
  - url: https://chat.eugpt.ai
    description: Production
  - url: https://demo.eugpt.ai
    description: Acceptance / demo
  - url: https://test.eugpt.ai
    description: Staging
  - url: http://localhost:8000
    description: Local development (eugpt-python)

security:
  - apiKey: []

tags:
  - name: Responses
    description: Generate AI responses, optionally inside a conversation thread.

paths:
  /v1/responses:
    post:
      tags: [Responses]
      operationId: createResponse
      summary: Create a response
      description: |
        Generate an AI response from a user message. Two modes:

        - **Streaming** (`stream: true`, default) — returns Server-Sent Events
          with OpenAI-compatible event payloads. Each event is delivered as
          `event: message\ndata: <json>\n\n`. Stream completion is signalled
          by a `response.completed` event.
        - **Non-streaming** (`stream: false`) — collects all output and
          returns a single JSON document.

        Conversations are optional. Omit `conversation_id` for stateless
        requests; pass an existing UUID to append to a thread that belongs to
        the authenticated user. Tools (web search, web fetch, calculator,
        etc.) are auto-approved for API requests — there is no interactive
        approval gate.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateResponseRequest'
      responses:
        '200':
          description: |
            Either an SSE stream (`stream: true`) or a single JSON response
            (`stream: false`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResponseObject'
            text/event-stream:
              schema:
                $ref: '#/components/schemas/SseEventStream'
        '400':
          description: Input message is required or otherwise invalid.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '401':
          description: Missing, expired, or revoked API key.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '403':
          description: |
            License or organization policy denies the request — for example,
            user is not a member of the requested project, or the
            organization's plan disallows the operation.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '404':
          description: Referenced conversation or project does not exist.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '429':
          description: Daily or monthly token usage limit exceeded.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '500':
          description: Internal error during response generation.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: eugpt_<token>
      description: |
        EU GPT API key. Issue from the web app; pass as
        `Authorization: Bearer eugpt_<token>`.

  schemas:
    CreateResponseRequest:
      type: object
      required: [input]
      properties:
        model:
          type: string
          default: auto
          description: |
            Accepted for OpenAI-SDK compatibility but ignored — the router
            always selects the model. Pass `"auto"` (the default and only
            meaningful value); any concrete id you send has no effect. The
            actual model used is reported in the response `model` field.
          example: auto
        input:
          description: |
            Either a plain string (single user message) or a list of
            structured `ContentItem` objects for multi-modal / file input.
          oneOf:
            - type: string
              example: What is the capital of France?
            - type: array
              items: { $ref: '#/components/schemas/ContentItem' }
        stream:
          type: boolean
          default: true
          description: |
            Stream as SSE (`true`, default) or return a single JSON response
            (`false`).
        instructions:
          type: string
          nullable: true
          description: System prompt for this response (or conversation, if new).
        conversation_id:
          type: string
          format: uuid
          nullable: true
          description: |
            Append to an existing conversation. Omit for a stateless request —
            an ephemeral conversation is created and returned in the response.
        project_id:
          type: string
          format: uuid
          nullable: true
          description: |
            Project context for RAG retrieval. Caller must be a member of the
            project.

    ContentItem:
      type: object
      required: [content]
      properties:
        role:
          type: string
          nullable: true
          description: Optional role hint (e.g. `"user"`).
        type:
          type: string
          nullable: true
        content:
          type: array
          items: { $ref: '#/components/schemas/InputContent' }

    InputContent:
      oneOf:
        - $ref: '#/components/schemas/InputText'
        - $ref: '#/components/schemas/InputFile'
      discriminator:
        propertyName: type
        mapping:
          input_text: '#/components/schemas/InputText'
          input_file: '#/components/schemas/InputFile'

    InputText:
      type: object
      required: [type, text]
      properties:
        type:
          type: string
          const: input_text
        text:
          type: string

    InputFile:
      type: object
      required: [type, file_id]
      properties:
        type:
          type: string
          const: input_file
        file_id:
          type: string
          format: uuid
          description: |
            Id of a file previously uploaded via the file upload API.

    ResponseObject:
      type: object
      description: "Returned when `stream: false`."
      required: [id, object, created_at, status, model, output, output_text]
      properties:
        id:
          type: string
          format: uuid
        object:
          type: string
          const: response
        created_at:
          type: integer
          description: Unix epoch milliseconds (zero if the chain didn't emit).
        status:
          type: string
          const: completed
        model:
          type: string
          description: Concrete model id used (resolved from `"auto"` when needed).
        output:
          type: array
          description: |
            Structured output items (text blocks, tool calls, etc.) in the
            same shape as the SSE `response.output_item.added` events.
          items:
            type: object
            additionalProperties: true
        output_text:
          type: string
          description: Concatenated text deltas — convenience for plain-text consumers.

    SseEventStream:
      type: string
      format: text/event-stream
      description: |
        Server-Sent Events stream. Each event is one of the OpenAI-compatible
        types below; payloads are JSON in the `data:` line.

        Event types:
          - `response.created` — initial event with response metadata.
          - `response.output_text.delta` — incremental text chunks.
          - `response.output_text.done` — text part finalised.
          - `response.output_item.added` / `response.output_item.done` —
            structured items (tool calls, file references).
          - `response.content_part.added` — additional content parts.
          - `response.completed` — terminal success event.
          - `error` — terminal error event with `{type, code, message}`.

    Error:
      type: object
      properties:
        detail:
          oneOf:
            - type: string
            - type: object
              additionalProperties: true
        error:
          type: object
          properties:
            message: { type: string }
            type: { type: string }
            code: { type: string }
          additionalProperties: true
      additionalProperties: true
