openapi: 3.1.0
info:
  title: Raise Engine Operator Onboarding API
  version: 0.1.0
  description: >-
    PRD-canonical B2B onboarding contract. The api_v2 name is an internal source-code
    boundary; public HTTP paths are intentionally unversioned.
servers:
  - url: /
security:
  - sessionCookie: []
  - secureSessionCookie: []
paths:
  /api/operators/{operatorId}/onboarding:
    parameters:
      - $ref: "#/components/parameters/OperatorId"
    get:
      operationId: getOperatorOnboarding
      summary: Get operator onboarding state
      description: Members of the existing operator tenant may inspect its redacted onboarding state.
      responses:
        "200":
          description: Current onboarding state. Credential secrets and hashes are never returned.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OnboardingResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/OperatorNotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /api/operators/{operatorId}/credentials:
    parameters:
      - $ref: "#/components/parameters/OperatorId"
    post:
      operationId: createOperatorSandboxCredential
      summary: Create the operator's first sandbox API credential
      description: >-
        Creates one active sandbox credential. The complete API key is returned once;
        only the independent 43-character secret component is hashed. Requires an
        owner/admin Better Auth session and an Origin matching the request URL origin.
      responses:
        "201":
          description: One-time credential response.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CredentialCreatedResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ForbiddenOrigin"
        "404":
          $ref: "#/components/responses/OperatorNotFound"
        "409":
          description: An active sandbox credential already exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          $ref: "#/components/responses/InternalError"
  /api/operators/{operatorId}/wallet-configuration:
    parameters:
      - $ref: "#/components/parameters/OperatorId"
    put:
      operationId: updateOperatorWalletConfiguration
      summary: Create or replace seamless-wallet configuration
      description: >-
        Stores the operator wallet callback, casino URL, allowed origin, and encrypted
        callback-signing secret. First creation expects version 0 and a secret. On later
        updates an omitted secret is preserved; a supplied secret replaces it. Clearing
        the secret is unsupported. Requires a same-origin owner/admin session.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WalletConfigurationInput"
      responses:
        "200":
          description: Redacted configuration with its incremented version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WalletConfigurationResponse"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ForbiddenOrigin"
        "404":
          $ref: "#/components/responses/OperatorNotFound"
        "409":
          description: expectedConfigurationVersion is stale.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          $ref: "#/components/responses/InternalError"
  /api/operators/{operatorId}/connection-tests:
    parameters:
      - $ref: "#/components/parameters/OperatorId"
    post:
      operationId: testOperatorConnectionConfiguration
      summary: Run local onboarding configuration checks
      description: >-
        This initial slice does not make outbound requests or perform wallet operations.
        Reachability, response-format, and representative-operation checks are returned
        as not_run/probe_not_implemented. A successful local result is not production-ready
        and does not verify or activate the integration.
      responses:
        "200":
          description: Fixed-order local and deferred external checks.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConnectionTestResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ForbiddenOrigin"
        "404":
          $ref: "#/components/responses/OperatorNotFound"
        "500":
          $ref: "#/components/responses/InternalError"
components:
  securitySchemes:
    sessionCookie:
      type: apiKey
      in: cookie
      name: better-auth.session_token
      description: Better Auth session cookie used for local HTTP development.
    secureSessionCookie:
      type: apiKey
      in: cookie
      name: __Secure-better-auth.session_token
      description: Better Auth automatically uses this secure-prefixed cookie on HTTPS deployments.
  parameters:
    OperatorId:
      name: operatorId
      in: path
      required: true
      description: The existing aggregator_studio.id operator-tenant identifier.
      schema:
        type: string
        format: uuid
  responses:
    InvalidRequest:
      description: Invalid JSON or request fields.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unauthorized:
      description: A valid Better Auth session is required.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    ForbiddenOrigin:
      description: Missing, malformed, or cross-origin Origin on an unsafe request.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    OperatorNotFound:
      description: Operator not found or not visible to the caller.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    InternalError:
      description: Sanitized internal failure.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
  schemas:
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              enum:
                - invalid_json
                - invalid_request
                - unauthorized
                - forbidden_origin
                - operator_not_found
                - active_credential_exists
                - configuration_version_conflict
                - internal_error
            message:
              type: string
            details: {}
    CredentialMetadata:
      type: object
      required: [id, environment, keyPrefix, lastFour, status, createdAt]
      properties:
        id: { type: string, format: uuid }
        environment: { type: string, const: sandbox }
        keyPrefix:
          type: string
          description: Public token portion in the exact form raise_sandbox_<credential UUID>.
        lastFour: { type: string, minLength: 4, maxLength: 4 }
        status: { type: string, const: active }
        createdAt: { type: string, format: date-time }
        revokedAt: { type: [string, "null"], format: date-time }
    CredentialCreatedResponse:
      type: object
      required: [data]
      properties:
        data:
          allOf:
            - $ref: "#/components/schemas/CredentialMetadata"
            - type: object
              required: [operatorId, apiKey]
              properties:
                operatorId: { type: string, format: uuid }
                apiKey:
                  type: string
                  pattern: "^raise_sandbox_[0-9a-f-]{36}\\.[A-Za-z0-9_-]{43}$"
                  description: Returned only once when the credential is created.
    WalletConfigurationInput:
      type: object
      additionalProperties: false
      required:
        - expectedConfigurationVersion
        - callbackBaseUrl
        - casinoUrl
        - allowedOrigin
        - callbackAuthentication
      properties:
        expectedConfigurationVersion: { type: integer, minimum: 0 }
        callbackBaseUrl: { type: string, format: uri, pattern: "^https://" }
        casinoUrl: { type: string, format: uri, pattern: "^https://" }
        allowedOrigin:
          type: string
          format: uri
          pattern: "^https://[^/]+$"
          description: Must exactly match the casinoUrl origin.
        callbackAuthentication:
          type: object
          additionalProperties: false
          required: [type]
          properties:
            type: { type: string, const: hmac_sha256 }
            secret:
              type: string
              minLength: 32
              writeOnly: true
              description: Required on first configuration; omitted later to preserve the existing secret.
    WalletConfiguration:
      type: object
      required:
        - walletMethod
        - callbackBaseUrl
        - casinoUrl
        - allowedOrigin
        - callbackAuthentication
        - configurationVersion
      properties:
        walletMethod: { type: string, const: seamless }
        callbackBaseUrl: { type: string, format: uri }
        casinoUrl: { type: string, format: uri }
        allowedOrigin: { type: string, format: uri }
        callbackAuthentication:
          type: object
          required: [type, configured]
          properties:
            type: { type: string, const: hmac_sha256 }
            configured: { type: boolean, const: true }
        configurationVersion: { type: integer, minimum: 1 }
    WalletConfigurationResponse:
      type: object
      required: [data]
      properties:
        data: { $ref: "#/components/schemas/WalletConfiguration" }
    OnboardingResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [operator, state, productionReady, credential, walletConfiguration]
          properties:
            operator:
              type: object
              required: [id, name]
              properties:
                id: { type: string, format: uuid }
                name: { type: string }
            state:
              type: string
              description: credential_required wins when both credential and configuration are absent.
              enum: [credential_required, configuration_required, local_test_available]
            productionReady: { type: boolean, const: false }
            credential:
              oneOf:
                - $ref: "#/components/schemas/CredentialMetadata"
                - type: "null"
            walletConfiguration:
              oneOf:
                - $ref: "#/components/schemas/WalletConfiguration"
                - type: "null"
    ConnectionCheck:
      type: object
      required: [name, status, code, message]
      properties:
        name:
          type: string
          enum:
            - active_sandbox_credential
            - callback_configuration
            - casino_origin
            - callback_reachability
            - response_format
            - representative_wallet_operations
        status: { type: string, enum: [passed, failed, not_run] }
        code: { type: string }
        message: { type: string }
    ConnectionTestResponse:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [state, productionReady, checks]
          properties:
            state: { type: string, enum: [incomplete, local_configuration_complete] }
            productionReady: { type: boolean, const: false }
            checks:
              type: array
              minItems: 6
              maxItems: 6
              items: { $ref: "#/components/schemas/ConnectionCheck" }
