openapi: "3.0.3"

info:
  title: SentryAgent.ai — Billing & Usage Metering
  version: 1.0.0
  description: |
    Billing and usage metering endpoints for the SentryAgent.ai AgentIdP platform.

    Integrates with **Stripe** for subscription and payment management.

    **Authenticated endpoints** (require Bearer JWT):
    - `POST /billing/checkout` — Create a Stripe Checkout Session for plan upgrades
    - `GET /billing/usage` — Retrieve today's usage summary

    **Unauthenticated endpoint** (Stripe webhook receiver):
    - `POST /billing/webhook` — Receives Stripe webhook events (raw body + signature verification)

    **Important:** The `/billing/webhook` endpoint uses `express.raw()` middleware
    to receive the raw request body as a Buffer. Do not apply `express.json()` to this route.
    The `Stripe-Signature` header is required for all webhook deliveries.

servers:
  - url: http://localhost:3000/api/v1
    description: Local development server
  - url: https://api.sentryagent.ai/v1
    description: Production server

tags:
  - name: Billing Checkout
    description: Stripe Checkout Session management
  - name: Billing Webhook
    description: Stripe webhook event receiver (unauthenticated)
  - name: Usage
    description: Usage metering and reporting

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        JWT access token obtained via `POST /token`.
        Include as `Authorization: Bearer <token>`.

  schemas:
    CheckoutRequest:
      type: object
      description: |
        Optional request body for creating a Stripe Checkout Session.
        When `successUrl` or `cancelUrl` are omitted, the platform generates
        default redirect URLs pointing to the dashboard.
      properties:
        successUrl:
          type: string
          format: uri
          description: URL to redirect to after successful payment.
          example: "https://my-app.example.com/dashboard?billing=success"
        cancelUrl:
          type: string
          format: uri
          description: URL to redirect to if the user cancels checkout.
          example: "https://my-app.example.com/dashboard?billing=cancel"

    CheckoutResponse:
      type: object
      description: Stripe Checkout Session URL to redirect the user to.
      required:
        - checkoutUrl
      properties:
        checkoutUrl:
          type: string
          format: uri
          description: |
            Stripe-hosted Checkout page URL. Redirect the authenticated user
            to this URL to complete payment.
          example: "https://checkout.stripe.com/pay/cs_test_abcdef1234567890"

    UsageSummary:
      type: object
      description: |
        Today's usage summary for the authenticated organization.
        Counters reset at UTC midnight.
      required:
        - organizationId
        - date
        - tokensIssued
        - agentsRegistered
        - credentialsGenerated
      properties:
        organizationId:
          type: string
          format: uuid
          description: Organization the usage data belongs to.
          example: "org-1234-5678-abcd-ef01"
        date:
          type: string
          format: date
          description: The calendar date (UTC) this summary covers.
          example: "2026-04-07"
        tokensIssued:
          type: integer
          description: Number of OAuth 2.0 tokens issued today.
          minimum: 0
          example: 4201
        agentsRegistered:
          type: integer
          description: Number of new agents registered today.
          minimum: 0
          example: 3
        credentialsGenerated:
          type: integer
          description: Number of new agent credentials generated today.
          minimum: 0
          example: 5
        apiCallsTotal:
          type: integer
          description: Total API calls across all endpoints today.
          minimum: 0
          example: 12450

    StripeWebhookResponse:
      type: object
      description: Acknowledgement response for a received Stripe webhook event.
      required:
        - received
      properties:
        received:
          type: boolean
          description: Always `true` when the webhook was processed successfully.
          example: true

    ErrorResponse:
      type: object
      description: Standard error response envelope.
      required:
        - code
        - message
      properties:
        code:
          type: string
          example: "VALIDATION_ERROR"
        message:
          type: string
          example: "Missing Stripe-Signature header."
        details:
          type: object
          additionalProperties: true

  responses:
    Unauthorized:
      description: Missing or invalid Bearer token.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            code: "UNAUTHORIZED"
            message: "A valid Bearer token is required to access this resource."

    Forbidden:
      description: Valid token but insufficient permissions.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            code: "FORBIDDEN"
            message: "You do not have permission to perform this action."

    InternalServerError:
      description: Unexpected server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            code: "INTERNAL_SERVER_ERROR"
            message: "An unexpected error occurred. Please try again later."

