fix(vv): resolve all 6 V&V issues — field trial unblocked
All findings from the inaugural LeadValidator audit resolved and confirmed. Release gate: PASS. VV_ISSUE_002 (BLOCKER): 15 OpenAPI specs verified present covering all 20 route groups (46 endpoints documented in docs/openapi/) VV_ISSUE_003 (MAJOR): Remove any types from src/db/pool.ts — replaced pool.query shim with unknown[] + Object.defineProperty, zero any types, eslint-disable suppressions removed VV_ISSUE_004 (MAJOR): Remove raw Pool from ScaffoldController and HealthDetailedController — injected AgentRepository/CredentialRepository and DbProbe interface respectively; added CredentialRepository.findActiveClientId() VV_ISSUE_005 (MAJOR): Add unit tests for 5 untested services — ComplianceStatusStore, EventPublisher, MarketplaceService, OIDCTrustPolicyService, UsageService VV_ISSUE_006 (MAJOR): Add integration tests for 7 missing route groups — analytics, billing, tiers, webhooks, marketplace, oidc-trust-policies, oidc-token-exchange VV_ISSUE_001 (MINOR): Create missing design.md and tasks.md in 4 OpenSpec archives — all archives now complete Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
355
docs/openapi/billing.yaml
Normal file
355
docs/openapi/billing.yaml
Normal file
@@ -0,0 +1,355 @@
|
||||
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'
|
||||
Reference in New Issue
Block a user