openapi: 3.0.3
info:
  title: Training Pipes API
  version: "1.0.0"
servers:
  - url: https://api.trainingpipes.com
  - url: http://localhost:8080
tags:
  - name: Config
  - name: Projects
  - name: Users
  - name: Connections
  - name: Mounts
  - name: Gateways
  - name: Usage
  - name: Buckets
  - name: Health
  - name: PublicMounts
  - name: Webhooks
  - name: ApiKeys
  - name: Plans
  - name: Billing
  - name: Subscriptions

paths:
  /v1/config:
    get:
      tags: [Config]
      operationId: getConfig
      summary: Get platform configuration
      description: Returns available providers and their supported regions. This is a public endpoint.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/PlatformConfig'}

  /v1/health:
    get:
      tags: [Health]
      operationId: healthCheck
      summary: Liveness probe
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Health'}

  /v1/projects:
    post:
      tags: [Projects]
      operationId: createProject
      summary: Create project (stubbed)
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/ProjectCreate'}
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Project'}
    get:
      tags: [Projects]
      operationId: listProjects
      summary: List projects (stubbed)
      parameters:
        - in: query
          name: page_size
          schema: {type: integer, minimum: 1, maximum: 1000, default: 50}
        - in: query
          name: page_token
          schema: {type: string}
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/ProjectList'}

  /v1/projects/{project_id}:
    parameters:
      - in: path
        name: project_id
        required: true
        schema: {type: string}
    get:
      tags: [Projects]
      operationId: getProject
      summary: Get project (stubbed)
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Project'}
        "404": {description: Not found}
    patch:
      tags: [Projects]
      operationId: updateProject
      summary: Update project (stubbed)
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/ProjectUpdate'}
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Project'}
    delete:
      tags: [Projects]
      operationId: deleteProject
      summary: Delete project (stubbed)
      responses:
        "204": {description: Deleted}

  /v1/users/{user_id}:
    parameters:
      - in: path
        name: user_id
        required: true
        schema: {type: string}
    get:
      tags: [Users]
      operationId: getUser
      summary: Get user profile
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/User'}
        "404": {description: Not found}
    patch:
      tags: [Users]
      operationId: updateUser
      summary: Update user profile
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/UserUpdate'}
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema: {$ref: '#/components/schemas/User'}

  /v1/users/{user_id}/project-memberships:
    parameters:
      - in: path
        name: user_id
        required: true
        schema: {type: string}
    get:
      tags: [Projects]
      operationId: listUserProjectMemberships
      summary: List project memberships for a user
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/ProjectMembershipList'}

  /v1/projects/{project_id}/memberships:
    parameters:
      - in: path
        name: project_id
        required: true
        schema: {type: string}
    get:
      tags: [Projects]
      operationId: listProjectMemberships
      summary: List all memberships (team members) for a project
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/ProjectMembershipList'}

  /v1/projects/{project_id}/connections:
    parameters:
      - in: path
        name: project_id
        required: true
        schema: {type: string}
    post:
      tags: [Connections]
      operationId: createConnection
      summary: Create BYO storage connection (stubbed)
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/ConnectionCreate'}
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Connection'}
    get:
      tags: [Connections]
      operationId: listConnections
      summary: List connections (stubbed)
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/ConnectionList'}

  /v1/connections/{conn_id}:
    parameters:
      - in: path
        name: conn_id
        required: true
        schema: {type: string}
    get:
      tags: [Connections]
      operationId: getConnection
      summary: Get connection (stubbed)
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Connection'}
        "404": {description: Not found}
    delete:
      tags: [Connections]
      operationId: deleteConnection
      summary: Delete a connection
      responses:
        "204": {description: Connection deleted}
        "404": {description: Not found}

  /v1/connections/{conn_id}:verify:
    parameters:
      - in: path
        name: conn_id
        required: true
        schema: {type: string}
    post:
      tags: [Connections]
      operationId: verifyConnection
      summary: Verify connection (stubbed)
      responses:
        "200":
          description: Verification result
          content:
            application/json:
              schema: {$ref: '#/components/schemas/VerifyResult'}

  /v1/projects/{project_id}/buckets:
    parameters:
      - in: path
        name: project_id
        required: true
        schema: {type: string}
    post:
      tags: [Buckets]
      operationId: createBucket
      summary: Create a managed storage bucket
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/BucketCreate'}
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Bucket'}
    get:
      tags: [Buckets]
      operationId: listBuckets
      summary: List managed buckets for a project
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/BucketList'}

  /v1/buckets/{bucket_id}:
    parameters:
      - in: path
        name: bucket_id
        required: true
        schema: {type: string}
    get:
      tags: [Buckets]
      operationId: getBucket
      summary: Get bucket details
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Bucket'}
        "404": {description: Not found}
    delete:
      tags: [Buckets]
      operationId: deleteBucket
      summary: Delete a managed bucket
      responses:
        "204": {description: Deleted}
        "404": {description: Not found}

  /v1/buckets/{bucket_id}/objects:
    parameters:
      - in: path
        name: bucket_id
        required: true
        schema: {type: string}
    get:
      tags: [Buckets]
      operationId: listBucketObjects
      summary: List objects in a managed bucket
      description: Lists objects (files and folder prefixes) using S3 ListObjectsV2 with `/` as the delimiter. Supply a `prefix` to list the contents of a specific folder.
      parameters:
        - in: query
          name: prefix
          schema: {type: string, default: ""}
        - in: query
          name: continuation_token
          schema: {type: string}
        - in: query
          name: max_keys
          schema: {type: integer, minimum: 1, maximum: 1000, default: 200}
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/ObjectList'}
        "404": {description: Not found}

  /v1/connections/{conn_id}/objects:
    parameters:
      - in: path
        name: conn_id
        required: true
        schema: {type: string}
    get:
      tags: [Connections]
      operationId: listConnectionObjects
      summary: List objects in a connected bucket
      description: Lists objects (files and folder prefixes) using S3 ListObjectsV2 with `/` as the delimiter. Supply a `prefix` to list the contents of a specific folder.
      parameters:
        - in: query
          name: prefix
          schema: {type: string, default: ""}
        - in: query
          name: continuation_token
          schema: {type: string}
        - in: query
          name: max_keys
          schema: {type: integer, minimum: 1, maximum: 1000, default: 200}
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/ObjectList'}
        "404": {description: Not found}

  /v1/projects/{project_id}/mounts:
    parameters:
      - in: path
        name: project_id
        required: true
        schema: {type: string}
    post:
      tags: [Mounts]
      operationId: createMount
      summary: Create mount (stubbed)
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/MountCreate'}
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Mount'}
    get:
      tags: [Mounts]
      operationId: listMounts
      summary: List mounts (stubbed)
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/MountList'}

  /v1/mounts:
    get:
      tags: [Mounts]
      operationId: listAllMounts
      summary: List all mounts across all tenants
      parameters:
        - in: query
          name: page_size
          schema: {type: integer, minimum: 1, maximum: 100, default: 20}
        - in: query
          name: page_token
          schema: {type: string}
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/MountList'}

  /v1/mounts/{mount_id}:
    parameters:
      - in: path
        name: mount_id
        required: true
        schema: {type: string}
    get:
      tags: [Mounts]
      operationId: getMount
      summary: Get mount (stubbed)
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Mount'}
        "404": {description: Not found}
    patch:
      tags: [Mounts]
      operationId: updateMount
      summary: Update mount
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/MountUpdate'}
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Mount'}
        "404": {description: Not found}
    delete:
      tags: [Mounts]
      operationId: deleteMount
      summary: Delete mount (stubbed)
      responses:
        "204": {description: Deleted}

  /v1/mounts/{mount_id}:flush:
    parameters:
      - in: path
        name: mount_id
        required: true
        schema: {type: string}
    post:
      tags: [Mounts]
      operationId: flushMount
      summary: Trigger flush (stubbed)
      responses:
        "202": {description: Flush started}

  /v1/mounts/{mount_id}/events:
    parameters:
      - in: path
        name: mount_id
        required: true
        schema: {type: string}
    get:
      tags: [Mounts]
      operationId: streamMountEvents
      summary: SSE stream (stubbed)
      responses:
        "200":
          description: text/event-stream
          content:
            text/event-stream:
              schema:
                type: string
                example: "event: status\ndata: {\"status\":\"ready\"}\n\n"

  /v1/gateways:
    post:
      tags: [Gateways]
      operationId: createGateway
      summary: Create gateway (stubbed)
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/GatewayCreate'}
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Gateway'}

  /v1/gateways/{gateway_id}:attach:
    post:
      tags: [Gateways]
      operationId: attachMount
      parameters:
        - in: path
          name: gateway_id
          required: true
          schema: {type: string}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [mount_id]
              properties:
                mount_id: {type: string}
      responses:
        "204": {description: Attached}

  /v1/gateways/{gateway_id}:detach:
    post:
      tags: [Gateways]
      operationId: detachMount
      parameters:
        - in: path
          name: gateway_id
          required: true
          schema: {type: string}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [mount_id]
              properties:
                mount_id: {type: string}
      responses:
        "204": {description: Detached}

  /v1/projects/{project_id}/usage:
    get:
      tags: [Usage]
      operationId: getUsage
      summary: Get storage usage report for a project
      parameters:
        - in: path
          name: project_id
          required: true
          schema: {type: string}
        - in: query
          name: from
          schema: {type: string, format: date-time}
        - in: query
          name: to
          schema: {type: string, format: date-time}
        - in: query
          name: granularity
          schema: {type: string, enum: [hour, day], default: day}
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/UsageReport'}

  /v1/webhooks/stripe:
    post:
      tags: [Webhooks]
      operationId: handleStripeWebhook
      summary: Handle Stripe webhook events
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Webhook processed

  /v1/projects/{project_id}/portal_session:
    post:
      tags: [Projects]
      operationId: createProjectPortalSession
      summary: Create Stripe billing portal session for project
      parameters:
        - in: path
          name: project_id
          required: true
          schema: {type: string}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/PortalSessionCreate'}
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/PortalSession'}
        "400":
          description: Bad request
        "404":
          description: Project not found

  /v1/projects/{project_id}/subscription:
    get:
      tags: [Projects]
      operationId: getProjectSubscription
      summary: Get the current Stripe subscription state for a project
      parameters:
        - in: path
          name: project_id
          required: true
          schema: {type: string}
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/ProjectSubscription'}
        "404":
          description: Project not found

  /v1/projects/{project_id}/plan:
    post:
      tags: [Projects]
      operationId: changeProjectPlan
      summary: Change the Stripe plan for an existing subscription
      description: |
        Swaps the base plan price of the project's existing Stripe subscription.
        Use this for upgrades/downgrades from an existing paying state. For
        brand-new subscriptions, use `createProjectCheckoutSession` instead.
      parameters:
        - in: path
          name: project_id
          required: true
          schema: {type: string}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/ChangePlanRequest'}
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/ProjectSubscription'}
        "400":
          description: Bad request
        "404":
          description: Project not found
        "409":
          description: Project has no active subscription to change

  /v1/projects/{project_id}/checkout_session:
    post:
      tags: [Projects]
      operationId: createProjectCheckoutSession
      summary: Create Stripe checkout session for project
      parameters:
        - in: path
          name: project_id
          required: true
          schema: {type: string}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/CheckoutSessionCreate'}
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/CheckoutSession'}
        "400":
          description: Bad request
        "404":
          description: Project not found

  /v1/plans:
    get:
      tags: [Projects]
      operationId: listPlans
      summary: List all subscription plans with features (public)
      description: Returns all available subscription plans with their associated features. This is a public endpoint that does not require authentication.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/PlanList'}

  /v1/subscription-features:
    post:
      tags: [Projects]
      operationId: createSubscriptionFeature
      summary: Create subscription feature
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/SubscriptionFeatureCreate'}
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: {$ref: '#/components/schemas/SubscriptionFeature'}

  /v1/subscription-features/{id}:
    parameters:
      - in: path
        name: id
        required: true
        schema: {type: string}
    patch:
      tags: [Projects]
      operationId: updateSubscriptionFeature
      summary: Update subscription feature
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/SubscriptionFeatureUpdate'}
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema: {$ref: '#/components/schemas/SubscriptionFeature'}

  /v1/subscription-plans:
    post:
      tags: [Projects]
      operationId: createSubscriptionPlan
      summary: Create subscription plan
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/SubscriptionPlanCreate'}
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: {$ref: '#/components/schemas/SubscriptionPlan'}

  /v1/subscription-plans/{id}:
    parameters:
      - in: path
        name: id
        required: true
        schema: {type: string}
    patch:
      tags: [Projects]
      operationId: updateSubscriptionPlan
      summary: Update subscription plan
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/SubscriptionPlanUpdate'}
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema: {$ref: '#/components/schemas/SubscriptionPlan'}

  /v1/subscription-plan-features:
    post:
      tags: [Projects]
      operationId: createSubscriptionPlanFeature
      summary: Create subscription plan feature
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/SubscriptionPlanFeatureCreate'}
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: {$ref: '#/components/schemas/SubscriptionPlanFeature'}

  /v1/subscription-plan-features/{id}:
    parameters:
      - in: path
        name: id
        required: true
        schema: {type: string}
    patch:
      tags: [Projects]
      operationId: updateSubscriptionPlanFeature
      summary: Update subscription plan feature
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/SubscriptionPlanFeatureUpdate'}
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema: {$ref: '#/components/schemas/SubscriptionPlanFeature'}

  /v1/subscription-plan-feature-usages:
    post:
      tags: [Projects]
      operationId: createSubscriptionPlanFeatureUsage
      summary: Create subscription plan feature usage
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/SubscriptionPlanFeatureUsageCreate'}
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: {$ref: '#/components/schemas/SubscriptionPlanFeatureUsage'}

  /v1/subscription-plan-feature-usages/{id}:
    parameters:
      - in: path
        name: id
        required: true
        schema: {type: string}
    patch:
      tags: [Projects]
      operationId: updateSubscriptionPlanFeatureUsage
      summary: Update subscription plan feature usage
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/SubscriptionPlanFeatureUsageUpdate'}
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema: {$ref: '#/components/schemas/SubscriptionPlanFeatureUsage'}

  /v1/api-keys:
    get:
      tags: [ApiKeys]
      operationId: listApiKeys
      summary: List API keys for the authenticated user
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/ApiKeyList'}
    post:
      tags: [ApiKeys]
      operationId: createApiKey
      summary: Create a new API key
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/ApiKeyCreate'}
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: {$ref: '#/components/schemas/ApiKeyCreated'}

  /v1/api-keys/{api_key_id}:
    parameters:
      - in: path
        name: api_key_id
        required: true
        schema: {type: string}
    delete:
      tags: [ApiKeys]
      operationId: deleteApiKey
      summary: Revoke an API key
      responses:
        "204": {description: Deleted}
        "404": {description: Not found}

  /v1/public-mounts:
    get:
      tags: [PublicMounts]
      operationId: listPublicMounts
      summary: List publicly available example mounts
      description: Returns mounts available for public demo use. No authentication required.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {$ref: '#/components/schemas/PublicMountList'}

