openapi: 3.0.3
info:
  title: Apex Pulse Billing API
  version: 1.0.0
  summary: Pulse subscription billing — tier management, Stripe integration, and webhook processing
  description: |
    OpenAPI contract for Apex Pulse billing endpoints under app.parkerapex.com.
    This spec covers subscription lifecycle (create, status, change, cancel),
    Stripe Customer Portal sessions, tier listing, and webhook processing
    for Free ($0), Pro ($9.99/mo), and Premium ($19.99/mo) tiers.
servers:
  - url: https://app.parkerapex.com
    description: Production
tags:
  - name: Tiers
    description: Public tier and pricing information
  - name: Subscriptions
    description: Subscription lifecycle management (authenticated)
  - name: Portal
    description: Stripe Customer Portal session management
  - name: Webhooks
    description: Stripe webhook event processing
security:
  - cookieAuth: []
paths:
  /api/pulse/billing/tiers:
    get:
      tags: [Tiers]
      operationId: listPulseTiers
      summary: List available Pulse tiers with pricing
      description: Return all available Pulse subscription tiers with pricing and feature lists. This endpoint is public and requires no authentication.
      security: []
      responses:
        '200':
          description: Tier listing
          content:
            application/json:
              schema:
                type: object
                required: [tiers]
                properties:
                  tiers:
                    type: array
                    items:
                      $ref: '#/components/schemas/PulseTier'
              example:
                tiers:
                  - tier: free
                    name: Apex Pulse Free
                    price_cents: 0
                    price_display: Free
                    features:
                      - basic_analytics
                      - community_benchmarks
                      - weekly_digest
                  - tier: pro
                    name: Apex Pulse Pro
                    price_cents: 999
                    price_display: "$9.99/mo"
                    features:
                      - real_time_analytics
                      - custom_dashboards
                      - daily_digest
                      - api_access
                      - export_csv
                  - tier: premium
                    name: Apex Pulse Premium
                    price_cents: 1999
                    price_display: "$19.99/mo"
                    features:
                      - real_time_analytics
                      - custom_dashboards
                      - hourly_digest
                      - api_access
                      - export_csv
                      - predictive_insights
                      - white_label_reports
                      - dedicated_support
  /api/pulse/billing/subscribe:
    post:
      tags: [Subscriptions]
      operationId: createPulseSubscription
      summary: Create a new Pulse subscription
      description: |
        Create a Stripe subscription for the authenticated user at the specified tier.
        For the free tier, no Stripe subscription is created — the user is simply
        marked as subscribed at the free level.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubscribeRequest'
            example:
              tier: pro
              trial_days: 14
      responses:
        '201':
          description: Subscription created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscribeResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
  /api/pulse/billing/status:
    get:
      tags: [Subscriptions]
      operationId: getPulseStatus
      summary: Get current subscription status
      description: Return the authenticated user's current Pulse subscription tier, status, features, and billing details.
      responses:
        '200':
          description: Current subscription status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingStatus'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/pulse/billing/change-tier:
    post:
      tags: [Subscriptions]
      operationId: changePulseTier
      summary: Upgrade or downgrade subscription tier
      description: |
        Change the authenticated user's Pulse subscription to a different tier.
        Stripe handles proration automatically.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChangeTierRequest'
            example:
              tier: premium
      responses:
        '200':
          description: Tier changed successfully
          content:
            application/json:
              schema:
                type: object
                required: [message, subscription_id, status, old_tier, new_tier]
                properties:
                  message:
                    type: string
                    example: Tier changed to Apex Pulse Premium
                  subscription_id:
                    type: string
                    example: sub_1ABC123
                  status:
                    type: string
                    example: active
                  old_tier:
                    type: string
                    enum: [free, pro, premium]
                  new_tier:
                    type: string
                    enum: [free, pro, premium]
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
  /api/pulse/billing/cancel:
    post:
      tags: [Subscriptions]
      operationId: cancelPulseSubscription
      summary: Cancel Pulse subscription
      description: |
        Cancel the authenticated user's Pulse subscription. By default cancels
        at the end of the current billing period. Set `immediate: true` to
        cancel immediately.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CancelRequest'
            example:
              immediate: false
      responses:
        '200':
          description: Subscription canceled
          content:
            application/json:
              schema:
                type: object
                required: [message, cancel_at_period_end, subscription_id]
                properties:
                  message:
                    type: string
                    example: Subscription canceled
                  cancel_at_period_end:
                    type: boolean
                  subscription_id:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
  /api/pulse/billing/portal-session:
    post:
      tags: [Portal]
      operationId: createPulsePortalSession
      summary: Create a Stripe Customer Portal session
      description: |
        Create a Stripe Customer Portal session so the user can manage payment
        methods, view invoices, and change or cancel subscriptions via Stripe's
        hosted portal UI.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PortalSessionRequest'
            example:
              return_url: https://app.parkerapex.com/billing
      responses:
        '200':
          description: Portal session created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PortalSessionResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '404':
          description: No billing account found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/pulse/billing/webhook:
    post:
      tags: [Webhooks]
      operationId: handlePulseWebhook
      summary: Handle Stripe webhook events
      description: |
        Receives and processes Stripe webhook events for Pulse subscriptions.
        This endpoint is CSRF-exempt and validates events using the Stripe
        webhook signature. No session authentication is required.

        Handled event types:
        - `invoice.paid` — payment succeeded
        - `invoice.payment_failed` — payment failed
        - `customer.subscription.deleted` — subscription canceled
        - `customer.subscription.updated` — subscription modified (logged)
        - `customer.subscription.created` — new subscription (logged)
      security: []
      parameters:
        - in: header
          name: Stripe-Signature
          required: true
          schema:
            type: string
          description: Stripe webhook signature for payload verification.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEvent'
      responses:
        '200':
          description: Webhook received
          content:
            application/json:
              schema:
                type: object
                required: [received]
                properties:
                  received:
                    type: boolean
                    example: true
        '400':
          description: Invalid payload or signature
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Handler error or webhook secret not configured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  securitySchemes:
    cookieAuth:
      type: apiKey
      in: cookie
      name: session
      description: Cookie-based session authentication via Plexus IAM.
  responses:
    BadRequest:
      description: Bad request — invalid tier or missing fields
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: Authentication required — user not logged in
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    PaymentRequired:
      description: Stripe payment error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    PulseTier:
      type: object
      required: [tier, name, price_cents, price_display, features]
      properties:
        tier:
          type: string
          enum: [free, pro, premium]
          description: Tier slug identifier.
        name:
          type: string
          description: Human-readable tier name.
          example: Apex Pulse Pro
        price_cents:
          type: integer
          minimum: 0
          description: Monthly price in cents.
          example: 999
        price_display:
          type: string
          description: Formatted price string for UI display.
          example: "$9.99/mo"
        features:
          type: array
          items:
            type: string
          description: List of feature slugs included in this tier.
    BillingStatus:
      type: object
      required: [subscribed, tier, tier_name, status, features]
      properties:
        subscribed:
          type: boolean
          description: Whether the user has an active subscription (always true, defaults to free).
        tier:
          type: string
          enum: [free, pro, premium]
        tier_name:
          type: string
          example: Apex Pulse Pro
        status:
          type: string
          enum: [active, trialing, past_due, canceled, incomplete, incomplete_expired, unpaid]
        features:
          type: array
          items:
            type: string
        amount_cents:
          type: integer
          minimum: 0
          description: Monthly price in cents (omitted for free tier).
        cancel_at_period_end:
          type: boolean
          description: Whether the subscription will cancel at the end of the current period.
        current_period_end:
          type: string
          format: date-time
          description: ISO-8601 timestamp of the current billing period end.
        trial_end:
          type: string
          format: date-time
          description: ISO-8601 timestamp of trial end (null if not trialing).
        stripe_subscription_id:
          type: string
          description: Stripe subscription ID (omitted for free tier).
    SubscribeRequest:
      type: object
      required: [tier]
      properties:
        tier:
          type: string
          enum: [free, pro, premium]
          description: The tier to subscribe to.
        trial_days:
          type: integer
          minimum: 0
          default: 14
          description: Number of trial days before billing starts.
    SubscribeResponse:
      type: object
      required: [tier, status, subscription_id, message]
      properties:
        tier:
          type: string
          enum: [free, pro, premium]
        status:
          type: string
          enum: [active, trialing, incomplete]
        subscription_id:
          type: string
          nullable: true
          description: Stripe subscription ID (null for free tier).
        trial_end:
          type: integer
          nullable: true
          description: Unix timestamp of trial end (null for free tier or no trial).
        message:
          type: string
          example: Subscribed to Apex Pulse Pro
    ChangeTierRequest:
      type: object
      required: [tier]
      properties:
        tier:
          type: string
          enum: [free, pro, premium]
          description: The new tier to switch to.
    CancelRequest:
      type: object
      properties:
        immediate:
          type: boolean
          default: false
          description: If true, cancel immediately. If false, cancel at end of billing period.
    PortalSessionRequest:
      type: object
      properties:
        return_url:
          type: string
          format: uri
          default: https://app.parkerapex.com/billing
          description: URL to redirect the user to after leaving the Stripe portal.
    PortalSessionResponse:
      type: object
      required: [url]
      properties:
        url:
          type: string
          format: uri
          description: Stripe Customer Portal session URL. Redirect the user here.
    WebhookEvent:
      type: object
      description: |
        Stripe webhook event payload. The server validates the signature
        and processes the event based on its type.
      required: [id, type, data]
      properties:
        id:
          type: string
          description: Stripe event ID.
          example: evt_1ABC123
        type:
          type: string
          description: Stripe event type.
          enum:
            - invoice.paid
            - invoice.payment_failed
            - customer.subscription.deleted
            - customer.subscription.updated
            - customer.subscription.created
        data:
          type: object
          required: [object]
          properties:
            object:
              type: object
              description: The Stripe resource associated with the event.
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: string
          description: Human-readable error message.
