openapi: 3.1.0
info:
  title: secureFlows Integration (hosted login) API
  version: 1.5.4
  description: |
    Browser entrypoint contract for **third-party applications** that use the hosted SafeHook login UI.

    This spec intentionally includes **only `/app/**` endpoints**. All JSON APIs live in the
    **Management API** (served under `/api/v1`).

    ## Deployment
    - **Nginx bundle** (e.g. Docker): UI + static docs on port **8080**; API under `/api/v1`.
    - **Spring only**: typically port **8081**, context path **`/api/v1`** (paths below are absolute from host, e.g. `/api/v1/workspaces`).

    See also: repository **`docs/integration/quickstart.md`**.
  contact:
    name: secureFlows
  license:
    name: Proprietary

externalDocs:
  description: Integration quickstart (Markdown)
  url: ../integration/quickstart.md

tags:
  - name: hosted-ui
    description: Browser entry (Flutter); not a JSON API

paths:
  /app/login:
    get:
      tags: [hosted-ui]
      summary: Hosted SafeHook login (browser)
      description: |
        Returns the **Flutter web** sign-in page. Optional query parameters enable the **redirect**
        integration:

        - **`app_id`**: application id registered as `appId` (alias **`client_id`**).
        - **`redirect_uri`**: must exactly match an allowlisted URI for that application.
        - **`state`**: opaque value echoed as `state` on the final redirect (CSRF binding).

        After successful sign-in, the client is redirected to `redirect_uri?firebaseToken=...&state=...`.
      operationId: hostedLogin
      parameters:
        - name: app_id
          in: query
          required: false
          schema:
            type: string
          example: acme-web
          description: Registered application id (same as `appId` in Management API)
        - name: client_id
          in: query
          required: false
          schema:
            type: string
          description: Deprecated alias for `app_id`
        - name: redirect_uri
          in: query
          required: false
          schema:
            type: string
            format: uri
          example: https://app.acme.com/auth/callback
        - name: state
          in: query
          required: false
          schema:
            type: string
          example: random-opaque-string
        - name: next
          in: query
          required: false
          schema:
            type: string
          description: Optional in-app route after sign-in when not using redirect integration
      responses:
        "200":
          description: HTML document (Flutter bootstrap)
          content:
            text/html:
              schema:
                type: string
              example: "<!DOCTYPE html>..."

  /app/sessions/login:
    get:
      tags: [hosted-ui]
      summary: Hosted login with server-side session (browser)
      description: |
        Same Flutter sign-in surface as `/app/login`, with a **session-first** integration contract.

        **Query parameters** (all required for the redirect integration, except `state`):

        - **`app_id`**: registered `appId` (alias **`client_id`**).
        - **`redirect_uri`**: third-party callback URL allowlisted for that application.
        - **`workspace_name`**: workspace used when creating the session on the server.
        - **`state`**: optional opaque value echoed as `state` on the final client redirect.
        - **`ttl_seconds`** (optional): forwarded to `/api/v1/auth/callback` — session TTL in seconds
          (default 0 = unlimited; server accepts 0 or 60–604800).
        - **`payload`** (optional): forwarded to `/api/v1/auth/callback` — URL-encoded JSON **object**
          stored as the session payload on create (default `{}`). **Must not** be sent together with
          **`session_token`** (the server responds with `error=invalid_request`).
        - **`session_token`** (optional): forwarded to `/api/v1/auth/callback` — previous session JWT (may be
          expired) to **renew** instead of creating a new session. URL-encode; long JWTs push against URL limits.

        After successful sign-in, the browser is sent to **`GET /api/v1/auth/callback`** (Management API)
        with `firebaseToken`, `client_redirect_uri`, `workspace_name`, `app_id`, `state`, and any of
        `ttl_seconds`, and either **`payload`** (create) or **`session_token`** (renew), not both. The backend
        verifies the Firebase token, then **get-or-creates** an active session or **renews** an existing one,
        and redirects to `redirect_uri` with **`sessionToken`** and **`state`** (no `firebaseToken` exposed
        to the client). When the Firebase token includes **`email`**, the backend best-effort stores it on
        the workspace end-user row (see Management API Firebase email notes for audit, API access log, and
        **`GET /users`**).

        The original flow `/app/login` → `redirect_uri?firebaseToken=...` is unchanged.
      operationId: hostedSessionsLogin
      parameters:
        - name: app_id
          in: query
          required: true
          schema:
            type: string
          example: acme-web
        - name: client_id
          in: query
          required: false
          schema:
            type: string
          description: Deprecated alias for `app_id`
        - name: redirect_uri
          in: query
          required: true
          schema:
            type: string
            format: uri
        - name: workspace_name
          in: query
          required: true
          schema:
            type: string
        - name: state
          in: query
          required: false
          schema:
            type: string
        - name: ttl_seconds
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            maximum: 604800
          description: Session lifetime; forwarded to `/api/v1/auth/callback`
        - name: payload
          in: query
          required: false
          schema:
            type: string
          description: |
            JSON object as a string (e.g. `{"hello":"world"}`), URL-encoded on the wire.
            Forwarded to `/api/v1/auth/callback`. Incompatible with `session_token` on the same request.
        - name: session_token
          in: query
          required: false
          schema:
            type: string
          description: |
            Previous session JWT for renewal; forwarded to `/api/v1/auth/callback`. URL-encode the value.
      responses:
        "200":
          description: HTML document (Flutter bootstrap)
          content:
            text/html:
              schema:
                type: string
