From 41e6b66d7bcf7c7f30021db6ac1a6e719fa1f67d Mon Sep 17 00:00:00 2001 From: Ignacio Dobronich Date: Mon, 6 Apr 2026 13:24:59 -0300 Subject: [PATCH] feat(billing): show tax in subscription upgrade preview (#44258) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR moves the client-side "Charge Today" calculation to the backend - relying on the `preview` endpoint data to show the `Charge Today`, `Prorated Credits` and `Customer Balance` in the Subscription Upgrade Preview. It also includes fields for displaying Tax information in the preview, only for enabled orgs. Do note that at the moment the Tax Preview will not change if the address in the Subscription Preview changes, that will be tackled in a follow-up PR. ## Changes - Replace client-side charge calculation with backend data: The upgrade dialog previously computed the prorated credit and total charge locally (using subscription period timestamps and plan prices). This PR uses the `upfront_charge` object returned by the subscription preview API instead. - Display itemized tax breakdown: When the backend returns tax data, the dialog now shows a line-by-line breakdown: plan cost → subtotal (if different) → tax (with rate %) → total charged today. - Use `tax_status` from the subscription preview to conditionally show tax details, and display a warning when tax calculation fails. ## Testing ### No taxes - Upgrade from Free to Paid Plan - With a Free Org with a billing address in Canada or any other non-enabled jurisdiction, start the upgrade to the Pro Plan - Assert that only the Charge Today field is shown in the summary image ### Taxes - Upgrade Plan - Update an Org's Orb Customer on the Free or Pro Plan with the `automatic_tax_enabled: true` flag. ``` curl --location --request PUT 'https://api.withorb.com/v1/customers/external_customer_id/{ORG_SLUG}' \ --header 'Content-Type: application/json' \ --header 'Accept: application/json' \ --header 'Authorization: ••••••' \ --data '{ "tax_configuration": { "tax_exempt": false, "tax_provider": "numeral", "automatic_tax_enabled": true } }' ``` - Update the Org address to a valid taxable address, like: ``` "address": { "country": "US", "line1": "100 Congress Ave", "city": "Austin", "state": "TX", "postal_code": "78701" } ``` - Assert that the tax information is shown in the preview, including Tax Rate, Prorated credits (if applicable), Customer balance (if applicable) and the price of the intended plan. image ## Summary by CodeRabbit * **New Features** * Added itemized breakdown of subscription charges, including plan cost, unused time credits, subtotal, and applicable taxes. * Enhanced billing estimates with detailed tax information display. * **Improvements** * Updated "Charge today" calculations to reflect real-time preview data for greater accuracy. * Improved billing estimate UI layout and clarity with better organization of charge details. --- .../SubscriptionPlanUpdateDialog.tsx | 171 +++-- ...ganization-billing-subscription-preview.ts | 38 +- packages/api-types/types/api.d.ts | 583 ++++++++++++++++-- packages/api-types/types/platform.d.ts | 231 ++++++- 4 files changed, 888 insertions(+), 135 deletions(-) diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/SubscriptionPlanUpdateDialog.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/SubscriptionPlanUpdateDialog.tsx index ccf817158c6..2fe4f3609fd 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/SubscriptionPlanUpdateDialog.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/SubscriptionPlanUpdateDialog.tsx @@ -20,7 +20,7 @@ import { getPlanChangeType, } from '@/components/interfaces/Billing/Subscription/Subscription.utils' import AlertError from '@/components/ui/AlertError' -import { OrganizationBillingSubscriptionPreviewResponse } from '@/data/organizations/organization-billing-subscription-preview' +import { OrganizationBillingSubscriptionPreviewData } from '@/data/organizations/organization-billing-subscription-preview' import { OrgProject } from '@/data/projects/org-projects-infinite-query' import { useConfirmPendingSubscriptionChangeMutation } from '@/data/subscriptions/org-subscription-confirm-pending-change' import { useOrgSubscriptionUpdateMutation } from '@/data/subscriptions/org-subscription-update-mutation' @@ -54,6 +54,10 @@ const DOWNGRADE_PLAN_HEADINGS = { type DowngradePlanHeadingKey = keyof typeof DOWNGRADE_PLAN_HEADINGS +type BreakdownItem = + | { type: 'amount'; label: string; amount: number; tooltip?: string } + | { type: 'notice'; label: string } + interface Props { selectedTier: 'tier_free' | 'tier_pro' | 'tier_team' | undefined onClose: () => void @@ -61,7 +65,7 @@ interface Props { subscriptionPreviewError: any subscriptionPreviewIsLoading: boolean subscriptionPreviewInitialized: boolean - subscriptionPreview: OrganizationBillingSubscriptionPreviewResponse | undefined + subscriptionPreview: OrganizationBillingSubscriptionPreviewData | undefined subscription: any currentPlanMeta: any projects: OrgProject[] @@ -211,23 +215,94 @@ export const SubscriptionPlanUpdateDialog = ({ }) : [] - // Calculate remaining days in current billing cycle - const now = Math.floor(Date.now() / 1000) // current time in seconds - const remainingSeconds = subscription?.current_period_end - now - const totalSeconds = subscription?.current_period_end - subscription?.current_period_start - const remainingRatio = remainingSeconds / totalSeconds + const upfrontCharge = subscriptionPreview?.upfront_charge - // Calculate prorated credit for current plan - const currentPlanMonthlyPrice = currentPlanMeta?.price ?? 0 - const proratedCredit = currentPlanMonthlyPrice * remainingRatio + const proratedCredit = upfrontCharge?.prorated_credit ?? 0 + const customerBalance = upfrontCharge?.customer_balance ?? 0 + const totalCharge = upfrontCharge?.total ?? 0 + const tax = upfrontCharge?.tax + const taxableAmount = upfrontCharge?.taxable_amount + const taxStatus = upfrontCharge?.tax_status + const hasTax = taxStatus === 'calculated' && (tax?.tax_amount ?? 0) > 0 + const taxFailed = taxStatus === 'failed' - // Calculate new plan cost - const newPlanCost = Number(subscriptionPlanMeta?.priceMonthly) ?? 0 + const newPlanCost = Number(subscriptionPlanMeta?.priceMonthly) || 0 - const customerBalance = ((subscription?.customer_balance ?? 0) / 100) * -1 + const currentPlanId = subscription?.plan?.id + const currentPlanName = subscription?.plan?.name - // Calculate total charge (new plan - prorated credit) - const totalCharge = Math.max(0, newPlanCost - proratedCredit - customerBalance) + // Derives the itemized charge breakdown rows shown above "Charge today". + // Example: Pro -> Team upgrade with proration, tax, and credits: + // Team Plan $25.00 + // Unused Time on Pro -$8.33 + // Subtotal $16.67 + // Tax (10%) $1.67 + // Credits -$5.00 + // ───────────────────────────── + // Charge today $13.34 + const breakdownItems = useMemo(() => { + const items: BreakdownItem[] = [] + + if (currentPlanId !== 'free' && proratedCredit > 0) { + items.push({ + type: 'amount', + label: `Unused Time on ${currentPlanName} Plan`, + amount: -proratedCredit, + tooltip: + 'Your previous plan was charged upfront, so a plan change will prorate any unused time in credits. If the prorated credits exceed the new plan charge, the excessive credits are added to your organization for future use.', + }) + } + + if (hasTax && tax) { + if (taxableAmount !== newPlanCost) { + items.push({ type: 'amount', label: 'Subtotal', amount: taxableAmount! }) + } + items.push({ + type: 'amount', + label: `Tax (${tax.tax_rate_percentage}%)`, + amount: tax.tax_amount, + }) + } + + if (taxFailed) { + items.push({ + type: 'notice', + label: 'Tax could not be estimated and may be applied separately', + }) + } + + if (customerBalance > 0) { + items.push({ + type: 'amount', + label: 'Credits', + amount: -customerBalance, + tooltip: 'Credits will be used first before charging your card.', + }) + } + + // Prepend the plan cost row when there are adjustment items to show + if (changeType !== 'downgrade' && items.length > 0) { + items.unshift({ + type: 'amount', + label: `${subscriptionPlanMeta?.name} Plan`, + amount: newPlanCost, + }) + } + + return items + }, [ + currentPlanId, + currentPlanName, + proratedCredit, + hasTax, + tax, + taxableAmount, + newPlanCost, + taxFailed, + customerBalance, + changeType, + subscriptionPlanMeta?.name, + ]) return (
+ {breakdownItems.map((item, i) => + item.type === 'amount' ? ( +
+
+ {item.label} + {item.tooltip && ( + {item.tooltip} + )} +
+
+ {formatCurrency(item.amount)} +
+
+ ) : ( +
+
{item.label}
+
+ ) + )} +
Charge today
-
+
{formatCurrency(totalCharge)} - {subscription?.plan?.id !== 'free' && ( + {currentPlanId !== 'free' && ( <> {' '}
- {subscription?.plan?.id !== 'free' && ( -
-
- Unused Time on {subscription?.plan?.name} Plan - - Your previous plan was charged upfront, so a plan change will prorate - any unused time in credits. If the prorated credits exceed the new plan - charge, the excessive credits are added to your organization for future - use. - -
-
- -{formatCurrency(proratedCredit)} -
-
- )} - - {/* Ignore rare case with negative balance (debt) */} - {customerBalance > 0 && ( -
-
- Credits - - Credits will be used first before charging your card. - -
-
- {formatCurrency(customerBalance)} -
-
- )} - -
+
Monthly invoice estimate @@ -543,7 +612,7 @@ export const SubscriptionPlanUpdateDialog = ({
-
+
{formatCurrency( subscriptionPreview?.breakdown.reduce( (prev: number, cur) => prev + cur.total_price, diff --git a/apps/studio/data/organizations/organization-billing-subscription-preview.ts b/apps/studio/data/organizations/organization-billing-subscription-preview.ts index 8707b68abb7..d91aac9749f 100644 --- a/apps/studio/data/organizations/organization-billing-subscription-preview.ts +++ b/apps/studio/data/organizations/organization-billing-subscription-preview.ts @@ -10,42 +10,6 @@ export type OrganizationBillingSubscriptionPreviewVariables = { tier?: SubscriptionTier } -export type OrganizationBillingSubscriptionPreviewResponse = { - breakdown: { - description: string - unit_price: number - unit_price_desc?: string - quantity?: number - total_price: number - breakdown?: { - project_name: string - project_ref: string - usage: number - }[] - }[] - number_of_projects?: number - plan_change_type?: 'downgrade' | 'none' | 'upgrade' - active_projects?: { - status: - | 'INACTIVE' - | 'ACTIVE_HEALTHY' - | 'ACTIVE_UNHEALTHY' - | 'COMING_UP' - | 'UNKNOWN' - | 'GOING_DOWN' - | 'INIT_FAILED' - | 'REMOVED' - | 'RESTORING' - | 'RESTARTING' - | 'RESIZING' - | 'UPGRADING' - instance_size: string - name: string - ref: string - }[] - billed_via_partner?: boolean -} - export async function previewOrganizationBillingSubscription({ organizationSlug, tier, @@ -68,7 +32,7 @@ export async function previewOrganizationBillingSubscription({ if (error) handleError(error) - return data as OrganizationBillingSubscriptionPreviewResponse + return data } export type OrganizationBillingSubscriptionPreviewData = Awaited< diff --git a/packages/api-types/types/api.d.ts b/packages/api-types/types/api.d.ts index e0199788cd9..a358c04b180 100644 --- a/packages/api-types/types/api.d.ts +++ b/packages/api-types/types/api.d.ts @@ -241,6 +241,26 @@ export interface paths { patch?: never trace?: never } + '/v1/organizations/{slug}/entitlements': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * Get entitlements for an organization + * @description Returns the entitlements available to the organization based on their plan and any overrides. + */ + get: operations['v1-get-organization-entitlements'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } '/v1/organizations/{slug}/members': { parameters: { query?: never @@ -298,6 +318,23 @@ export interface paths { patch?: never trace?: never } + '/v1/profile': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** Gets the user's profile */ + get: operations['v1-get-profile'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } '/v1/projects': { parameters: { query?: never @@ -2075,6 +2112,10 @@ export interface components { /** Format: date-time */ updated_at?: string | null } + /** @example { + * "addon_variant": "pitr_7", + * "addon_type": "pitr" + * } */ ApplyProjectAddonBody: { /** @enum {string} */ addon_type: @@ -2289,6 +2330,7 @@ export interface components { oauth_server_allow_dynamic_registration: boolean oauth_server_authorization_path: string | null oauth_server_enabled: boolean + passkey_enabled: boolean password_hibp_enabled: boolean | null password_min_length: number | null /** @enum {string|null} */ @@ -2356,11 +2398,21 @@ export interface components { smtp_sender_name: string | null smtp_user: string | null uri_allow_list: string | null + webauthn_rp_display_name: string | null + webauthn_rp_id: string | null + webauthn_rp_origins: string | null } + /** @example { + * "role": "postgres", + * "rhost": "203.0.113.10" + * } */ AuthorizeJitAccessBody: { rhost: string role: string } + /** @example { + * "migration_version": "20250312000000" + * } */ BranchActionBody: { migration_version?: string } @@ -2458,6 +2510,17 @@ export interface components { message: 'ok' workflow_run_id: string } + /** @example [ + * { + * "id": "3c078cce-ad70-4148-9f37-4da362789053", + * "slug": "hello-world", + * "name": "Hello World", + * "status": "ACTIVE", + * "version": 2, + * "verify_jwt": true, + * "entrypoint_path": "index.ts" + * } + * ] */ BulkUpdateFunctionBody: { /** Format: int64 */ created_at?: number @@ -2492,6 +2555,11 @@ export interface components { version: number }[] } + /** @example { + * "type": "secret", + * "name": "ci_secret_key", + * "description": "CI deploy key" + * } */ CreateApiKeyBody: { description?: string | null name: string @@ -2501,6 +2569,13 @@ export interface components { /** @enum {string} */ type: 'publishable' | 'secret' } + /** @example { + * "branch_name": "preview-login-page", + * "git_branch": "feature/login-page", + * "persistent": true, + * "with_data": false, + * "notify_url": "https://example.com/webhooks/branches" + * } */ CreateBranchBody: { branch_name: string /** @enum {string} */ @@ -2549,6 +2624,9 @@ export interface components { } with_data?: boolean } + /** @example { + * "name": "Acme" + * } */ CreateOrganizationV1: { name: string } @@ -2560,6 +2638,26 @@ export interface components { token: string token_alias: string } + /** @example { + * "type": "saml", + * "metadata_url": "https://sso.acme.com/metadata.xml", + * "domains": [ + * "acme.com" + * ], + * "attribute_mapping": { + * "keys": { + * "email": { + * "name": "email" + * }, + * "first_name": { + * "name": "first_name" + * }, + * "last_name": { + * "name": "last_name" + * } + * } + * } + * } */ CreateProviderBody: { attribute_mapping?: { keys: { @@ -2619,6 +2717,9 @@ export interface components { } updated_at?: string } + /** @example { + * "read_only": true + * } */ CreateRoleBody: { read_only: boolean } @@ -2628,14 +2729,25 @@ export interface components { /** Format: int64 */ ttl_seconds: number } + /** @example [ + * { + * "name": "OPENAI_API_KEY", + * "value": "sk-example-secret" + * }, + * { + * "name": "STRIPE_WEBHOOK_SECRET", + * "value": "whsec_example" + * } + * ] */ CreateSecretBody: { - /** - * @description Secret name must not start with the SUPABASE_ prefix. - * @example string - */ + /** @description Secret name must not start with the SUPABASE_ prefix. */ name: string value: string }[] + /** @example { + * "algorithm": "RS256", + * "status": "standby" + * } */ CreateSigningKeyBody: { /** @enum {string} */ algorithm: 'EdDSA' | 'ES256' | 'RS256' | 'HS256' @@ -2714,6 +2826,10 @@ export interface components { /** @enum {string} */ status?: 'in_use' | 'standby' } + /** @example { + * "oidc_issuer_url": "https://login.acme.com", + * "jwks_url": "https://login.acme.com/.well-known/jwks.json" + * } */ CreateThirdPartyAuthBody: { custom_jwks?: unknown jwks_url?: string @@ -2788,6 +2904,9 @@ export interface components { /** @enum {string} */ message: 'ok' } + /** @example [ + * "OPENAI_API_KEY" + * ] */ DeleteSecretsBody: string[] DeployFunctionResponse: { /** Format: int64 */ @@ -2814,6 +2933,14 @@ export interface components { /** @description Minimum increment size for disk autoscaling in GB */ min_increment_gb: number | null } + /** @example { + * "attributes": { + * "type": "gp3", + * "size_gb": 100, + * "iops": 3000, + * "throughput_mibps": 125 + * } + * } */ DiskRequestBody: { attributes: | { @@ -2855,6 +2982,16 @@ export interface components { } timestamp: string } + /** @example { + * "file": [ + * "./supabase/functions/hello-world/index.ts" + * ], + * "metadata": { + * "entrypoint_path": "index.ts", + * "verify_jwt": true, + * "name": "Hello World" + * } + * } */ FunctionDeployBody: { file?: string[] metadata: { @@ -2953,6 +3090,9 @@ export interface components { } updated_at?: string } + /** @example { + * "state": "enabled" + * } */ JitAccessRequestRequest: { /** @enum {string} */ state: 'enabled' | 'disabled' | 'unavailable' @@ -3177,6 +3317,18 @@ export interface components { type: string }[] } + /** @example { + * "add": { + * "dbAllowedCidrs": [ + * "203.0.113.0/24" + * ] + * }, + * "remove": { + * "dbAllowedCidrs": [ + * "198.51.100.0/24" + * ] + * } + * } */ NetworkRestrictionsPatchRequest: { add?: { dbAllowedCidrs?: string[] @@ -3187,6 +3339,14 @@ export interface components { dbAllowedCidrsV6?: string[] } } + /** @example { + * "dbAllowedCidrs": [ + * "203.0.113.0/24" + * ], + * "dbAllowedCidrsV6": [ + * "2001:db8::/32" + * ] + * } */ NetworkRestrictionsRequest: { dbAllowedCidrs?: string[] dbAllowedCidrsV6?: string[] @@ -3201,7 +3361,17 @@ export interface components { } /** @enum {string} */ entitlement: 'disallowed' | 'allowed' - /** @description Populated when a new config has been received, but not registered as successfully applied to a project. */ + /** + * @description Populated when a new config has been received, but not registered as successfully applied to a project. + * @example { + * "dbAllowedCidrs": [ + * "203.0.113.0/24" + * ], + * "dbAllowedCidrsV6": [ + * "2001:db8::/32" + * ] + * } + */ old_config?: { dbAllowedCidrs?: string[] dbAllowedCidrsV6?: string[] @@ -3237,12 +3407,26 @@ export interface components { /** Format: date-time */ updated_at?: string } + /** @example { + * "client_id": "66666666-6666-4666-8666-666666666666", + * "client_secret": "sb_secret_live_example_9f4d3a206b2e4a7e8c91", + * "refresh_token": "oauth_refresh_9f4d3a206b2e4a7e8c91" + * } */ OAuthRevokeTokenBody: { /** Format: uuid */ client_id: string client_secret: string refresh_token: string } + /** @example { + * "grant_type": "authorization_code", + * "client_id": "66666666-6666-4666-8666-666666666666", + * "client_secret": "sb_secret_live_example_9f4d3a206b2e4a7e8c91", + * "code": "oauth_code_9f4d3a206b2e4a7e8c91", + * "code_verifier": "qW0Z6d9pQnW0mL1dK1q9wFq6Yz2nV5rA8jT3mP7sH4c", + * "redirect_uri": "https://app.acme.com/auth/callback", + * "scope": "projects:read projects:write" + * } */ OAuthTokenBody: { /** Format: uuid */ client_id?: string @@ -3390,7 +3574,10 @@ export interface components { */ id: string name: string - /** @description Organization slug */ + /** + * @description Organization slug + * @example tsrqponmlkjihgfedcba + */ slug: string } PgsodiumConfigResponse: { @@ -3641,6 +3828,12 @@ export interface components { }[] } } + /** @example { + * "ipv4_addresses": [ + * "203.0.113.10" + * ], + * "requester_ip": false + * } */ RemoveNetworkBanRequest: { identifier?: string /** @description List of IP addresses to unban. */ @@ -3651,6 +3844,9 @@ export interface components { */ requester_ip?: boolean } + /** @example { + * "database_identifier": "abcdefghijklmnopqrst-rr-us-west-1-abcde" + * } */ RemoveReadReplicaBody: { database_identifier: string } @@ -3659,10 +3855,12 @@ export interface components { updated_at?: string value: string } + /** @example { + * "read_replica_region": "us-west-1" + * } */ SetUpReadReplicaBody: { /** * @description Region you want your read replica to reside in - * @example us-east-1 * @enum {string} */ read_replica_region: @@ -3773,6 +3971,11 @@ export interface components { /** @enum {string} */ visibility: 'user' | 'project' | 'org' | 'public' } + /** @example { + * "requestedConfig": { + * "database": true + * } + * } */ SslEnforcementRequest: { requestedConfig: { database: boolean @@ -3853,6 +4056,10 @@ export interface components { TypescriptResponse: { types: string } + /** @example { + * "name": "ci_secret_key_rotated", + * "description": "Rotated after March release" + * } */ UpdateApiKeyBody: { description?: string | null name?: string @@ -3860,6 +4067,11 @@ export interface components { [key: string]: unknown } | null } + /** @example { + * "site_url": "https://app.example.com", + * "disable_signup": false, + * "jwt_exp": 3600 + * } */ UpdateAuthConfigBody: { api_max_request_duration?: number | null custom_oauth_enabled?: boolean @@ -4037,6 +4249,7 @@ export interface components { oauth_server_allow_dynamic_registration?: boolean | null oauth_server_authorization_path?: string | null oauth_server_enabled?: boolean | null + passkey_enabled?: boolean password_hibp_enabled?: boolean | null password_min_length?: number | null /** @enum {string|null} */ @@ -4103,7 +4316,17 @@ export interface components { smtp_sender_name?: string | null smtp_user?: string | null uri_allow_list?: string | null + webauthn_rp_display_name?: string | null + webauthn_rp_id?: string | null + webauthn_rp_origins?: string | null } + /** @example { + * "branch_name": "preview-login-page", + * "git_branch": "feature/login-page", + * "persistent": true, + * "request_review": true, + * "notify_url": "https://example.com/webhooks/branches" + * } */ UpdateBranchBody: { branch_name?: string git_branch?: string @@ -4128,6 +4351,9 @@ export interface components { | 'FUNCTIONS_DEPLOYED' | 'FUNCTIONS_FAILED' } + /** @example { + * "custom_hostname": "docs.example.com" + * } */ UpdateCustomHostnameBody: { custom_hostname: string } @@ -4168,6 +4394,22 @@ export interface components { | '4_origin_setup_completed' | '5_services_reconfigured' } + /** @example { + * "user_id": "55555555-5555-4555-8555-555555555555", + * "roles": [ + * { + * "role": "postgres", + * "expires_at": 1740787200, + * "allowed_networks": { + * "allowed_cidrs": [ + * { + * "cidr": "203.0.113.0/24" + * } + * ] + * } + * } + * ] + * } */ UpdateJitAccessBody: { roles: { allowed_networks?: { @@ -4184,9 +4426,18 @@ export interface components { /** Format: uuid */ user_id: string } + /** @example { + * "root_key": "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=" + * } */ UpdatePgsodiumConfigBody: { root_key: string } + /** @example { + * "max_connections": 120, + * "shared_buffers": "256MB", + * "work_mem": "4MB", + * "statement_timeout": "60000ms" + * } */ UpdatePostgresConfigBody: { /** @description Default unit: s */ checkpoint_timeout?: string @@ -4219,6 +4470,13 @@ export interface components { wal_sender_timeout?: string work_mem?: string } + /** @example { + * "metadata_url": "https://sso.acme.com/metadata.xml", + * "domains": [ + * "acme.com", + * "contractors.acme.com" + * ] + * } */ UpdateProviderBody: { attribute_mapping?: { keys: { @@ -4273,6 +4531,11 @@ export interface components { } updated_at?: string } + /** @example { + * "private_only": false, + * "max_concurrent_users": 1000, + * "max_channels_per_client": 100 + * } */ UpdateRealtimeConfigBody: { /** @description Sets connection pool size for Realtime Authorization */ connection_pool?: number @@ -4297,6 +4560,12 @@ export interface components { /** @description Disables the Realtime service for this project when true. Set to false to re-enable it. */ suspend?: boolean } + /** @example { + * "clone": "RUNNING", + * "configure": "RUNNING", + * "migrate": "RUNNING", + * "deploy": "CREATED" + * } */ UpdateRunStatusBody: { /** @enum {string} */ clone?: 'CREATED' | 'DEAD' | 'EXITED' | 'PAUSED' | 'REMOVING' | 'RESTARTING' | 'RUNNING' @@ -4317,10 +4586,21 @@ export interface components { /** @enum {string} */ message: 'ok' } + /** @example { + * "status": "standby" + * } */ UpdateSigningKeyBody: { /** @enum {string} */ status: 'in_use' | 'previously_used' | 'revoked' | 'standby' } + /** @example { + * "fileSizeLimit": 10485760, + * "features": { + * "imageTransformation": { + * "enabled": true + * } + * } + * } */ UpdateStorageConfigBody: { external?: { /** @enum {string} */ @@ -4348,6 +4628,10 @@ export interface components { /** Format: int64 */ fileSizeLimit?: number } + /** @example { + * "default_pool_size": 25, + * "pool_mode": "transaction" + * } */ UpdateSupavisorConfigBody: { default_pool_size?: number | null /** @@ -4360,6 +4644,10 @@ export interface components { default_pool_size: number | null pool_mode: string } + /** @example { + * "target_version": "17", + * "release_channel": "ga" + * } */ UpgradeDatabaseBody: { /** @enum {string} */ release_channel?: 'internal' | 'alpha' | 'beta' | 'ga' | 'withdrawn' | 'preview' @@ -4380,23 +4668,39 @@ export interface components { region: string walg_enabled: boolean } + /** @example { + * "slug": "hello-world", + * "name": "Hello World", + * "body": "Deno.serve(() => new Response('Hello, world!'))", + * "verify_jwt": true + * } */ V1CreateFunctionBody: { body: string name: string slug: string verify_jwt?: boolean } + /** @example { + * "query": "create table public.widgets(id bigint primary key);", + * "name": "create_widgets_table", + * "rollback": "drop table if exists public.widgets;" + * } */ V1CreateMigrationBody: { name?: string query: string rollback?: string } + /** @example { + * "db_pass": "correct-horse-battery-staple", + * "name": "acme-prod", + * "organization_slug": "tsrqponmlkjihgfedcba", + * "region": "us-east-1" + * } */ V1CreateProjectBody: { /** @description Database password */ db_pass: string /** * @description Desired instance size. Omit this field to always default to the smallest possible size. - * @example nano * @enum {string} */ desired_instance_size?: @@ -4431,7 +4735,10 @@ export interface components { * @description Deprecated: Use `organization_slug` instead. */ organization_id?: string - /** @description Organization slug */ + /** + * @description Organization slug + * @example tsrqponmlkjihgfedcba + */ organization_slug: string /** * @deprecated @@ -4442,7 +4749,6 @@ export interface components { /** * @deprecated * @description Region you want your server to reside in. Use region_selection instead. - * @example us-east-1 * @enum {string} */ region?: @@ -4464,10 +4770,7 @@ export interface components { | 'ca-central-1' | 'ap-south-1' | 'sa-east-1' - /** - * @description Region selection. Only one of region or region_selection can be specified. - * @example { type: 'smartGroup', code: 'americas' } - */ + /** @description Region selection. Only one of region or region_selection can be specified. */ region_selection?: | { /** @@ -4499,7 +4802,6 @@ export interface components { | { /** * @description The Smart Region Group's code. The codes supported are not a stable API, and should be retrieved from the /available-regions endpoint. - * @example apac * @enum {string} */ code: 'americas' | 'emea' | 'apac' @@ -4509,7 +4811,6 @@ export interface components { /** * Format: uri * @description Template URL used to create the project from the CLI. - * @example https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone */ template_url?: string } @@ -4564,6 +4865,89 @@ export interface components { count: number }[] } + V1ListEntitlementsResponse: { + entitlements: { + config: + | { + enabled: boolean + } + | { + enabled: boolean + unit: string + unlimited: boolean + value: number + } + | { + enabled: boolean + set: string[] + } + feature: { + /** @enum {string} */ + key: + | 'instances.compute_update_available_sizes' + | 'instances.read_replicas' + | 'instances.disk_modifications' + | 'instances.high_availability' + | 'instances.orioledb' + | 'replication.etl' + | 'storage.max_file_size' + | 'storage.max_file_size.configurable' + | 'storage.image_transformations' + | 'storage.vector_buckets' + | 'storage.iceberg_catalog' + | 'security.audit_logs_days' + | 'security.questionnaire' + | 'security.soc2_report' + | 'security.private_link' + | 'security.enforce_mfa' + | 'log.retention_days' + | 'custom_domain' + | 'vanity_subdomain' + | 'ipv4' + | 'pitr.available_variants' + | 'log_drains' + | 'branching_limit' + | 'branching_persistent' + | 'auth.mfa_phone' + | 'auth.mfa_web_authn' + | 'auth.mfa_enhanced_security' + | 'auth.hooks' + | 'auth.platform.sso' + | 'auth.custom_jwt_template' + | 'auth.saml_2' + | 'auth.user_sessions' + | 'auth.leaked_password_protection' + | 'auth.advanced_auth_settings' + | 'auth.performance_settings' + | 'auth.password_hibp' + | 'backup.retention_days' + | 'backup.restore_to_new_project' + | 'function.max_count' + | 'function.size_limit_mb' + | 'realtime.max_concurrent_users' + | 'realtime.max_events_per_second' + | 'realtime.max_joins_per_second' + | 'realtime.max_channels_per_client' + | 'realtime.max_bytes_per_second' + | 'realtime.max_presence_events_per_second' + | 'realtime.max_payload_size_in_kb' + | 'project_scoped_roles' + | 'security.member_roles' + | 'project_pausing' + | 'project_cloning' + | 'project_restore_after_expiry' + | 'assistant.advance_model' + | 'integrations.github_connections' + | 'dedicated_pooler' + | 'observability.dashboard_advanced_metrics' + /** @enum {string} */ + type: 'boolean' | 'numeric' | 'set' + } + hasAccess: boolean + /** @enum {string} */ + type: 'boolean' | 'numeric' | 'set' + }[] + } V1ListMigrationsResponse: { name?: string version: string @@ -4635,6 +5019,10 @@ export interface components { /** @enum {string} */ plan?: 'free' | 'pro' | 'team' | 'enterprise' | 'platform' } + /** @example { + * "name": "create_widgets_table", + * "rollback": "drop table if exists public.widgets;" + * } */ V1PatchMigrationBody: { name?: string rollback?: string @@ -4658,6 +5046,11 @@ export interface components { db_schema: string max_rows: number } + V1ProfileResponse: { + gotrue_id: string + primary_email: string + username: string + } V1ProjectAdvisorsResponse: { lints: { cache_key: string @@ -4718,10 +5111,7 @@ export interface components { ref: string } V1ProjectResponse: { - /** - * @description Creation timestamp - * @example 2023-03-29T16:32:59Z - */ + /** @description Creation timestamp */ created_at: string /** * @deprecated @@ -4735,14 +5125,17 @@ export interface components { * @description Deprecated: Use `organization_slug` instead. */ organization_id: string - /** @description Organization slug */ - organization_slug: string - /** @description Project ref */ - ref: string /** - * @description Region of your project - * @example us-east-1 + * @description Organization slug + * @example tsrqponmlkjihgfedcba */ + organization_slug: string + /** + * @description Project ref + * @example abcdefghijklmnopqrst + */ + ref: string + /** @description Region of your project */ region: string /** @enum {string} */ status: @@ -4763,10 +5156,7 @@ export interface components { | 'RESIZING' } V1ProjectWithDatabaseResponse: { - /** - * @description Creation timestamp - * @example 2023-03-29T16:32:59Z - */ + /** @description Creation timestamp */ created_at: string database: { /** @description Database host */ @@ -4790,14 +5180,17 @@ export interface components { * @description Deprecated: Use `organization_slug` instead. */ organization_id: string - /** @description Organization slug */ - organization_slug: string - /** @description Project ref */ - ref: string /** - * @description Region of your project - * @example us-east-1 + * @description Organization slug + * @example tsrqponmlkjihgfedcba */ + organization_slug: string + /** + * @description Project ref + * @example abcdefghijklmnopqrst + */ + ref: string + /** @description Region of your project */ region: string /** @enum {string} */ status: @@ -4817,14 +5210,23 @@ export interface components { | 'PAUSE_FAILED' | 'RESIZING' } + /** @example { + * "query": "select * from pg_stat_activity limit 1;" + * } */ V1ReadOnlyQueryBody: { parameters?: unknown[] query: string } + /** @example { + * "recovery_time_target_unix": 1740787200 + * } */ V1RestorePitrBody: { /** Format: int64 */ recovery_time_target_unix: number } + /** @example { + * "name": "before-upgrade" + * } */ V1RestorePointPostBody: { name: string } @@ -4835,6 +5237,10 @@ export interface components { /** @enum {string} */ status: 'AVAILABLE' | 'PENDING' | 'REMOVED' | 'FAILED' } + /** @example { + * "query": "select * from pg_stat_activity limit 1;", + * "read_only": true + * } */ V1RunQueryBody: { parameters?: unknown[] query: string @@ -4888,34 +5294,61 @@ export interface components { public: boolean updated_at: string } + /** @example { + * "name": "before-upgrade" + * } */ V1UndoBody: { name: string } + /** @example { + * "name": "Hello World", + * "body": "Deno.serve(() => new Response('Hello again!'))", + * "verify_jwt": true + * } */ V1UpdateFunctionBody: { body?: string name?: string verify_jwt?: boolean } + /** @example { + * "password": "correct-horse-battery-staple" + * } */ V1UpdatePasswordBody: { password: string } V1UpdatePasswordResponse: { message: string } + /** @example { + * "db_schema": "public,storage", + * "db_pool": 20, + * "max_rows": 1000 + * } */ V1UpdatePostgrestConfigBody: { db_extra_search_path?: string db_pool?: number db_schema?: string max_rows?: number } + /** @example { + * "name": "Acme Platform" + * } */ V1UpdateProjectBody: { name: string } + /** @example { + * "query": "create table public.widgets(id bigint primary key);", + * "name": "create_widgets_table", + * "rollback": "drop table if exists public.widgets;" + * } */ V1UpsertMigrationBody: { name?: string query: string rollback?: string } + /** @example { + * "vanity_subdomain": "acme-prod" + * } */ VanitySubdomainBody: { vanity_subdomain: string } @@ -4938,7 +5371,7 @@ export interface operations { query?: never header?: never path: { - /** @description Branch ID */ + /** @description Branch ref or deprecated branch ID */ branch_id_or_ref: string } cookie?: never @@ -4970,7 +5403,7 @@ export interface operations { } header?: never path: { - /** @description Branch ID */ + /** @description Branch ref or deprecated branch ID */ branch_id_or_ref: string } cookie?: never @@ -4999,7 +5432,7 @@ export interface operations { query?: never header?: never path: { - /** @description Branch ID */ + /** @description Branch ref or deprecated branch ID */ branch_id_or_ref: string } cookie?: never @@ -5036,7 +5469,7 @@ export interface operations { } header?: never path: { - /** @description Branch ID */ + /** @description Branch ref or deprecated branch ID */ branch_id_or_ref: string } cookie?: never @@ -5065,7 +5498,7 @@ export interface operations { query?: never header?: never path: { - /** @description Branch ID */ + /** @description Branch ref or deprecated branch ID */ branch_id_or_ref: string } cookie?: never @@ -5098,7 +5531,7 @@ export interface operations { query?: never header?: never path: { - /** @description Branch ID */ + /** @description Branch ref or deprecated branch ID */ branch_id_or_ref: string } cookie?: never @@ -5131,7 +5564,7 @@ export interface operations { query?: never header?: never path: { - /** @description Branch ID */ + /** @description Branch ref or deprecated branch ID */ branch_id_or_ref: string } cookie?: never @@ -5164,7 +5597,7 @@ export interface operations { query?: never header?: never path: { - /** @description Branch ID */ + /** @description Branch ref or deprecated branch ID */ branch_id_or_ref: string } cookie?: never @@ -5388,6 +5821,49 @@ export interface operations { } } } + 'v1-get-organization-entitlements': { + parameters: { + query?: never + header?: never + path: { + /** @description Organization slug */ + slug: string + } + cookie?: never + } + requestBody?: never + responses: { + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['V1ListEntitlementsResponse'] + } + } + /** @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-list-organization-members': { parameters: { query?: never @@ -5538,6 +6014,25 @@ export interface operations { } } } + 'v1-get-profile': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody?: never + responses: { + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['V1ProfileResponse'] + } + } + } + } 'v1-list-all-projects': { parameters: { query?: never diff --git a/packages/api-types/types/platform.d.ts b/packages/api-types/types/platform.d.ts index b63d459aedf..22dfa8ca4d6 100644 --- a/packages/api-types/types/platform.d.ts +++ b/packages/api-types/types/platform.d.ts @@ -1335,6 +1335,26 @@ export interface paths { patch?: never trace?: never } + '/platform/organizations/{slug}/documents/dpa-signed': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * Check if organization has signed any version of the DPA + * @description Results are cached per organization for up to 24 hours. Signed status may not reflect immediately after a document is completed. + */ + get: operations['OrgDocumentsController_getDpaSignedStatus'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } '/platform/organizations/{slug}/documents/soc2-type-2-report': { parameters: { query?: never @@ -4653,6 +4673,13 @@ export interface components { state?: string | null } billing_name?: string + /** @enum {boolean} */ + clear_tax_id?: true + tax_id?: { + country?: string + type: string + value: string + } } Buffer: Record BulkDeleteUserContentResponse: { @@ -6099,6 +6126,11 @@ export interface components { } timestamp: string } + DocumentSignedStatusResponse: { + /** Format: date-time */ + checked_at: string + signed: boolean + } DownloadableBackupsResponse: { backups: { id: number @@ -6964,6 +6996,7 @@ export interface components { OAUTH_SERVER_ALLOW_DYNAMIC_REGISTRATION: boolean OAUTH_SERVER_AUTHORIZATION_PATH: string | null OAUTH_SERVER_ENABLED: boolean + PASSKEY_ENABLED: boolean PASSWORD_HIBP_ENABLED: boolean PASSWORD_MIN_LENGTH: number PASSWORD_REQUIRED_CHARACTERS: string @@ -6984,6 +7017,7 @@ export interface components { SECURITY_MANUAL_LINKING_ENABLED: boolean SECURITY_REFRESH_TOKEN_REUSE_INTERVAL: number SECURITY_SB_FORWARDED_FOR_ENABLED: boolean + SECURITY_UPDATE_PASSWORD_REQUIRE_CURRENT_PASSWORD: boolean SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION: boolean SECURITY_UPDATE_PASSWORD_REQUIRE_CURRENT_PASSWORD: boolean SESSIONS_INACTIVITY_TIMEOUT: number @@ -7022,6 +7056,9 @@ export interface components { SMTP_SENDER_NAME: string SMTP_USER: string URI_ALLOW_LIST: string + WEBAUTHN_RP_DISPLAY_NAME: string | null + WEBAUTHN_RP_ID: string | null + WEBAUTHN_RP_ORIGINS: string | null } InstallPlatformAppBody: { /** Format: uuid */ @@ -7661,7 +7698,10 @@ export interface components { name: string projects: { name: string - /** @description Project ref */ + /** + * @description Project ref + * @example abcdefghijklmnopqrst + */ ref: string }[] }[] @@ -7672,7 +7712,10 @@ export interface components { name: string projects: { name: string - /** @description Project ref */ + /** + * @description Project ref + * @example abcdefghijklmnopqrst + */ ref: string }[] }[] @@ -8183,6 +8226,127 @@ export interface components { message: string }[] } + PreviewSubscriptionChangeResponse: { + active_projects: { + /** @enum {string} */ + instance_size: + | 'pico' + | 'nano' + | 'micro' + | 'small' + | 'medium' + | 'large' + | 'xlarge' + | '2xlarge' + | '4xlarge' + | '8xlarge' + | '12xlarge' + | '16xlarge' + | '24xlarge' + | '24xlarge_optimized_memory' + | '24xlarge_optimized_cpu' + | '24xlarge_high_memory' + | '48xlarge' + | '48xlarge_optimized_memory' + | '48xlarge_optimized_cpu' + | '48xlarge_high_memory' + name: string + ref: string + /** @enum {string} */ + status: + | 'INACTIVE' + | 'ACTIVE_HEALTHY' + | 'ACTIVE_UNHEALTHY' + | 'COMING_UP' + | 'UNKNOWN' + | 'GOING_DOWN' + | 'INIT_FAILED' + | 'REMOVED' + | 'RESTORING' + | 'UPGRADING' + | 'PAUSING' + | 'RESTORE_FAILED' + | 'RESTARTING' + | 'PAUSE_FAILED' + | 'RESIZING' + }[] + billed_via_partner: boolean + breakdown: { + breakdown?: { + amount?: number + /** @enum {string} */ + project_db_instance_size?: + | 'pico' + | 'nano' + | 'micro' + | 'small' + | 'medium' + | 'large' + | 'xlarge' + | '2xlarge' + | '4xlarge' + | '8xlarge' + | '12xlarge' + | '16xlarge' + | '24xlarge' + | '24xlarge_optimized_memory' + | '24xlarge_optimized_cpu' + | '24xlarge_high_memory' + | '48xlarge' + | '48xlarge_optimized_memory' + | '48xlarge_optimized_cpu' + | '48xlarge_high_memory' + project_name: string + project_ref: string + /** @enum {string} */ + project_status?: + | 'INACTIVE' + | 'ACTIVE_HEALTHY' + | 'ACTIVE_UNHEALTHY' + | 'COMING_UP' + | 'UNKNOWN' + | 'GOING_DOWN' + | 'INIT_FAILED' + | 'REMOVED' + | 'RESTORING' + | 'UPGRADING' + | 'PAUSING' + | 'RESTORE_FAILED' + | 'RESTARTING' + | 'PAUSE_FAILED' + | 'RESIZING' + usage: number + }[] + description: string + quantity?: number + total_price: number + unit_price?: number + unit_price_desc?: string + }[] + /** @enum {string} */ + monthly_invoice_tax_status: 'calculated' | 'not_applicable' | 'failed' + number_of_projects: number + /** @enum {boolean} */ + pending_subscription_flow: true + /** @enum {string} */ + plan_change_type: 'upgrade' | 'downgrade' | 'none' + slug: string + upfront_charge: { + customer_balance: number + prorated_credit: number + 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' + taxable_amount: number + total: number + } | null + } PrivateLinkResponse: { appliedSuccessfully: boolean currentConfig: { @@ -10131,6 +10295,7 @@ export interface components { OAUTH_SERVER_ALLOW_DYNAMIC_REGISTRATION?: boolean | null OAUTH_SERVER_AUTHORIZATION_PATH?: string | null OAUTH_SERVER_ENABLED?: boolean | null + PASSKEY_ENABLED?: boolean PASSWORD_HIBP_ENABLED?: boolean | null PASSWORD_MIN_LENGTH?: number | null /** @enum {string|null} */ @@ -10158,6 +10323,7 @@ export interface components { SECURITY_MANUAL_LINKING_ENABLED?: boolean | null SECURITY_REFRESH_TOKEN_REUSE_INTERVAL?: number | null SECURITY_SB_FORWARDED_FOR_ENABLED?: boolean | null + SECURITY_UPDATE_PASSWORD_REQUIRE_CURRENT_PASSWORD?: boolean | null SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION?: boolean | null SECURITY_UPDATE_PASSWORD_REQUIRE_CURRENT_PASSWORD?: boolean | null SESSIONS_INACTIVITY_TIMEOUT?: number | null @@ -10198,6 +10364,9 @@ export interface components { SMTP_SENDER_NAME?: string | null SMTP_USER?: string | null URI_ALLOW_LIST?: string | null + WEBAUTHN_RP_DISPLAY_NAME?: string | null + WEBAUTHN_RP_ID?: string | null + WEBAUTHN_RP_ORIGINS?: string | null } UpdateGoTrueConfigHooksBody: { HOOK_AFTER_USER_CREATED_ENABLED?: boolean | null @@ -10266,6 +10435,9 @@ export interface components { slug: string stripe_customer_id: string } + /** @example { + * "password": "correct-horse-battery-staple" + * } */ UpdatePasswordBody: { password: string } @@ -10769,6 +10941,14 @@ export interface components { file_size_limit?: number | null public: boolean } + /** @example { + * "fileSizeLimit": 10485760, + * "features": { + * "imageTransformation": { + * "enabled": true + * } + * } + * } */ UpdateStorageConfigBody: { external?: { /** @enum {string} */ @@ -14707,7 +14887,9 @@ export interface operations { headers: { [name: string]: unknown } - content?: never + content: { + 'application/json': components['schemas']['PreviewSubscriptionChangeResponse'] + } } /** @description Unauthorized */ 401: { @@ -15042,6 +15224,49 @@ export interface operations { } } } + OrgDocumentsController_getDpaSignedStatus: { + parameters: { + query?: never + header?: never + path: { + /** @description Organization slug */ + slug: string + } + cookie?: never + } + requestBody?: never + responses: { + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['DocumentSignedStatusResponse'] + } + } + /** @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 + } + } + } OrgDocumentsController_getSoc2Type2ReportUrl: { parameters: { query?: never