paths:
  /billing/checkout:
    post:
      operationId: createBillingCheckoutSession
      tags:
        - Billing Checkout
      summary: Create a Stripe Checkout Session
      description: |
        Creates a Stripe Checkout Session for the authenticated organization
        to upgrade their subscription plan.

        The organization ID is read from the `organization_id` claim in the
        Bearer JWT — the caller does not need to provide it in the request body.

        The `checkoutUrl` in the response is a Stripe-hosted checkout page.
        Redirect the authenticated user to this URL to complete payment.

        Requires a valid Bearer JWT. The `organization_id` claim must be present in the token.
      security:
        - BearerAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CheckoutRequest'
            example:
              successUrl: "https://my-app.example.com/dashboard?billing=success"
              cancelUrl: "https://my-app.example.com/dashboard?billing=cancel"
      responses:
        '201':
          description: Stripe Checkout Session created successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CheckoutResponse'
              example:
                checkoutUrl: "https://checkout.stripe.com/pay/cs_test_abcdef1234567890"
        '400':
          description: Validation error — organization_id missing from token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                code: "VALIDATION_ERROR"
                message: "organization_id is required in token."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          description: Unexpected error or Stripe API error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                code: "STRIPE_ERROR"
                message: "Failed to create Stripe Checkout Session. Please try again."

  /billing/webhook:
    post:
      operationId: handleStripeWebhook
      tags:
        - Billing Webhook
      summary: Receive Stripe webhook events
      description: |
        Receives webhook events from Stripe's delivery system. This endpoint is
        **unauthenticated** — authentication is provided by Stripe's HMAC signature
        in the `Stripe-Signature` header.

        **Body format:** The request body MUST be the raw JSON payload as sent by
        Stripe (not parsed JSON). The `Content-Type` is `application/json` but
        the body is read as a raw `Buffer` for signature verification.

        **Signature verification:** The `Stripe-Signature` header is required.
        If absent or invalid, the request is rejected with `400`.

        **Supported events processed:**
        - `checkout.session.completed` — Activates subscription after payment
        - `customer.subscription.deleted` — Downgrades plan on cancellation
        - `invoice.payment_failed` — Handles failed renewals
      security: []
      parameters:
        - name: Stripe-Signature
          in: header
          required: true
          description: |
            HMAC signature from Stripe for payload verification.
            Format: `t=<timestamp>,v1=<signature>,...`
          schema:
            type: string
          example: "t=1492774577,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a05bd412fbc2a2bzo..."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Raw Stripe event payload (read as Buffer internally).
              additionalProperties: true
              example:
                id: "evt_1234567890"
                object: "event"
                type: "checkout.session.completed"
                data:
                  object:
                    id: "cs_test_abcdef1234567890"
                    customer: "cus_abc123"
      responses:
        '200':
          description: Webhook event received and processed successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StripeWebhookResponse'
              example:
                received: true
        '400':
          description: Missing or invalid Stripe-Signature header, or malformed payload.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                missingSignature:
                  summary: Missing Stripe-Signature header
                  value:
                    code: "VALIDATION_ERROR"
                    message: "Missing Stripe-Signature header."
                invalidSignature:
                  summary: Signature verification failed
                  value:
                    code: "STRIPE_SIGNATURE_INVALID"
                    message: "Webhook signature verification failed."
        '500':
          $ref: '#/components/responses/InternalServerError'

  /billing/usage:
    get:
      operationId: getBillingUsage
      tags:
        - Usage
      summary: Get today's usage summary
      description: |
        Returns the usage summary for the authenticated organization
        for the current calendar day (UTC).

        Usage counters reset at UTC midnight.
        The `organization_id` claim is read from the Bearer JWT.

        Requires a valid Bearer JWT.
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Usage summary returned successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UsageSummary'
              example:
                organizationId: "org-1234-5678-abcd-ef01"
                date: "2026-04-07"
                tokensIssued: 4201
                agentsRegistered: 3
                credentialsGenerated: 5
                apiCallsTotal: 12450
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
