Manage subscriptions, invoices, payment methods, checkout sessions, credit packs, pricing plans, API keys, Stripe Connect profiles, and academic verification flows for Backbuild and Backbuild Science.
GET /v1/academic-verifications
List academic verifications (reviewer queue)
Returns the org-scoped reviewer queue of pending and submitted academic verifications. Filtered by optional `status` and paginated by `page`/`page_size`.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
page | query | integer | no | 1-based page number. |
page_size | query | integer | no | Records per page (1–100). |
status | query | string (enum) | no | Filter by verification status. |
Responses
| Status | Description |
200 | Paginated reviewer queue. |
400 | 400 Bad Request — a request parameter, body field, or the JSON body itself failed validation. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
GET /v1/academic-verifications/{id}
Get an academic verification
Returns a single academic verification by id. Accessible by the subject user or a reviewer.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
id | path | string<uuid> | yes | Academic verification identifier (UUIDv7). |
Responses
| Status | Description |
200 | Academic verification record. |
400 | `INVALID_ID_FORMAT` — malformed `id`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | 404 Not Found — the requested resource does not exist or is not visible to the caller. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
id | string<uuid> | |
user_id | string<uuid> | |
org_id | string | null | |
saas_package_id | string | null | |
provider | string (enum) manual_reviewsheeridverifyle | |
status | string (enum) pendingsubmittedapprovedrejectedexpired | |
institution_name | string | null | |
academic_role | string | null | |
rejection_reason | string | null | |
reviewed_by | string | null | |
reviewed_at | string | null | |
expires_at | string | null | |
created_at | string<date-time> | |
updated_at | string<date-time> | |
POST /v1/academic-verifications/{id}/review
Review an academic verification
Approves or rejects an academic verification. Callers must hold the reviewer role. `rejection_reason` is required when `decision=rejected`.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
id | path | string<uuid> | yes | Academic verification identifier (UUIDv7). |
Request Body
| Field | Type | Required | Description |
decision | string (enum) approvedrejected | yes | |
rejection_reason | string | no | Required when `decision=rejected`. |
Responses
| Status | Description |
200 | Verification reviewed. |
400 | `INVALID_ID_FORMAT`, `INVALID_JSON`, `VALIDATION_ERROR` (including missing `rejection_reason` when decision=rejected). |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | 404 Not Found — the requested resource does not exist or is not visible to the caller. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
id | string<uuid> | |
user_id | string<uuid> | |
org_id | string | null | |
saas_package_id | string | null | |
provider | string (enum) manual_reviewsheeridverifyle | |
status | string (enum) pendingsubmittedapprovedrejectedexpired | |
institution_name | string | null | |
academic_role | string | null | |
rejection_reason | string | null | |
reviewed_by | string | null | |
reviewed_at | string | null | |
expires_at | string | null | |
created_at | string<date-time> | |
updated_at | string<date-time> | |
POST /v1/academic-verifications/{id}/submit
Submit evidence for academic verification
Attaches institution name, role, and optional evidence URL to an in-progress academic verification.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
id | path | string<uuid> | yes | Academic verification identifier (UUIDv7). |
Request Body
| Field | Type | Required | Description |
institution_name | string | yes | |
academic_role | string | yes | |
evidence_url | string<uri> | no | |
Responses
| Status | Description |
200 | Evidence submitted. |
400 | `INVALID_ID_FORMAT`, `INVALID_JSON`, `VALIDATION_ERROR`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | 404 Not Found — the requested resource does not exist or is not visible to the caller. |
409 | `CONFLICT` — resubmission quota exceeded. |
429 | 429 Too Many Requests — a rate limit or usage quota was exceeded. Retry after the indicated window. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
id | string<uuid> | |
user_id | string<uuid> | |
org_id | string | null | |
saas_package_id | string | null | |
provider | string (enum) manual_reviewsheeridverifyle | |
status | string (enum) pendingsubmittedapprovedrejectedexpired | |
institution_name | string | null | |
academic_role | string | null | |
rejection_reason | string | null | |
reviewed_by | string | null | |
reviewed_at | string | null | |
expires_at | string | null | |
created_at | string<date-time> | |
updated_at | string<date-time> | |
POST /v1/academic-verifications/start
Start an academic verification
Initiates an academic verification for the authenticated user. Optionally scoped to a specific SaaS package and verification provider.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
saas_package_id | string<uuid> | no | |
provider | string (enum) manual_reviewsheeridverifyle | no | |
Responses
| Status | Description |
201 | Academic verification started. |
400 | `VALIDATION_ERROR`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | `NOT_FOUND` — the specified package or provider not found. |
409 | `CONFLICT` — concurrent verification submission quota exceeded. |
429 | `RATE_LIMIT_EXCEEDED`. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
201 response body: data fields
| Field | Type | Description |
id | string<uuid> | |
user_id | string<uuid> | |
org_id | string | null | |
saas_package_id | string | null | |
provider | string (enum) manual_reviewsheeridverifyle | |
status | string (enum) pendingsubmittedapprovedrejectedexpired | |
institution_name | string | null | |
academic_role | string | null | |
rejection_reason | string | null | |
reviewed_by | string | null | |
reviewed_at | string | null | |
expires_at | string | null | |
created_at | string<date-time> | |
updated_at | string<date-time> | |
GET /v1/api-keys
List organization API keys
Returns the API keys for the authenticated organization. Raw key values are never returned — only the key prefix, name, scopes, and metadata. The organization and RLS scope are derived from the session.
Auth Session JWT (Bearer)
Responses
| Status | Description |
200 | List of API keys. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
keys | array of object | |
POST /v1/api-keys
Create an API key
Creates a new organization API key with the specified name, scopes, expiry, and optional rate limit. The raw key (`bb_live_<prefix>_<secret>`) is returned ONCE in the response and never stored — if lost it cannot be retrieved. Requires recent authentication (token issued within 5 minutes). Organization API key cap (100) is enforced before creation.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
name | string | yes | |
scopes | array of string | yes | |
expires_in_days | integer | no | Expiry in days from now (omit for non-expiring). |
rate_limit_rpm | integer | no | |
pinned_env_id | string<uuid> | no | |
Responses
| Status | Description |
201 | API key created. The `key` field contains the raw key; store it now as it will not be shown again. |
400 | `VALIDATION_ERROR` — invalid JSON, invalid `name`, disallowed `scopes`, out-of-range `expires_in_days` or `rate_limit_rpm`, or malformed `pinned_env_id`. |
401 | `AUTH_REQUIRED`, `REAUTH_REQUIRED` — a recent (within 5 min) session token is required. |
403 | `FORBIDDEN` — caller lacks permission to create keys or the `pinned_env_id` would escalate privilege. |
429 | `QUOTA_EXCEEDED` — organization has reached the 100-key limit. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
201 response body: data fields
| Field | Type | Description |
id | string<uuid> | |
key | string | Full raw API key in format `bb_live_{8hexchars}_{64hexchars}`. Store it now — it will not be shown again. |
prefix | string | |
name | string | |
scopes | array of string | |
expires_at | string | null | |
created_at | string<date-time> | |
DELETE /v1/api-keys/{id}
Revoke an API key
Immediately revokes an API key, preventing any future use. Requires recent authentication (token issued within 5 minutes).
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
id | path | string<uuid> | yes | API key identifier (UUIDv7). |
Responses
| Status | Description |
200 | API key revoked. |
400 | `VALIDATION_ERROR` — malformed key `id`. |
401 | `AUTH_REQUIRED`, `REAUTH_REQUIRED` — a recent session token is required. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
id | string<uuid> | |
revoked | boolean (enum) true | |
POST /v1/api-keys/{id}/rotate
Rotate an API key
Atomically revokes the existing key and creates a replacement with the same name and scopes. The new raw key is returned once. An optional `grace_period_minutes` (0–60, default 5) keeps the old key valid for a short overlap window so in-flight requests are not disrupted. Requires recent authentication (token issued within 5 minutes).
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
id | path | string<uuid> | yes | API key identifier (UUIDv7). |
Request Body
| Field | Type | Required | Description |
grace_period_minutes | integer | no | How long the old key remains valid after rotation (0–60 min). |
Responses
| Status | Description |
200 | API key rotated. The `key` field contains the new raw key; store it now as it will not be shown again. |
400 | `VALIDATION_ERROR` — malformed key `id`. |
401 | `AUTH_REQUIRED`, `REAUTH_REQUIRED` — a recent session token is required. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
id | string<uuid> | New key identifier. |
key | string | New raw API key. Store it now. |
prefix | string | |
name | string | |
scopes | array of string | |
old_key_id | string<uuid> | |
old_key_expires_at | string | null | Grace period expiry for the old key (null = revoked immediately). |
created_at | string<date-time> | |
GET /v1/billing
Get billing overview
Returns the billing overview for the authenticated organization, including subscription state, payment methods, and credit balance. Requires owner or admin role within the org; members without that role receive `403 FORBIDDEN`.
Auth Session JWT (Bearer)
Responses
| Status | Description |
200 | Billing overview for the organization. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | `FORBIDDEN` — caller lacks owner or admin role within the organization. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
subscription | object | An organization subscription record. |
payment_methods | array of object | Saved payment methods. |
credits | object | AI credit balance for an organization. |
GET /v1/billing/client-config
Get Stripe client configuration
Returns the Stripe publishable key required to initialize the Stripe.js client on the frontend. Returns `503 CHECKOUT_FAILED` if Stripe is not configured for the deployment.
Auth Session JWT (Bearer)
Responses
| Status | Description |
200 | Stripe client configuration. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
503 | `CHECKOUT_FAILED` — Stripe is not configured for this deployment. |
200 response body: data fields
| Field | Type | Description |
publishable_key | string | Stripe publishable key (pk_live_* or pk_test_*) used to initialize Stripe.js. |
POST /v1/billing/create-checkout-session
Create a hosted Stripe Checkout session
Creates a hosted Stripe Checkout session for a subscription plan. Returns a `checkout_url` to redirect the user to Stripe. `success_url` and `cancel_url` must be `https://` URLs targeting an allowlisted application domain; other values return `400 VALIDATION_ERROR`. `trial_period_days` is a client hint that is server-clamped against the plan's configured maximum — the effective value can be less than requested.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
price_id | string | yes | Stripe price id for the plan. |
package_id | string<uuid> | yes | SaaS package id (UUIDv7). |
success_url | string<uri> | no | HTTPS URL to redirect to after successful checkout. Must target an allowlisted application domain. |
cancel_url | string<uri> | no | HTTPS URL to redirect to after checkout cancellation. Must target an allowlisted application domain. |
trial_period_days | integer | no | Client-suggested free trial length in days. Server-clamped to the plan's configured maximum; the effective value may be lower than requested. |
Responses
| Status | Description |
201 | Checkout session created. Redirect the user to `checkout_url`. |
400 | `VALIDATION_ERROR` (invalid fields, disallowed redirect domain, or invalid `trial_period_days`), `STRIPE_CREDENTIALS_MISSING` (Stripe not configured for the package). |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
500 | `CHECKOUT_FAILED` — Stripe session creation failed or server misconfiguration. |
201 response body: data fields
| Field | Type | Description |
checkout_url | string | null | Stripe-hosted Checkout URL. Redirect the user here. |
POST /v1/billing/create-embedded-checkout-session
Create an embedded Stripe Checkout session
Creates an embedded Stripe Checkout session. Returns a `client_secret` for initializing Stripe.js Embedded Checkout on the page. Supports `mode=subscription` (default, requires `price_id`) and `mode=setup` (card collection without a charge). `return_url` must target an allowlisted application domain; other values return `400 VALIDATION_ERROR`.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
price_id | string | no | Stripe price id. Required for `mode=subscription`; ignored for `mode=setup`. |
package_id | string<uuid> | yes | SaaS package id (UUIDv7). |
return_url | string<uri> | no | HTTPS URL the embedded checkout redirects to on completion. Must target an allowlisted application domain. |
trial_period_days | integer | no | Client-suggested trial length in days. Server-clamped to the plan's maximum. |
mode | string (enum) subscriptionsetup | no | Checkout mode. `subscription` (default) charges immediately; `setup` collects a card for later use. |
Responses
| Status | Description |
201 | Embedded checkout session created. |
400 | `VALIDATION_ERROR` (invalid fields, disallowed redirect domain, or `price_id` missing for `mode=subscription`), `STRIPE_CREDENTIALS_MISSING` (Stripe not configured for the package). |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
500 | `CHECKOUT_FAILED` — Stripe session creation failed or server misconfiguration. |
201 response body: data fields
| Field | Type | Description |
client_secret | string | Stripe Checkout client secret for initializing Stripe.js Embedded Checkout. |
mode | string (enum) subscriptionsetup | Mode of the session (`setup` only present when mode=setup was requested). |
POST /v1/billing/credit-packs/{planId}/purchase
Purchase a credit pack
Initiates a one-time Stripe Checkout session (`mode=payment`) to purchase an AI credit pack identified by `planId`. Returns `checkout_url` to redirect the user and an `order_id` for status polling. The plan must be of type `credit_pack` with a configured Stripe price. `return_url` and `cancel_url` must be `https://` URLs targeting an allowlisted application domain.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
planId | path | string<uuid> | yes | Credit pack pricing plan identifier (UUIDv7). |
Request Body
| Field | Type | Required | Description |
return_url | string<uri> | no | HTTPS URL to redirect to after successful purchase. Must target an allowlisted application domain. |
cancel_url | string<uri> | no | HTTPS URL to redirect to on cancellation. Must target an allowlisted application domain. |
Responses
| Status | Description |
201 | Credit pack checkout session created. |
400 | `INVALID_ID_FORMAT` (malformed `planId`), `VALIDATION_ERROR` (invalid redirect URLs or plan not eligible for credit-pack purchase). |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | `FORBIDDEN` — caller lacks permission to purchase credit packs for the org. |
404 | `NOT_FOUND` — plan not found. |
500 | `CHECKOUT_FAILED` — Stripe session creation failed. |
503 | `CHECKOUT_FAILED` — credit pack plan not yet synced to Stripe (missing `stripe_price_id`). |
201 response body: data fields
| Field | Type | Description |
order_id | string<uuid> | Platform order id for status polling. |
checkout_url | string | null | Stripe Checkout URL to redirect the user to. |
checkout_session_id | string | Stripe Checkout session id. |
credit_amount | integer | Number of AI credits in the pack. |
price_cents | integer | Price in the smallest currency unit (e.g. cents). |
currency | string | ISO 4217 currency code. |
GET /v1/billing/credit-packs/orders/{orderId}
Get credit pack order status
Returns the status and metadata of a one-time credit pack order. Used for post-checkout polling to confirm that credits have been provisioned after Stripe payment.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
orderId | path | string<uuid> | yes | Credit pack order identifier (UUIDv7). |
Responses
| Status | Description |
200 | Credit pack order record. |
400 | `INVALID_ID_FORMAT` — malformed `orderId`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | `FORBIDDEN` — order does not belong to this organization. |
404 | `NOT_FOUND` — order not found. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
id | string<uuid> | |
org_id | string<uuid> | |
plan_id | string<uuid> | |
status | string | Order status (e.g. `pending`, `completed`, `failed`). |
credit_amount | integer | |
price_cents | integer | |
currency | string | |
credits_applied | boolean | Whether credits have been applied to the organization balance. |
credits_applied_at | string | null | |
stripe_checkout_session_id | string | null | |
failure_reason | string | null | |
created_at | string<date-time> | |
completed_at | string | null | |
GET /v1/billing/credits
Get credit balance
Returns the current AI credit balance for the authenticated organization.
Auth Session JWT (Bearer)
Responses
| Status | Description |
200 | Credit balance. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
balance | number | Current available credit balance. |
currency | string | Currency code (e.g. `usd`). |
GET /v1/billing/invoices
List invoices
Returns invoices for the authenticated organization with optional status and date-range filters. Default limit 25, max 100.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
status | query | string (enum) | no | Filter by invoice status. |
from_date | query | string<date-time> | no | Inclusive start of the invoice date range (ISO 8601 datetime). |
to_date | query | string<date-time> | no | Inclusive end of the invoice date range (ISO 8601 datetime). |
limit | query | integer | no | Maximum number of invoices to return (1–100, default 25). |
offset | query | integer | no | Number of invoices to skip for pagination. |
Responses
| Status | Description |
200 | Paginated list of invoices. |
400 | 400 Bad Request — a request parameter, body field, or the JSON body itself failed validation. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
GET /v1/billing/payment-methods
List saved payment methods
Returns all saved Stripe payment methods for the authenticated organization.
Auth Session JWT (Bearer)
Responses
| Status | Description |
200 | List of saved payment methods. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
PUT /v1/billing/payment-methods
Upsert a payment method
Records a Stripe payment method (`pm_*`) for the organization. The API verifies the payment method belongs to this organization's Stripe customer before writing; a cross-customer or unknown `pm_*` returns `400 PAYMENT_METHOD_NOT_OWNED`. All `pm_*` and billing-address string fields are capped at Stripe's documented field lengths.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
payment_method_id | string | yes | Stripe `pm_*` identifier. Must belong to this organization's Stripe customer. |
set_as_default | boolean | no | Whether to set this as the default payment method (default true when omitted). |
billing_details | object | no | Optional billing detail overrides. |
Responses
| Status | Description |
200 | Payment method upserted. |
400 | `VALIDATION_ERROR` (invalid body fields), `PAYMENT_METHOD_NOT_OWNED` (the `pm_*` id does not belong to this org's Stripe customer or cannot be found). |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
id | string<uuid> | Platform-assigned record id. |
stripe_payment_method_id | string | Stripe `pm_*` identifier. |
type | string | Payment method type (e.g. `card`). |
is_default | boolean | Whether this is the default payment method. |
created_at | string<date-time> | When the record was created. |
GET /v1/billing/subscription-status
Poll subscription activation status
Returns whether the organization's subscription has been activated. Intended for short-polling after Stripe Checkout redirect to confirm the Stripe webhook has been processed (typically called every 3 s for up to 2 min).
Auth Session JWT (Bearer)
Responses
| Status | Description |
200 | Current activation state. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
active | boolean | True once the Stripe webhook has confirmed activation. |
status | string | null | Current subscription status string. |
GET /v1/billing/subscriptions
List subscriptions
Returns the organization's subscriptions with optional status filter and offset pagination. Default limit 25, max 100.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
status | query | string (enum) | no | Filter by subscription status. Must be one of the canonical values matching the underlying platform subscription state. |
limit | query | integer | no | Maximum number of records to return (1–100, default 25). |
offset | query | integer | no | Number of records to skip for pagination. |
Responses
| Status | Description |
200 | Paginated list of subscriptions. |
400 | 400 Bad Request — a request parameter, body field, or the JSON body itself failed validation. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
POST /v1/billing/subscriptions
Create a subscription
Creates a new subscription for the authenticated organization using a Stripe payment method. All Stripe identifier and billing-address fields are bounded at Stripe's documented field lengths.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
plan_id | string | yes | Pricing plan key or ID. |
interval | string (enum) monthyear | yes | Billing interval. |
payment_method_id | string | yes | Stripe `pm_*` identifier. |
billing_details | object | yes | |
seats | integer | no | Number of seats (default 1). |
metadata | object | no | Optional metadata JSONB. |
Responses
| Status | Description |
201 | Subscription created. |
400 | 400 Bad Request — a request parameter, body field, or the JSON body itself failed validation. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
201 response body: data fields
| Field | Type | Description |
id | string<uuid> | Platform-assigned subscription id. |
org_id | string<uuid> | Owning organization. |
plan_id | string | Pricing plan key. |
interval | string (enum) monthyear | Billing interval. |
status | string (enum) activetrialingpast_duecanceledincompletepaused | Subscription lifecycle status. |
seats | integer | Number of seats. |
cancel_at_period_end | boolean | Whether the subscription is scheduled to cancel at period end. |
current_period_end | string | null | End of the current billing period. |
created_at | string<date-time> | |
updated_at | string<date-time> | |
PUT /v1/billing/subscriptions/{subscriptionId}
Update a subscription
Updates an existing subscription's plan, seat count, or cancellation scheduling. At least one body field must be supplied. Returns `404 NOT_FOUND` when the subscription does not exist or belongs to another organization.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
subscriptionId | path | string<uuid> | yes | Subscription identifier (UUIDv7). |
Request Body
| Field | Type | Required | Description |
plan_id | string | no | New pricing plan key. |
seats | integer | no | New seat count. |
cancel_at_period_end | boolean | no | Schedule cancellation at the current period end. |
metadata | object | no | Optional metadata JSONB. |
Responses
| Status | Description |
200 | Updated subscription record. |
400 | `INVALID_ID_FORMAT` (malformed `subscriptionId`), `VALIDATION_ERROR` (invalid body fields). |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
404 | `NOT_FOUND` — subscription not found or belongs to another organization. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
id | string<uuid> | Platform-assigned subscription id. |
org_id | string<uuid> | Owning organization. |
plan_id | string | Pricing plan key. |
interval | string (enum) monthyear | Billing interval. |
status | string (enum) activetrialingpast_duecanceledincompletepaused | Subscription lifecycle status. |
seats | integer | Number of seats. |
cancel_at_period_end | boolean | Whether the subscription is scheduled to cancel at period end. |
current_period_end | string | null | End of the current billing period. |
created_at | string<date-time> | |
updated_at | string<date-time> | |
GET /v1/billing/usage
Get billable usage summary
Returns billable usage rollup for the authenticated organization. When `env_id` is absent or `all`, the global aggregate across all environments is returned. The `window` parameter controls the time range (default `30d`).
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
env_id | query | string | no | Environment UUID to scope usage to, or the literal `all` (default) for a global aggregate across all environments. Any other non-UUID value returns `400 VALIDATION_ERROR`. |
window | query | string (enum) | no | Aggregation window. One of `24h`, `7d`, `30d` (default), `90d`, `all`. |
Responses
| Status | Description |
200 | Usage summary. |
400 | `VALIDATION_ERROR` — `env_id` is not a UUID or `all`; `window` is not one of the allowed values. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
window | string | Requested aggregation window. |
env_id | string | null | Environment the rollup is scoped to (null for global aggregate). |
total_credits_used | number | Total AI credits consumed in the window. |
by_env | array of object | Per-environment breakdown (only present in global-aggregate responses). |
GET /v1/pricing
List public pricing plans
Returns pricing plans sourced from Stripe, optionally filtered by `package_id` and `environment`. This is a public endpoint — no authentication required. Results are KV-cached; `cached` and `cacheAge` indicate whether the response is from cache. Rate-limited to 50 requests per minute per IP (IPv6 normalized to /64).
Public Public: no authentication
Parameters
| Name | In | Type | Required | Description |
package_id | query | string<uuid> | no | Filter to a specific SaaS package (UUIDv7). When omitted all published packages are returned. |
environment | query | string (enum) | no | Filter to a specific deployment environment. |
Responses
| Status | Description |
200 | Pricing plans. The `cached` flag indicates whether the response was served from the KV cache. |
400 | 400 Bad Request — a request parameter, body field, or the JSON body itself failed validation. |
429 | 429 Too Many Requests — a rate limit or usage quota was exceeded. Retry after the indicated window. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
POST /v1/signup-checkout/intents
Create a pre-auth signup checkout intent
Creates a signup checkout intent before the user authenticates, capturing plan and package selection. No authentication is required; rate-limiting (20/hour per IP) is enforced by the platform. A Cloudflare Turnstile token is required (supplied as `cf-turnstile-response` header by the frontend). Only `https://` requests traversing the Cloudflare edge are accepted (the source IP is derived exclusively from `cf-connecting-ip`). At least one of `org_id`, `org_slug_or_id`, or `domain` must be supplied as a resolved UUID; slug/domain resolution is not performed server-side on this endpoint.
Public Public: no authentication
Request Body
| Field | Type | Required | Description |
anonymous_session_id | string | yes | Stable anonymous session identifier to link the pre-auth intent to a post-auth session. |
org_id | string<uuid> | no | |
org_slug_or_id | string | no | |
domain | string | no | |
package_id | string<uuid> | no | |
package_slug | string | no | |
pricing_plan_price_id | string | no | |
billing_interval | string (enum) monthyear | no | |
trial_period_days | integer | no | Requested trial length (server-clamped to plan maximum). |
metadata | object | no | |
Responses
| Status | Description |
201 | Signup checkout intent created. |
400 | `INVALID_JSON` (unparseable body), `VALIDATION_ERROR` (schema violation), `INVALID_PARAMETER` (source IP could not be determined — request must traverse Cloudflare edge). |
403 | `FORBIDDEN` — academic-verification prerequisite not met, or prerequisite plan not satisfied. |
404 | `NOT_FOUND` — the specified plan or package does not exist. |
409 | `CONFLICT` — org conflict or intent lifecycle state conflict (e.g. intent expired or already consumed). `error.details` may carry `intent_org_id` and `caller_org_id` for org-conflict disambiguation. |
429 | `RATE_LIMIT_EXCEEDED` — per-IP rate limit exceeded. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
201 response body: data fields
| Field | Type | Description |
id | string<uuid> | |
status | string (enum) pendingprocessingcompletedexpiredcancelled | |
checkout_url | string | null | Hosted checkout redirect URL. |
client_secret | string | null | Stripe embedded checkout client_secret. |
created_at | string<date-time> | |
expires_at | string | null | |
POST /v1/signup-checkout/intents/{id}/resume
Resume a signup checkout intent (authenticated)
Resumes a previously created signup checkout intent after user authentication. The caller must be an active member of the intent's owning organization. When `resolved_org_id` differs from the session's `org_id`, the caller's membership in `resolved_org_id` is pre-checked at the route layer before the underlying platform call.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
id | path | string<uuid> | yes | Signup checkout intent identifier (UUIDv7). |
Request Body
| Field | Type | Required | Description |
pricing_plan_price_id | string | no | |
billing_interval | string (enum) monthyear | no | |
Responses
| Status | Description |
200 | Intent resumed; checkout session details returned. |
400 | `INVALID_ID_FORMAT` (malformed `id`), `INVALID_JSON` (unparseable body), `VALIDATION_ERROR`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | `FORBIDDEN` — caller is not an active member of the resolved org. |
404 | `NOT_FOUND` — intent not found. |
409 | `CONFLICT` — intent expired, already consumed, or org conflict. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
id | string<uuid> | |
status | string (enum) pendingprocessingcompletedexpiredcancelled | |
checkout_url | string | null | Hosted checkout redirect URL. |
client_secret | string | null | Stripe embedded checkout client_secret. |
created_at | string<date-time> | |
expires_at | string | null | |
GET /v1/stripe-connect
Get legacy Stripe Connect configuration
Returns the Stripe Connect configuration for the given SaaS package and environment. Requires `stripe_connect.view` permission. This is the legacy per-(package, environment) endpoint; new integrations should use `/v1/stripe-profiles`.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
saas_package_id | query | string<uuid> | yes | SaaS package identifier (UUIDv7). |
environment | query | string (enum) | no | Deployment environment. |
Responses
| Status | Description |
200 | Stripe Connect configuration. |
400 | 400 Bad Request — a request parameter, body field, or the JSON body itself failed validation. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | `NOT_FOUND` — Stripe Connect configuration not found for this package and environment. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
id | string<uuid> | |
saas_package_id | string<uuid> | |
environment | string (enum) developmentstagingpreprodproduction | |
stripe_account_id | string | null | |
charges_enabled | boolean | |
payouts_enabled | boolean | |
details_submitted | boolean | |
platform_fee_pct | number | null | |
client_fee_pct | number | null | |
created_at | string<date-time> | |
updated_at | string<date-time> | |
POST /v1/stripe-connect
Create legacy Stripe Connect configuration
Creates a Stripe Connect configuration for a SaaS package and environment. Requires `stripe_connect.create` permission. This is the legacy per-(package, environment) endpoint; new integrations should use `/v1/stripe-profiles`.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
saas_package_id | string<uuid> | yes | |
environment | string (enum) developmentstagingpreprodproduction | no | |
stripe_account_id | string | yes | Stripe account id (`acct_...`). Required for the legacy endpoint. |
account_type | string (enum) standardexpresscustom | no | Stripe Connect account type (default `express`). |
publishable_key | string | no | |
business_profile | object | no | |
webhook_secret_ref | string | no | Reference name for the stored webhook secret. |
Responses
| Status | Description |
201 | Stripe Connect configuration created. |
400 | `INVALID_JSON`, `VALIDATION_ERROR`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
409 | `CONFLICT` — a Stripe Connect configuration already exists for this package and environment. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
201 response body: data fields
| Field | Type | Description |
id | string<uuid> | |
saas_package_id | string<uuid> | |
environment | string (enum) developmentstagingpreprodproduction | |
stripe_account_id | string | null | |
charges_enabled | boolean | |
payouts_enabled | boolean | |
details_submitted | boolean | |
platform_fee_pct | number | null | |
client_fee_pct | number | null | |
created_at | string<date-time> | |
updated_at | string<date-time> | |
PUT /v1/stripe-connect
Update legacy Stripe Connect configuration
Updates the Stripe Connect configuration for a SaaS package and environment. At least one mutable field (beyond `saas_package_id` and `environment`) must be supplied. Requires `stripe_connect.update` permission. A body-supplied `stripe_account_id` that does not match the verified stored value is rejected with `403 FORBIDDEN`. This is the legacy endpoint; new integrations should use `/v1/stripe-profiles`.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
saas_package_id | string<uuid> | yes | |
environment | string (enum) developmentstagingpreprodproduction | no | |
stripe_account_id | string | no | |
account_type | string (enum) standardexpresscustom | no | |
publishable_key | string | no | |
business_profile | object | no | |
webhook_secret_ref | string | no | |
Responses
| Status | Description |
200 | Updated Stripe Connect configuration. |
400 | `INVALID_JSON`, `VALIDATION_ERROR`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | `FORBIDDEN` — missing permission or submitted `stripe_account_id` does not match the verified record. |
404 | `NOT_FOUND` — configuration not found. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
id | string<uuid> | |
saas_package_id | string<uuid> | |
environment | string (enum) developmentstagingpreprodproduction | |
stripe_account_id | string | null | |
charges_enabled | boolean | |
payouts_enabled | boolean | |
details_submitted | boolean | |
platform_fee_pct | number | null | |
client_fee_pct | number | null | |
created_at | string<date-time> | |
updated_at | string<date-time> | |
PUT /v1/stripe-connect/fees
Update legacy Stripe Connect fee percentages
Updates the platform and/or client fee percentages for a Stripe Connect configuration. At least one fee field must be supplied. Requires `stripe_connect.update` permission. This is the legacy endpoint; prefer `/v1/stripe-profiles/:profileId/fees`.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
saas_package_id | string<uuid> | yes | |
environment | string (enum) developmentstagingpreprodproduction | no | |
platform_fee_percent | number | no | |
client_fee_percent | number | no | |
Responses
| Status | Description |
200 | Fee percentages updated. |
400 | `INVALID_JSON`, `VALIDATION_ERROR`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | `NOT_FOUND` — configuration not found. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
POST /v1/stripe-connect/onboard
Start legacy Stripe Connect onboarding (deprecated — 410 Gone)
**Permanently retired.** This endpoint is disabled by default and returns `410 GONE` with `Sunset`, `Link`, and `Deprecation` headers pointing to the successor `/v1/stripe-profiles/:profileId/onboard/start`. It accepted a tenant-supplied `stripe_account_id`, making account-claim races possible. Use `POST /v1/stripe-profiles` and `POST /v1/stripe-profiles/:profileId/onboard/start` instead.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
saas_package_id | string<uuid> | yes | |
environment | string (enum) developmentstagingpreprodproduction | no | |
stripe_account_id | string | yes | |
refresh_url | string<uri> | no | |
return_url | string<uri> | no | |
Responses
| Status | Description |
200 | Onboarding started (only returned when the endpoint is explicitly re-enabled by the operator). |
400 | `INVALID_JSON`, `VALIDATION_ERROR`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
409 | `CONFLICT` — a Stripe Connect record already exists for this package/environment. |
410 | `GONE` — this endpoint is permanently retired. Use `POST /v1/stripe-profiles` + `POST /v1/stripe-profiles/:profileId/onboard/start`. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
POST /v1/stripe-connect/onboard/complete
Complete legacy Stripe Connect onboarding
Marks the Stripe Connect onboarding as complete by recording the `charges_enabled`, `payouts_enabled`, and `details_submitted` states reported by Stripe. Requires `stripe_connect.update` permission. This is the legacy endpoint; prefer `/v1/stripe-profiles/:profileId/onboard/complete`.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
saas_package_id | string<uuid> | yes | |
environment | string (enum) developmentstagingpreprodproduction | no | |
charges_enabled | boolean | no | |
payouts_enabled | boolean | no | |
details_submitted | boolean | no | |
Responses
| Status | Description |
200 | Onboarding completion recorded. |
400 | `INVALID_JSON`, `VALIDATION_ERROR`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | `NOT_FOUND` — Stripe Connect configuration not found. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
POST /v1/stripe-connect/sync
Sync legacy Stripe Connect account status
Syncs Stripe account status fields (charges/payouts enabled, business profile, currency) for a legacy Stripe Connect configuration. Requires `stripe_connect.update` permission. This is the legacy endpoint; prefer `/v1/stripe-profiles/:profileId/sync`.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
saas_package_id | string<uuid> | yes | |
environment | string (enum) developmentstagingpreprodproduction | no | |
Responses
| Status | Description |
200 | Stripe Connect status synced. |
400 | `INVALID_JSON`, `VALIDATION_ERROR`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | `NOT_FOUND` — configuration not found. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
GET /v1/stripe-profiles
List Stripe Connect profiles
Returns the organization's Stripe Connect profiles with optional status filter and page/pageSize pagination. Permission gating is enforced inside the platform; the caller must hold `stripe.profiles.view` or equivalent.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
page | query | integer | no | 1-based page number. |
page_size | query | integer | no | Records per page (1–100). |
status | query | string (enum) | no | Filter by profile onboarding status. |
Responses
| Status | Description |
200 | Paginated list of Stripe Connect profiles. |
400 | 400 Bad Request — a request parameter, body field, or the JSON body itself failed validation. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
POST /v1/stripe-profiles
Create a Stripe Connect profile
Creates a new Stripe Connect profile for the organization. `stripe_account_id` may not be supplied on creation — the Stripe account is created server-side by calling `POST /v1/stripe-profiles/:profileId/onboard/start`. Permission gating is enforced inside the platform.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
name | string | yes | |
description | string | no | |
country | string | no | |
default_currency | string | no | |
Responses
| Status | Description |
201 | Stripe Connect profile created. |
400 | `INVALID_JSON`, `VALIDATION_ERROR` (including rejection of a supplied `stripe_account_id`). |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
409 | `CONFLICT` — a profile with a conflicting constraint already exists. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
201 response body: data fields
| Field | Type | Description |
id | string<uuid> | |
org_id | string<uuid> | |
name | string | |
description | string | null | |
stripe_account_id | string | null | |
status | string (enum) pendingonboardingrestrictedactivedisabled | |
country | string | null | |
default_currency | string | null | |
charges_enabled | boolean | |
payouts_enabled | boolean | |
details_submitted | boolean | |
platform_fee_pct | number | null | |
client_fee_pct | number | null | |
version | integer | Optimistic concurrency version. |
created_at | string<date-time> | |
updated_at | string<date-time> | |
GET /v1/stripe-profiles/{profileId}
Get a Stripe Connect profile
Returns a single Stripe Connect profile by id. Returns `404 NOT_FOUND` for profiles not belonging to the caller's organization.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
profileId | path | string<uuid> | yes | Stripe Connect profile identifier (UUIDv7). |
Responses
| Status | Description |
200 | Stripe Connect profile. |
400 | `INVALID_ID_FORMAT` — malformed `profileId`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | 404 Not Found — the requested resource does not exist or is not visible to the caller. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
id | string<uuid> | |
org_id | string<uuid> | |
name | string | |
description | string | null | |
stripe_account_id | string | null | |
status | string (enum) pendingonboardingrestrictedactivedisabled | |
country | string | null | |
default_currency | string | null | |
charges_enabled | boolean | |
payouts_enabled | boolean | |
details_submitted | boolean | |
platform_fee_pct | number | null | |
client_fee_pct | number | null | |
version | integer | Optimistic concurrency version. |
created_at | string<date-time> | |
updated_at | string<date-time> | |
PATCH /v1/stripe-profiles/{profileId}
Update a Stripe Connect profile
Updates mutable fields on a Stripe Connect profile (name, description, country, default currency, business profile). `expected_version` is required for optimistic concurrency. At least one mutable field (beyond `expected_version`) must be supplied.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
profileId | path | string<uuid> | yes | Stripe Connect profile identifier (UUIDv7). |
Request Body
| Field | Type | Required | Description |
expected_version | integer | yes | |
name | string | no | |
description | string | no | |
country | string | no | |
default_currency | string | no | |
Responses
| Status | Description |
200 | Updated profile. |
400 | `INVALID_ID_FORMAT`, `INVALID_JSON`, `VALIDATION_ERROR`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | 404 Not Found — the requested resource does not exist or is not visible to the caller. |
409 | `CONFLICT` — `expected_version` is stale (concurrent modification). |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
id | string<uuid> | |
org_id | string<uuid> | |
name | string | |
description | string | null | |
stripe_account_id | string | null | |
status | string (enum) pendingonboardingrestrictedactivedisabled | |
country | string | null | |
default_currency | string | null | |
charges_enabled | boolean | |
payouts_enabled | boolean | |
details_submitted | boolean | |
platform_fee_pct | number | null | |
client_fee_pct | number | null | |
version | integer | Optimistic concurrency version. |
created_at | string<date-time> | |
updated_at | string<date-time> | |
DELETE /v1/stripe-profiles/{profileId}
Delete (archive) a Stripe Connect profile
Soft-deletes a Stripe Connect profile. `expected_version` is required for optimistic concurrency. The platform enforces that the profile has no active subscriber dependencies before deletion.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
profileId | path | string<uuid> | yes | Stripe Connect profile identifier (UUIDv7). |
Request Body
| Field | Type | Required | Description |
expected_version | integer | yes | |
Responses
| Status | Description |
200 | Profile archived. |
400 | `INVALID_ID_FORMAT`, `INVALID_JSON`, `VALIDATION_ERROR`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | 404 Not Found — the requested resource does not exist or is not visible to the caller. |
409 | `CONFLICT` — `expected_version` is stale, profile has dependents, or profile is already archived. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
PATCH /v1/stripe-profiles/{profileId}/fees
Update Stripe Connect profile fee percentages
Updates the platform and/or client fee percentages for a Stripe Connect profile. Requires a fresh MFA step-up (the session's last MFA step-up must be within the configured window). `expected_version` is required for optimistic concurrency. At least one fee field must be supplied.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
profileId | path | string<uuid> | yes | Stripe Connect profile identifier (UUIDv7). |
Request Body
| Field | Type | Required | Description |
expected_version | integer | yes | |
platform_fee_percent | number | null | no | Platform application fee percentage (0–50). Nullable to clear. |
client_fee_percent | number | no | Client-side fee percentage (0–50). |
Responses
| Status | Description |
200 | Fee percentages updated. |
400 | `INVALID_ID_FORMAT`, `INVALID_JSON`, `VALIDATION_ERROR`. |
401 | `AUTH_REQUIRED` or `MFA_STEPUP_REQUIRED` — a fresh MFA step-up is required for fee changes. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | 404 Not Found — the requested resource does not exist or is not visible to the caller. |
409 | `CONFLICT` — `expected_version` is stale. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
id | string<uuid> | |
org_id | string<uuid> | |
name | string | |
description | string | null | |
stripe_account_id | string | null | |
status | string (enum) pendingonboardingrestrictedactivedisabled | |
country | string | null | |
default_currency | string | null | |
charges_enabled | boolean | |
payouts_enabled | boolean | |
details_submitted | boolean | |
platform_fee_pct | number | null | |
client_fee_pct | number | null | |
version | integer | Optimistic concurrency version. |
created_at | string<date-time> | |
updated_at | string<date-time> | |
GET /v1/stripe-profiles/{profileId}/links
List package links for a Stripe Connect profile
Returns all (SaaS package, environment) links associated with this Stripe Connect profile.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
profileId | path | string<uuid> | yes | Stripe Connect profile identifier (UUIDv7). |
Responses
| Status | Description |
200 | List of package links. |
400 | `INVALID_ID_FORMAT`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | 404 Not Found — the requested resource does not exist or is not visible to the caller. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
POST /v1/stripe-profiles/{profileId}/links
Link a package to a Stripe Connect profile
Associates a (SaaS package, environment) combination with this Stripe Connect profile.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
profileId | path | string<uuid> | yes | Stripe Connect profile identifier (UUIDv7). |
Request Body
| Field | Type | Required | Description |
saas_package_id | string<uuid> | yes | |
environment | string (enum) developmentstagingpreprodproduction | yes | |
Responses
| Status | Description |
201 | Package linked. |
400 | `INVALID_ID_FORMAT`, `INVALID_JSON`, `VALIDATION_ERROR`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | 404 Not Found — the requested resource does not exist or is not visible to the caller. |
409 | `CONFLICT` — link already exists or live/test mode conflict. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
201 response body: data fields
| Field | Type | Description |
profile_id | string<uuid> | |
saas_package_id | string<uuid> | |
environment | string (enum) developmentstagingpreprodproduction | |
created_at | string<date-time> | |
DELETE /v1/stripe-profiles/{profileId}/links/{pkgId}/{env}
Unlink a package from a Stripe Connect profile
Removes the link between a (SaaS package, environment) combination and this Stripe Connect profile. The `archive_stripe_products` query parameter controls whether linked Stripe products are archived.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
profileId | path | string<uuid> | yes | Stripe Connect profile identifier (UUIDv7). |
pkgId | path | string<uuid> | yes | SaaS package identifier (UUIDv7). |
env | path | string (enum) | yes | Deployment environment. |
archive_stripe_products | query | string (enum) | no | When `true`, archive the associated Stripe products/prices for this link. |
Responses
| Status | Description |
200 | Package unlinked. |
400 | `INVALID_ID_FORMAT` (malformed `profileId` or `pkgId`), `INVALID_PARAMETER` (invalid `env` value). |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | 404 Not Found — the requested resource does not exist or is not visible to the caller. |
409 | `CONFLICT` — link has active subscribers or active Stripe products (archive first or use `archive_stripe_products=true`). |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
POST /v1/stripe-profiles/{profileId}/onboard/complete
Complete Stripe Connect profile onboarding
Syncs the Stripe account's current state (charges/payouts/details, onboarding status, tax info) from Stripe into the platform record. `expected_version` is required for optimistic concurrency.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
profileId | path | string<uuid> | yes | Stripe Connect profile identifier (UUIDv7). |
Request Body
| Field | Type | Required | Description |
expected_version | integer | yes | |
Responses
| Status | Description |
200 | Onboarding state synced from Stripe. |
400 | `INVALID_ID_FORMAT`, `INVALID_JSON`, `VALIDATION_ERROR`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | 404 Not Found — the requested resource does not exist or is not visible to the caller. |
409 | `CONFLICT` — profile has no Stripe account yet (call `/onboard/start` first) or `expected_version` is stale. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
502 | `EXTERNAL_SERVICE_ERROR` — Stripe account lookup failed. |
503 | `SERVICE_UNAVAILABLE` — Stripe is not configured. |
200 response body: data fields
| Field | Type | Description |
id | string<uuid> | |
org_id | string<uuid> | |
name | string | |
description | string | null | |
stripe_account_id | string | null | |
status | string (enum) pendingonboardingrestrictedactivedisabled | |
country | string | null | |
default_currency | string | null | |
charges_enabled | boolean | |
payouts_enabled | boolean | |
details_submitted | boolean | |
platform_fee_pct | number | null | |
client_fee_pct | number | null | |
version | integer | Optimistic concurrency version. |
created_at | string<date-time> | |
updated_at | string<date-time> | |
POST /v1/stripe-profiles/{profileId}/onboard/start
Start Stripe Connect profile onboarding
Orchestrates server-side Stripe account creation (if the profile has no `stripe_account_id` yet) and returns a fresh Stripe AccountLink URL for the operator to complete KYC. Uses a stable Idempotency-Key so a retry after a transient failure does not create a duplicate Stripe account. `refresh_url` and `return_url` must use `https://` and target the configured application domain.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
profileId | path | string<uuid> | yes | Stripe Connect profile identifier (UUIDv7). |
Request Body
| Field | Type | Required | Description |
refresh_url | string<uri> | no | |
return_url | string<uri> | no | |
Responses
| Status | Description |
200 | Onboarding URL returned. Redirect the operator to `onboarding_url`. |
400 | `INVALID_ID_FORMAT`, `INVALID_JSON`, `VALIDATION_ERROR` (disallowed redirect URL). |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | 404 Not Found — the requested resource does not exist or is not visible to the caller. |
409 | `CONFLICT` — profile state conflict (e.g. `stripe_account_id` already claimed by another profile). |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
502 | `EXTERNAL_SERVICE_ERROR` — Stripe account creation or AccountLink creation failed. |
503 | `SERVICE_UNAVAILABLE` — Stripe is not configured for this deployment. |
200 response body: data fields
| Field | Type | Description |
onboarding_url | string<uri> | Stripe AccountLink URL for the operator. |
expires_at | string<date-time> | |
POST /v1/stripe-profiles/{profileId}/sync
Queue Stripe Connect profile sync
Enqueues a re-sync of linked Stripe plans for this profile, optionally filtered to specific environments. Returns `202 Accepted`.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
profileId | path | string<uuid> | yes | Stripe Connect profile identifier (UUIDv7). |
Request Body
| Field | Type | Required | Description |
environments | array of enum developmentstagingpreprodproduction | no | |
Responses
| Status | Description |
202 | Sync enqueued. |
400 | `INVALID_ID_FORMAT`, `VALIDATION_ERROR`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | 404 Not Found — the requested resource does not exist or is not visible to the caller. |
409 | `CONFLICT` — profile is not in a state that allows sync (e.g. onboarding not yet complete). |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
POST /v1/stripe-profiles/{profileId}/webhook-secret/rotate
Rotate Stripe Connect profile webhook secret
Rotates the webhook signing secret for a Stripe Connect profile. Requires a fresh MFA step-up (PCI DSS Req 8.6 / SOC 2 CC6.6 key management control). `expected_version` is required for optimistic concurrency.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
profileId | path | string<uuid> | yes | Stripe Connect profile identifier (UUIDv7). |
Request Body
| Field | Type | Required | Description |
expected_version | integer | yes | |
Responses
| Status | Description |
200 | Webhook secret rotated. |
400 | `INVALID_ID_FORMAT`, `INVALID_JSON`, `VALIDATION_ERROR`. |
401 | `AUTH_REQUIRED` or `MFA_STEPUP_REQUIRED` — a fresh MFA step-up is required. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | 404 Not Found — the requested resource does not exist or is not visible to the caller. |
409 | `CONFLICT` — `expected_version` is stale. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |