feat: show tax preview for credit top-ups (#45005)

## Summary

- Add tax preview to the credit top-up flow by integrating the new `POST
/organizations/{slug}/billing/credits/preview` endpoint
- Show a price breakdown (credit amount, tax line item, total) in the
top-up dialog before the user confirms payment
- Handle all three `tax_status` states: show tax when `calculated`, hide
the line when `not_applicable`, show an inline warning when `failed`


## Behavior

- Preview fires as soon as a valid amount (300–2000) is entered; address
and tax ID are optional and refine the estimate
- Amount and address changes are debounced at 1s to avoid excessive API
calls

## Test plan

- [ ]  Open credit top-up dialog - verify preview appears with default
$300 amount
- [ ]  Change amount within 300–2000 - verify preview updates after
debounce
- [ ]  Enter amount outside range (e.g. 100 or 3000) - verify preview
hides and validation error shows
- [ ]  Add a new payment method with a billing address in a taxed region
- verify tax line item appears
- [ ]  Add a new payment method with no tax jurisdiction - verify no tax
line, just total
- [ ]  Complete a top-up - verify the charge goes through and dialog
closes


<img width="571" height="551" alt="image"
src="https://github.com/user-attachments/assets/d3357752-f913-4a4a-b84a-f78e2f457c7b"
/>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Credit top-up preview with charge breakdown (credit, tax or tax note,
total).
* Onboarding survey endpoint and ISO 27001 certificate availability for
organizations.

* **Updates**
* Improved top-up UX: debounced address/tax inputs, enforced min/max
amount validation, preview-driven form state, and submit disabled while
preview is loading/stale.
  * API docs wording changed to “temporary access configuration.”
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Ignacio Dobronich authored and GitHub committed 2026-04-23 12:16:57 -03:00
1 parent 03f6083b1b
commit e6ca2766ff
6 files changed
+803 -68

No files matched your search

@@ -0,0 +1,58 @@
import { cn } from 'ui'
import { formatCurrency } from '@/lib/helpers'
export interface ChargeBreakdownProps {
subtotal: number
total: number
tax?: { amount: number; percentage: number }
taxStatus?: 'calculated' | 'failed' | 'not_applicable'
isFetching: boolean
}
export const ChargeBreakdown = ({
subtotal,
total,
tax,
taxStatus,
isFetching,
}: ChargeBreakdownProps) => {
return (
<div
className={cn('text-foreground-light text-sm transition-opacity', isFetching && 'opacity-50')}
>
{total !== subtotal && (
<div className="flex items-center justify-between gap-2 border-b border-muted text-sm">
<div className="py-2">Subtotal</div>
<div className="py-2 text-right tabular-nums" translate="no">
{formatCurrency(subtotal)}
</div>
</div>
)}
{taxStatus === 'calculated' && tax && tax.amount > 0 && (
<div className="flex items-center justify-between gap-2 border-b border-muted text-sm">
<div className="py-2">Tax ({tax.percentage}%)</div>
<div className="py-2 text-right tabular-nums" translate="no">
{formatCurrency(tax.amount)}
</div>
</div>
)}
{taxStatus === 'failed' && (
<div className="flex items-center justify-between gap-2 border-b border-muted text-sm">
<div className="py-2 text-foreground-lighter">
Tax could not be estimated and may be applied separately
</div>
</div>
)}
<div className="flex items-center justify-between gap-2 text-foreground text-base">
<div className="py-2">Total due today</div>
<div className="py-2 text-right tabular-nums" translate="no">
{formatCurrency(total)}
</div>
</div>
</div>
)
}
@@ -4,6 +4,7 @@ import { Elements } from '@stripe/react-stripe-js'
import { loadStripe, PaymentIntentResult } from '@stripe/stripe-js'
import { PermissionAction, SupportCategories } from '@supabase/shared-types/out/constants'
import { useQueryClient } from '@tanstack/react-query'
import { useDebounce } from '@uidotdev/usehooks'
import { AlertCircle, Info } from 'lucide-react'
import { useTheme } from 'next-themes'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
@@ -32,12 +33,15 @@ import { z } from 'zod'
import type { PaymentMethodElementRef } from '../../Billing/Payment/PaymentMethods/NewPaymentMethodElement'
import PaymentMethodSelection from './Subscription/PaymentMethodSelection'
import { ChargeBreakdown } from '@/components/interfaces/Billing/ChargeBreakdown'
import { getStripeElementsAppearanceOptions } from '@/components/interfaces/Billing/Payment/Payment.utils'
import { PaymentConfirmation } from '@/components/interfaces/Billing/Payment/PaymentConfirmation'
import { NO_PROJECT_MARKER } from '@/components/interfaces/Support/SupportForm.utils'
import { SupportLink } from '@/components/interfaces/Support/SupportLink'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import { useOrganizationCreditTopUpMutation } from '@/data/organizations/organization-credit-top-up-mutation'
import { useCreditTopUpPreview } from '@/data/organizations/organization-credit-top-up-preview'
import type { CustomerAddress, CustomerTaxId } from '@/data/organizations/types'
import { subscriptionKeys } from '@/data/subscriptions/keys'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { STRIPE_PUBLIC_KEY } from '@/lib/constants'
@@ -45,12 +49,14 @@ import { STRIPE_PUBLIC_KEY } from '@/lib/constants'
const stripePromise = loadStripe(STRIPE_PUBLIC_KEY)
const FORM_ID = 'credit-top-up'
const MIN_TOP_UP_AMOUNT = 300
const MAX_TOP_UP_AMOUNT = 2000
const FormSchema = z.object({
amount: z.coerce
.number()
.gte(300, 'Amount must be between $300 - $2000.')
.lte(2000, 'Amount must be between $300 - $2000.')
.gte(MIN_TOP_UP_AMOUNT, `Amount must be between $${MIN_TOP_UP_AMOUNT} - $${MAX_TOP_UP_AMOUNT}.`)
.lte(MAX_TOP_UP_AMOUNT, `Amount must be between $${MIN_TOP_UP_AMOUNT} - $${MAX_TOP_UP_AMOUNT}.`)
.int('Amount must be a whole number.'),
paymentMethod: z.string(),
})
@@ -87,6 +93,52 @@ export const CreditTopUp = ({ slug }: { slug: string | undefined }) => {
const [topUpModalVisible, setTopUpModalVisible] = useState(false)
const [useAsDefaultBillingAddress, setUseAsDefaultBillingAddress] = useState(true)
const [paymentConfirmationLoading, setPaymentConfirmationLoading] = useState(false)
const [latestAddress, setLatestAddress] = useState<CustomerAddress>()
const [latestTaxId, setLatestTaxId] = useState<CustomerTaxId | null>()
const billingAddress = useAsDefaultBillingAddress ? latestAddress : undefined
const billingTaxId = useAsDefaultBillingAddress ? latestTaxId : null
const debouncedAddress = useDebounce(billingAddress, 1000)
const debouncedTaxId = useDebounce(billingTaxId, 1000)
const watchedAmount = form.watch('amount')
const debouncedAmount = useDebounce(watchedAmount, 1000)
const parsedAmount = Number(debouncedAmount)
const validAmount =
!Number.isNaN(parsedAmount) &&
Number.isInteger(parsedAmount) &&
parsedAmount >= MIN_TOP_UP_AMOUNT &&
parsedAmount <= MAX_TOP_UP_AMOUNT
? parsedAmount
: undefined
const isPreviewStale =
watchedAmount !== debouncedAmount ||
billingAddress !== debouncedAddress ||
billingTaxId !== debouncedTaxId
const handleAddressChange = useCallback((address: CustomerAddress) => {
setLatestAddress(address)
}, [])
const handleTaxIdChange = useCallback((taxId: CustomerTaxId | null) => {
setLatestTaxId(taxId)
}, [])
const {
data: creditPreview,
isFetching: creditPreviewIsFetching,
isSuccess: creditPreviewInitialized,
} = useCreditTopUpPreview(
{
slug,
amount: validAmount,
address: debouncedAddress,
taxId: debouncedTaxId ?? undefined,
},
{ enabled: topUpModalVisible && !!validAmount }
)
const [captchaToken, setCaptchaToken] = useState<string | null>(null)
const [captchaRef, setCaptchaRef] = useState<HCaptcha | null>(null)
@@ -175,6 +227,8 @@ export const CreditTopUp = ({ slug }: { slug: string | undefined }) => {
setCaptchaRef(null)
setPaymentIntentConfirmation(undefined)
setPaymentIntentSecret('')
setLatestAddress(undefined)
setLatestTaxId(null)
}
}
@@ -288,6 +342,8 @@ export const CreditTopUp = ({ slug }: { slug: string | undefined }) => {
readOnly={executingTopUp || paymentConfirmationLoading}
useAsDefaultBillingAddress={useAsDefaultBillingAddress}
onUseAsDefaultBillingAddressChange={setUseAsDefaultBillingAddress}
onAddressChange={handleAddressChange}
onTaxIdChange={handleTaxIdChange}
/>
)}
/>
@@ -324,6 +380,25 @@ export const CreditTopUp = ({ slug }: { slug: string | undefined }) => {
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
{creditPreviewInitialized && !!validAmount && (
<div className="mt-4">
<ChargeBreakdown
subtotal={creditPreview.amount}
total={creditPreview.total}
tax={
creditPreview.tax
? {
amount: creditPreview.tax.tax_amount,
percentage: creditPreview.tax.tax_rate_percentage,
}
: undefined
}
taxStatus={creditPreview.tax_status}
isFetching={creditPreviewIsFetching}
/>
</div>
)}
</DialogSection>
{!paymentIntentConfirmation?.paymentIntent && (
@@ -332,6 +407,7 @@ export const CreditTopUp = ({ slug }: { slug: string | undefined }) => {
htmlType="submit"
type="primary"
loading={executingTopUp || paymentConfirmationLoading}
disabled={isPreviewStale || creditPreviewIsFetching}
>
Top Up
</Button>
+4
View File
@@ -32,4 +32,8 @@ export const organizationKeys = {
) => ['organizations', 'creation-preview', tier, params] as const,
previewCreditCode: (slug: string | undefined, code: string) =>
['organizations', slug, 'preview-credit-code', code] as const,
creditTopUpPreview: (
slug: string | undefined,
params?: { amount?: number; address?: Record<string, unknown>; taxId?: Record<string, unknown> }
) => ['organizations', slug, 'credits', 'preview', params] as const,
}
@@ -0,0 +1,57 @@
import { keepPreviousData, useQuery } from '@tanstack/react-query'
import { organizationKeys } from './keys'
import type { CustomerAddress, CustomerTaxId } from './types'
import { handleError, post } from '@/data/fetchers'
import type { ResponseError, UseCustomQueryOptions } from '@/types'
export type CreditTopUpPreviewVariables = {
slug?: string
amount?: number
address?: CustomerAddress
taxId?: CustomerTaxId
}
export async function previewCreditTopUp({
slug,
amount,
address,
taxId,
}: CreditTopUpPreviewVariables) {
if (!slug) throw new Error('slug is required')
if (!amount) throw new Error('amount is required')
const { data, error } = await post(`/platform/organizations/{slug}/billing/credits/preview`, {
params: { path: { slug } },
body: {
amount,
...(address && { address }),
...(taxId && { tax_id: taxId }),
},
})
if (error) handleError(error)
return data
}
export type CreditTopUpPreviewData = Awaited<ReturnType<typeof previewCreditTopUp>>
export const useCreditTopUpPreview = <TData = CreditTopUpPreviewData>(
{ slug, amount, address, taxId }: CreditTopUpPreviewVariables,
{
enabled = true,
...options
}: UseCustomQueryOptions<CreditTopUpPreviewData, ResponseError, TData> = {}
) =>
useQuery<CreditTopUpPreviewData, ResponseError, TData>({
queryKey: organizationKeys.creditTopUpPreview(slug, {
amount,
address: address as Record<string, unknown> | undefined,
taxId: taxId as Record<string, unknown> | undefined,
}),
queryFn: () => previewCreditTopUp({ slug, amount, address, taxId }),
enabled: enabled && typeof slug !== 'undefined' && typeof amount !== 'undefined',
placeholderData: keepPreviousData,
...options,
})
+168 -63
View File
@@ -5625,6 +5625,27 @@ export interface operations {
}
content?: never
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
'v1-revoke-token': {
@@ -5688,6 +5709,27 @@ export interface operations {
'application/json': components['schemas']['OrganizationResponseV1'][]
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Unexpected error listing organizations */
500: {
headers: {
@@ -5718,6 +5760,27 @@ export interface operations {
'application/json': components['schemas']['OrganizationResponseV1']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Unexpected error creating an organization */
500: {
headers: {
@@ -5954,6 +6017,27 @@ export interface operations {
'application/json': components['schemas']['OrganizationProjectsResponse']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Failed to retrieve projects */
500: {
headers: {
@@ -5999,6 +6083,27 @@ export interface operations {
'application/json': components['schemas']['V1ProjectWithDatabaseResponse'][]
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
'v1-create-a-project': {
@@ -6022,6 +6127,27 @@ export interface operations {
'application/json': components['schemas']['V1ProjectResponse']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
'v1-get-project': {
@@ -7252,27 +7378,6 @@ export interface operations {
'application/json': components['schemas']['BranchResponse'][]
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Failed to retrieve database branches */
500: {
headers: {
@@ -7306,27 +7411,6 @@ export interface operations {
'application/json': components['schemas']['BranchResponse']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Failed to create database branch */
500: {
headers: {
@@ -7405,27 +7489,6 @@ export interface operations {
'application/json': components['schemas']['BranchResponse']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Failed to fetch database branch */
500: {
headers: {
@@ -12898,6 +12961,27 @@ export interface operations {
'application/json': components['schemas']['SnippetList']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Failed to list user's SQL snippets */
500: {
headers: {
@@ -12926,6 +13010,27 @@ export interface operations {
'application/json': components['schemas']['SnippetResponse']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Failed to retrieve SQL snippet */
500: {
headers: {
+438 -3
View File
@@ -1077,6 +1077,23 @@ export interface paths {
patch?: never
trace?: never
}
'/platform/organizations/{slug}/billing/credits/preview': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** Preview for credit top-up */
post: operations['OrgCreditsController_previewTopUp']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/platform/organizations/{slug}/billing/credits/redeem': {
parameters: {
query?: never
@@ -1888,6 +1905,23 @@ export interface paths {
patch?: never
trace?: never
}
'/platform/organizations/onboarding-survey': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** Submit onboarding survey for a newly created organization */
post: operations['OrganizationsController_handleOnboardingSurvey']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/platform/organizations/preview-creation': {
parameters: {
query?: never
@@ -4988,11 +5022,24 @@ export interface components {
workdir: string
}
CreateInvitationBody: {
email: string
/**
* Format: email
* @deprecated
*/
email?: string
emails?: string[]
require_sso?: boolean
role_id: number
role_scoped_projects?: string[]
}
CreateInvitationResponse: {
failed: {
/** Format: email */
email: string
error: string
}[]
succeeded: string[]
} | null
CreateNamespaceBody: {
namespace: string
}
@@ -5222,6 +5269,8 @@ export interface components {
| 'action_runs_read'
| 'action_runs_write'
| 'advisors_read'
| 'analytics_config_read'
| 'analytics_config_write'
| 'analytics_logs_read'
| 'analytics_usage_read'
| 'api_gateway_keys_read'
@@ -5685,6 +5734,8 @@ export interface components {
| 'action_runs_read'
| 'action_runs_write'
| 'advisors_read'
| 'analytics_config_read'
| 'analytics_config_write'
| 'analytics_logs_read'
| 'analytics_usage_read'
| 'api_gateway_keys_read'
@@ -7631,6 +7682,13 @@ export interface components {
)[]
website: string
}
OnboardingSurveyBody: {
building?: string
heard_from?: string
kind?: string
size?: string
slug: string
}
OrganizationProjectsResponse: {
pagination: {
/** @description Total number of projects. Use this to calculate the total number of pages. */
@@ -8271,6 +8329,36 @@ export interface components {
name: string
schema: string
}
PreviewCreditTopUpRequest: {
address?: {
city?: string | null
country: string
line1: string
line2?: string | null
postal_code?: string | null
state?: string | null
}
amount: number
tax_id?: {
country: string
type: string
value: string
}
}
PreviewCreditTopUpResponse: {
amount: number
currency: string
tax: {
currency: string
tax_amount: number
tax_rate_percentage: number
total_amount_excluding_tax: number
total_amount_including_tax: number
} | null
/** @enum {string} */
tax_status: 'calculated' | 'not_applicable' | 'failed'
total: number
}
PreviewOrganizationCreationBody: {
address?: {
city?: string | null
@@ -8648,7 +8736,7 @@ export interface components {
| '48xlarge_optimized_cpu'
| '48xlarge_high_memory'
inserted_at: string
integration_source?: string | null
integration_source: string | null
is_branch_enabled: boolean
is_physical_backups_enabled: boolean
lastDatabaseResizeAt?: string
@@ -10591,6 +10679,8 @@ export interface components {
| 'action_runs_read'
| 'action_runs_write'
| 'advisors_read'
| 'analytics_config_read'
| 'analytics_config_write'
| 'analytics_logs_read'
| 'analytics_usage_read'
| 'api_gateway_keys_read'
@@ -14002,6 +14092,27 @@ export interface operations {
'application/json': components['schemas']['ListPlatformAppsResponse']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
PlatformAppsController_createPlatformApp: {
@@ -14028,6 +14139,27 @@ export interface operations {
'application/json': components['schemas']['CreatePlatformAppResponse']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
PlatformAppsController_getPlatformApp: {
@@ -14051,6 +14183,27 @@ export interface operations {
'application/json': components['schemas']['GetPlatformAppResponse']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
PlatformAppsController_deletePlatformApp: {
@@ -14072,6 +14225,27 @@ export interface operations {
}
content?: never
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
PlatformAppsController_updatePlatformApp: {
@@ -14099,6 +14273,27 @@ export interface operations {
'application/json': components['schemas']['GetPlatformAppResponse']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
PlatformAppSigningKeysController_listPlatformAppSigningKeys: {
@@ -14122,6 +14317,27 @@ export interface operations {
'application/json': components['schemas']['ListPlatformAppSigningKeysResponse']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
PlatformAppSigningKeysController_createPlatformAppSigningKey: {
@@ -14145,6 +14361,27 @@ export interface operations {
'application/json': components['schemas']['CreatePlatformAppSigningKeyResponse']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
PlatformAppSigningKeysController_deletePlatformAppSigningKey: {
@@ -14167,6 +14404,27 @@ export interface operations {
}
content?: never
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
PlatformAppInstallationsController_listPlatformAppInstallations: {
@@ -14189,6 +14447,27 @@ export interface operations {
'application/json': components['schemas']['ListPlatformAppInstallationsResponse']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
PlatformAppInstallationsController_installPlatformApp: {
@@ -14215,6 +14494,27 @@ export interface operations {
'application/json': components['schemas']['InstallPlatformAppResponse']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
PlatformAppInstallationsController_getPlatformAppInstallation: {
@@ -14238,6 +14538,27 @@ export interface operations {
'application/json': components['schemas']['GetPlatformAppInstallationResponse']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
PlatformAppInstallationsController_uninstallPlatformInstallation: {
@@ -14259,6 +14580,27 @@ export interface operations {
}
content?: never
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
PlatformAppInstallationsController_updatePlatformAppInstallation: {
@@ -14282,6 +14624,27 @@ export interface operations {
'application/json': components['schemas']['UpdatePlatformAppInstallationResponse']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
OrgAuditLogsController_getAuditLogs: {
@@ -14393,6 +14756,54 @@ export interface operations {
}
}
}
OrgCreditsController_previewTopUp: {
parameters: {
query?: never
header?: never
path: {
/** @description Organization slug */
slug: string
}
cookie?: never
}
requestBody: {
content: {
'application/json': components['schemas']['PreviewCreditTopUpRequest']
}
}
responses: {
/** @description Credit top-up preview with tax calculation. */
200: {
headers: {
[name: string]: unknown
}
content: {
'application/json': components['schemas']['PreviewCreditTopUpResponse']
}
}
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Forbidden action */
403: {
headers: {
[name: string]: unknown
}
content?: never
}
/** @description Rate limit exceeded */
429: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
OrgCreditsController_redeemCode: {
parameters: {
query?: never
@@ -15910,11 +16321,14 @@ export interface operations {
}
}
responses: {
/** @description Batch invitations result when using "emails" field. null/no body when using legacy "email" field. */
201: {
headers: {
[name: string]: unknown
}
content?: never
content: {
'application/json': components['schemas']['CreateInvitationResponse']
}
}
/** @description Unauthorized */
401: {
@@ -17462,6 +17876,27 @@ export interface operations {
}
}
}
OrganizationsController_handleOnboardingSurvey: {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
requestBody: {
content: {
'application/json': components['schemas']['OnboardingSurveyBody']
}
}
responses: {
204: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
OrganizationsController_previewOrganizationCreation: {
parameters: {
query?: never