components:
  schemas:
    Health:
      type: object
      properties:
        status: {type: string, enum: [ok]}
        version: {type: string}

    ProjectCreate:
      type: object
      required: [name]
      properties:
        name: {type: string}

    ProjectUpdate:
      type: object
      properties:
        name: {type: string}

    Project:
      type: object
      properties:
        id: {type: string}
        name: {type: string}
        creator_id: {type: string}
        stripe_customer_id: {type: string}
        stripe_subscription_id: {type: string}
        stripe_price_id:
          type: string
          description: Current Stripe price ID of the subscription's base plan
        stripe_subscription_status:
          type: string
          description: Latest known status of the Stripe subscription (active, past_due, canceled, ...)
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}

    ProjectList:
      type: object
      properties:
        items:
          type: array
          items: {$ref: '#/components/schemas/Project'}
        next_page_token: {type: string}

    ProjectMembership:
      type: object
      properties:
        id: {type: string}
        user_id: {type: string}
        project_id: {type: string}
        project: {$ref: '#/components/schemas/Project'}
        user: {$ref: '#/components/schemas/User'}
        role: {type: string, enum: [owner, admin, member]}
        status: {type: string, enum: [invited, accepted, denied]}
        invitation_sent_at: {type: string, format: date-time}
        invitation_accepted_at: {type: string, format: date-time}
        invitation_denied_at: {type: string, format: date-time}
        created_at: {type: string, format: date-time}

    ProjectMembershipList:
      type: object
      properties:
        items:
          type: array
          items: {$ref: '#/components/schemas/ProjectMembership'}

    S3Spec:
      type: object
      properties:
        bucket_name: {type: string}
        region: {type: string}
        endpoint: {type: string, description: "Custom endpoint for S3-compatible storage (e.g., R2, MinIO)"}
        access_key_id: {type: string}
        secret_access_key: {type: string}

    GcpSpec:
      type: object
      properties:
        bucket_name: {type: string}
        region: {type: string}
        service_account_json: {type: string}

    ConnectionCreate:
      type: object
      required: [provider, uri, region, access_key_id, secret_access_key]
      properties:
        provider: {type: string, enum: [aws, gcp, s3compat]}
        uri: {type: string, example: "s3://bucket/prefix"}
        region: {type: string, example: "aws:us-east-1"}
        s3_spec: {$ref: '#/components/schemas/S3Spec'}
        gcp_spec: {$ref: '#/components/schemas/GcpSpec'}

    Connection:
      type: object
      properties:
        id: {type: string}
        project_id: {type: string}
        provider: {type: string}
        uri: {type: string}
        region: {type: string}
        status: {type: string, enum: [pending, verifying, verified, invalid, failed, scaling, running, stopped, terminated]}
        desired_status: {type: string, enum: [running, stopped]}
        s3_spec: {$ref: '#/components/schemas/S3Spec'}
        gcp_spec: {$ref: '#/components/schemas/GcpSpec'}
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}

    ConnectionList:
      type: object
      properties:
        items:
          type: array
          items: {$ref: '#/components/schemas/Connection'}

    VerifyResult:
      type: object
      properties:
        status: {type: string, enum: [verified, failed]}
        checks:
          type: array
          items:
            type: object
            properties:
              name: {type: string}
              ok: {type: boolean}

    MountCreate:
      type: object
      required: [region]
      properties:
        connection_id: {type: string, description: "ID of a BYO connection (provide either connection_id or bucket_id)"}
        bucket_id: {type: string, description: "ID of a managed bucket (provide either connection_id or bucket_id)"}
        region: {type: string}
        preload: {type: boolean, default: false}

    MountUpdate:
      type: object
      properties:
        desired_status: {type: string, enum: [running, stopped]}

    Mount:
      type: object
      properties:
        id: {type: string}
        project_id: {type: string}
        connection_id: {type: string}
        bucket_id: {type: string}
        region: {type: string}
        status: {type: string, enum: [pending, running, scaling, terminating, stopped, failed]}
        desired_status: {type: string, enum: [running, stopped]}
        export: {$ref: '#/components/schemas/MountExport'}
        created_at: {type: string, format: date-time}

    MountExport:
      type: object
      properties:
        nfs: {$ref: '#/components/schemas/MountExportNfs'}
        smb: {$ref: '#/components/schemas/MountExportSmb'}
        agent: {$ref: '#/components/schemas/MountExportAgent'}
        wireguard: {$ref: '#/components/schemas/MountExportWireguard'}

    MountExportNfs:
      type: object
      properties:
        server: {type: string}
        port: {type: integer}
        path: {type: string}

    MountExportSmb:
      type: object
      properties:
        server: {type: string}
        port: {type: integer}
        share: {type: string}

    MountExportAgent:
      type: object
      properties:
        download_url: {type: string}

    MountExportWireguard:
      type: object
      properties:
        server_public_key: {type: string}
        endpoint: {type: string}
        client_private_key: {type: string}
        client_ip: {type: string}

    MountList:
      type: object
      properties:
        items:
          type: array
          items: {$ref: '#/components/schemas/Mount'}
        next_page_token: {type: string}

    GatewayCreate:
      type: object
      required: [project_id, provider, region, type]
      properties:
        project_id: {type: string}
        provider: {type: string, enum: [aws, gcp, local]}
        region: {type: string}
        type: {type: string}

    Gateway:
      type: object
      properties:
        id: {type: string}
        project_id: {type: string}
        provider: {type: string, enum: [aws, gcp, local]}
        region: {type: string}
        type: {type: string}
        replicas: {type: integer}
        status: {type: string, enum: [pending, running, scaling, terminating, stopped, failed]}
        desired_status: {type: string, enum: [running, stopped]}

    UsageReport:
      type: object
      properties:
        project_id: {type: string}
        from: {type: string, format: date-time}
        to: {type: string, format: date-time}
        current_total_bytes: {type: integer, format: int64}
        buckets:
          type: array
          items:
            $ref: '#/components/schemas/BucketUsageSummary'
        daily_usage:
          type: array
          items:
            $ref: '#/components/schemas/DailyUsage'
        total_mount_hours:
          type: number
          format: double
          description: Total mount-hours across all running mounts
        mounts:
          type: array
          items:
            $ref: '#/components/schemas/MountUsageSummary'

    BucketUsageSummary:
      type: object
      properties:
        bucket_id: {type: string}
        bucket_name: {type: string}
        current_size_bytes: {type: integer, format: int64}
        object_count: {type: integer}

    DailyUsage:
      type: object
      properties:
        date: {type: string}
        avg_bytes: {type: integer, format: int64}

    MountUsageSummary:
      type: object
      properties:
        mount_id: {type: string}
        region: {type: string}
        status: {type: string}
        running_hours:
          type: number
          format: double
          description: Hours this mount has been running in the current session

    User:
      type: object
      properties:
        id: {type: string}
        email: {type: string}
        first_name: {type: string}
        last_name: {type: string}
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}

    UserUpdate:
      type: object
      properties:
        first_name: {type: string}
        last_name: {type: string}

    PortalSessionCreate:
      type: object
      required: [return_url]
      properties:
        return_url:
          type: string
          format: uri
          description: URL to redirect to after the portal session

    PortalSession:
      type: object
      properties:
        url:
          type: string
          format: uri
          description: URL to redirect the user to for the Stripe billing portal

    CheckoutSessionCreate:
      type: object
      required: [price_id, success_url, cancel_url]
      properties:
        price_id:
          type: string
          description: Stripe price ID for the product/subscription
        success_url:
          type: string
          format: uri
          description: URL to redirect to after successful payment
        cancel_url:
          type: string
          format: uri
          description: URL to redirect to if payment is cancelled
        mode:
          type: string
          enum: [payment, subscription, setup]
          default: subscription
          description: Checkout session mode

    CheckoutSession:
      type: object
      properties:
        url:
          type: string
          format: uri
          description: URL to redirect the user to for the Stripe checkout
        session_id:
          type: string
          description: Stripe checkout session ID

    ChangePlanRequest:
      type: object
      required: [price_id]
      properties:
        price_id:
          type: string
          description: Stripe price ID to switch the subscription's base plan to

    ProjectSubscription:
      type: object
      properties:
        subscription_id:
          type: string
          description: Stripe subscription ID, empty if no subscription exists
        status:
          type: string
          description: Stripe subscription status (active, trialing, past_due, canceled, ...)
        price_id:
          type: string
          description: Stripe price ID of the current base plan

    SubscriptionFeatureCreate:
      type: object
      required: [name]
      properties:
        name: {type: string}
        is_binary_based: {type: boolean}
        is_usage_based: {type: boolean}
        usage_unit: {type: string}
        highlight: {type: boolean}

    SubscriptionFeatureUpdate:
      type: object
      properties:
        name: {type: string}
        is_binary_based: {type: boolean}
        is_usage_based: {type: boolean}
        usage_unit: {type: string}
        highlight: {type: boolean}

    SubscriptionFeature:
      type: object
      properties:
        id: {type: string}
        name: {type: string}
        is_binary_based: {type: boolean}
        is_usage_based: {type: boolean}
        usage_unit: {type: string}
        highlight: {type: boolean}
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}

    SubscriptionPlanCreate:
      type: object
      required: [name, stripe_product_id, monthly_price_id, yearly_price_id]
      properties:
        name: {type: string}
        stripe_product_id: {type: string}
        monthly_price_id: {type: string}
        yearly_price_id: {type: string}

    SubscriptionPlanUpdate:
      type: object
      properties:
        name: {type: string}
        stripe_product_id: {type: string}
        monthly_price_id: {type: string}
        yearly_price_id: {type: string}

    SubscriptionPlan:
      type: object
      properties:
        id: {type: string}
        name: {type: string}
        stripe_product_id: {type: string}
        monthly_price_id: {type: string}
        yearly_price_id: {type: string}
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}

    SubscriptionPlanFeatureCreate:
      type: object
      required: [subscription_plan_id, subscription_feature_id]
      properties:
        subscription_plan_id: {type: string}
        subscription_feature_id: {type: string}
        binary_value: {type: integer}
        usage_limit: {type: integer, format: int64}

    SubscriptionPlanFeatureUpdate:
      type: object
      properties:
        subscription_plan_id: {type: string}
        subscription_feature_id: {type: string}
        binary_value: {type: integer}
        usage_limit: {type: integer, format: int64}

    SubscriptionPlanFeature:
      type: object
      properties:
        id: {type: string}
        subscription_plan_id: {type: string}
        subscription_feature_id: {type: string}
        binary_value: {type: integer}
        usage_limit: {type: integer, format: int64}
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}

    SubscriptionPlanFeatureUsageCreate:
      type: object
      required: [subscription_feature_id, project_id, value]
      properties:
        subscription_feature_id: {type: string}
        project_id: {type: string}
        value: {type: integer}

    SubscriptionPlanFeatureUsageUpdate:
      type: object
      properties:
        subscription_feature_id: {type: string}
        project_id: {type: string}
        value: {type: integer}

    SubscriptionPlanFeatureUsage:
      type: object
      properties:
        id: {type: string}
        subscription_feature_id: {type: string}
        project_id: {type: string}
        value: {type: integer}
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}

    PlanFeature:
      type: object
      properties:
        feature_id: {type: string}
        feature_name: {type: string}
        is_binary_based: {type: boolean}
        is_usage_based: {type: boolean}
        usage_unit: {type: string}
        usage_limit: {type: integer, format: int64}
        binary_value: {type: integer}
        highlight: {type: boolean}

    PlanWithFeatures:
      type: object
      properties:
        id: {type: string}
        name: {type: string}
        stripe_product_id: {type: string}
        monthly_price_id: {type: string}
        yearly_price_id: {type: string}
        monthly_price: {type: integer, description: "Monthly price in cents (e.g., 4900 = $49.00)"}
        yearly_price: {type: integer, description: "Yearly price in cents (e.g., 49000 = $490.00)"}
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}
        features:
          type: array
          items: {$ref: '#/components/schemas/PlanFeature'}

    PlanList:
      type: object
      properties:
        items:
          type: array
          items: {$ref: '#/components/schemas/PlanWithFeatures'}

    PlatformConfig:
      type: object
      properties:
        providers:
          type: array
          items: {$ref: '#/components/schemas/ProviderConfig'}

    ProviderConfig:
      type: object
      properties:
        name:
          type: string
          description: Provider identifier (e.g., "aws", "gcp")
        display_name:
          type: string
          description: Human-readable provider name (e.g., "Amazon Web Services")
        regions:
          type: array
          items: {$ref: '#/components/schemas/RegionConfig'}

    RegionConfig:
      type: object
      properties:
        name:
          type: string
          description: Region identifier (e.g., "us-east-1")
        display_name:
          type: string
          description: Human-readable region name (e.g., "US East (N. Virginia)")

    ApiKeyCreate:
      type: object
      required: [name]
      properties:
        name:
          type: string
          description: A label for the API key (e.g., "CLI", "CI/CD")

    ApiKey:
      type: object
      properties:
        id: {type: string}
        name: {type: string}
        key_prefix:
          type: string
          description: First few characters of the key for identification
        last_used_at: {type: string, format: date-time}
        expires_at: {type: string, format: date-time}
        created_at: {type: string, format: date-time}

    ApiKeyCreated:
      type: object
      properties:
        id: {type: string}
        name: {type: string}
        key:
          type: string
          description: The full API key. Only shown once at creation time.
        key_prefix: {type: string}
        created_at: {type: string, format: date-time}

    ApiKeyList:
      type: object
      properties:
        items:
          type: array
          items: {$ref: '#/components/schemas/ApiKey'}

    PublicMount:
      type: object
      properties:
        id: {type: string}
        name:
          type: string
          description: Human-readable name (e.g., "Example Dataset")
        description:
          type: string
          description: What this mount contains
        nfs_server: {type: string}
        nfs_path: {type: string}
        nfs_port:
          type: integer
          default: 2049
        region: {type: string}

    PublicMountList:
      type: object
      properties:
        items:
          type: array
          items: {$ref: '#/components/schemas/PublicMount'}

    BucketCreate:
      type: object
      required: [name, region]
      properties:
        name: {type: string, description: "User-friendly bucket name"}
        region: {type: string, description: "Storage region (e.g., us-east-1)"}

    Bucket:
      type: object
      properties:
        id: {type: string}
        project_id: {type: string}
        name: {type: string}
        bucket_name: {type: string, description: "Bucket name to use with the S3-compatible endpoint"}
        provider: {type: string, enum: [managed]}
        region: {type: string}
        endpoint: {type: string, description: "S3-compatible endpoint URL for accessing this bucket"}
        access_key_id: {type: string, description: "Access key for authenticating with the storage endpoint"}
        secret_access_key: {type: string, description: "Secret key for authenticating with the storage endpoint"}
        status: {type: string, enum: [pending, active, failed, deleting]}
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}

    BucketList:
      type: object
      properties:
        items:
          type: array
          items: {$ref: '#/components/schemas/Bucket'}

    ObjectEntry:
      type: object
      required: [key, name, type]
      properties:
        key:
          type: string
          description: Full object key (files) or prefix including trailing slash (folders).
        name:
          type: string
          description: Display name (last path segment).
        type:
          type: string
          enum: [file, folder]
        size:
          type: integer
          format: int64
          description: Size in bytes (files only).
        last_modified:
          type: string
          format: date-time
          description: Last modified timestamp (files only).

    ObjectList:
      type: object
      required: [items, prefix, has_more]
      properties:
        items:
          type: array
          items: {$ref: '#/components/schemas/ObjectEntry'}
        prefix:
          type: string
          description: The prefix that was listed.
        has_more:
          type: boolean
          description: Whether more results are available via continuation_token.
        next_continuation_token:
          type: string
